PageRenderTime 159ms CodeModel.GetById 30ms app.highlight 97ms RepoModel.GetById 24ms app.codeStats 0ms

/mordor/fiber.cpp

http://github.com/mozy/mordor
C++ | 683 lines | 598 code | 48 blank | 37 comment | 116 complexity | 2717039ffb69ada8f9f39089292f3d81 MD5 | raw file
  1// Copyright (c) 2009 - Mozy, Inc.
  2
  3#include "fiber.h"
  4
  5#include <boost/thread/tss.hpp>
  6
  7#include "assert.h"
  8#include "mordor/config.h"
  9#include "exception.h"
 10#include "statistics.h"
 11#include "version.h"
 12
 13#ifdef WINDOWS
 14#include <windows.h>
 15
 16#include "runtime_linking.h"
 17#else
 18#include <sys/mman.h>
 19#include <pthread.h>
 20#endif
 21
 22namespace Mordor {
 23
 24static AverageMinMaxStatistic<unsigned int> &g_statAlloc =
 25    Statistics::registerStatistic("fiber.allocstack",
 26    AverageMinMaxStatistic<unsigned int>("us"));
 27static AverageMinMaxStatistic<unsigned int> &g_statFree=
 28    Statistics::registerStatistic("fiber.freestack",
 29    AverageMinMaxStatistic<unsigned int>("us"));
 30static volatile unsigned int g_cntFibers = 0; // Active fibers
 31static MaxStatistic<unsigned int> &g_statMaxFibers=Statistics::registerStatistic("fiber.max",
 32    MaxStatistic<unsigned int>());
 33
 34#ifdef SETJMP_FIBERS
 35#ifdef OSX
 36#define setjmp _setjmp
 37#define longjmp _longjmp
 38#endif
 39#endif
 40
 41static size_t g_pagesize;
 42
 43namespace {
 44
 45static struct FiberInitializer {
 46    FiberInitializer()
 47    {
 48#ifdef WINDOWS
 49        SYSTEM_INFO info;
 50        GetSystemInfo(&info);
 51        g_pagesize = info.dwPageSize;
 52#elif defined(POSIX)
 53        g_pagesize = sysconf(_SC_PAGESIZE);
 54#endif
 55    }
 56} g_init;
 57
 58}
 59
 60static ConfigVar<size_t>::ptr g_defaultStackSize = Config::lookup<size_t>(
 61    "fiber.defaultstacksize",
 62#ifdef NATIVE_WINDOWS_FIBERS
 63    0u,
 64#else
 65    1024 * 1024u,
 66#endif
 67    "Default stack size for new fibers.  This is the virtual size; physical "
 68    "memory isn't consumed until it is actually referenced.");
 69
 70// t_fiber is the Fiber currently executing on this thread
 71// t_threadFiber is the Fiber that represents the thread's original stack
 72// t_threadFiber is a boost::tss, because it supports automatic cleanup when
 73// the thread exits (and datatypes larger than pointer size), while
 74// ThreadLocalStorage does not
 75// t_fiber is a ThreadLocalStorage, because it's faster than boost::tss
 76ThreadLocalStorage<Fiber *> Fiber::t_fiber;
 77static boost::thread_specific_ptr<Fiber::ptr> t_threadFiber;
 78
 79static boost::mutex & g_flsMutex()
 80{
 81    static boost::mutex mutex;
 82    return mutex;
 83}
 84static std::vector<bool> & g_flsIndices()
 85{
 86    static std::vector<bool> indices;
 87    return indices;
 88}
 89
 90Fiber::Fiber()
 91{
 92    g_statMaxFibers.update(atomicIncrement(g_cntFibers));
 93    MORDOR_ASSERT(!t_fiber);
 94    m_state = EXEC;
 95    m_stack = NULL;
 96    m_stacksize = 0;
 97    m_sp = NULL;
 98    setThis(this);
 99#ifdef NATIVE_WINDOWS_FIBERS
100    if (!pIsThreadAFiber())
101        m_stack = ConvertThreadToFiber(NULL);
102    m_sp = GetCurrentFiber();
103#elif defined(UCONTEXT_FIBERS)
104    m_sp = &m_ctx;
105#elif defined(SETJMP_FIBERS)
106    m_sp = &m_env;
107#endif
108}
109
110Fiber::Fiber(boost::function<void ()> dg, size_t stacksize)
111{
112    g_statMaxFibers.update(atomicIncrement(g_cntFibers));
113    stacksize += g_pagesize - 1;
114    stacksize -= stacksize % g_pagesize;
115    m_dg = dg;
116    m_state = INIT;
117    m_stack = NULL;
118    m_stacksize = stacksize;
119    allocStack();
120#ifdef UCONTEXT_FIBERS
121    m_sp = &m_ctx;
122#elif defined(SETJMP_FIBERS)
123    m_sp = &m_env;
124#endif
125    initStack();
126}
127
128Fiber::~Fiber()
129{
130    atomicDecrement(g_cntFibers);
131    if (!m_stack || m_stack == m_sp) {
132        // Thread entry fiber
133        MORDOR_NOTHROW_ASSERT(!m_dg);
134        MORDOR_NOTHROW_ASSERT(m_state == EXEC);
135        Fiber *cur = t_fiber.get();
136
137        // We're actually running on the fiber we're about to delete
138        // i.e. the thread is dying, so clean up after ourselves
139        if (cur == this)  {
140            setThis(NULL);
141#ifdef NATIVE_WINDOWS_FIBERS
142            if (m_stack) {
143                MORDOR_NOTHROW_ASSERT(m_stack == m_sp);
144                MORDOR_NOTHROW_ASSERT(m_stack == GetCurrentFiber());
145                pConvertFiberToThread();
146            }
147#endif
148        }
149        // Otherwise, there's not a thread left to clean up
150    } else {
151        // Regular fiber
152        MORDOR_NOTHROW_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
153        freeStack();
154    }
155}
156
157void
158Fiber::reset(boost::function<void ()> dg)
159{
160    m_exception = boost::exception_ptr();
161    MORDOR_ASSERT(m_stack);
162    MORDOR_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
163    m_dg = dg;
164    initStack();
165    m_state = INIT;
166}
167
168Fiber::ptr
169Fiber::getThis()
170{
171    if (t_fiber)
172        return t_fiber->shared_from_this();
173    Fiber::ptr threadFiber(new Fiber());
174    MORDOR_ASSERT(t_fiber.get() == threadFiber.get());
175    t_threadFiber.reset(new Fiber::ptr(threadFiber));
176    return t_fiber->shared_from_this();
177}
178
179void
180Fiber::setThis(Fiber* f)
181{
182    t_fiber = f;
183}
184
185void
186Fiber::call()
187{
188    MORDOR_ASSERT(!m_outer);
189    ptr cur = getThis();
190    MORDOR_ASSERT(m_state == HOLD || m_state == INIT);
191    MORDOR_ASSERT(cur);
192    MORDOR_ASSERT(cur.get() != this);
193    setThis(this);
194    m_outer = cur;
195    m_state = m_exception ? EXCEPT : EXEC;
196    cur->switchContext(this);
197    setThis(cur.get());
198    MORDOR_ASSERT(cur->m_yielder);
199    m_outer.reset();
200    if (cur->m_yielder) {
201        MORDOR_ASSERT(cur->m_yielder.get() == this);
202        Fiber::ptr yielder = cur->m_yielder;
203        yielder->m_state = cur->m_yielderNextState;
204        cur->m_yielder.reset();
205        if (yielder->m_state == EXCEPT && yielder->m_exception)
206            Mordor::rethrow_exception(yielder->m_exception);
207    }
208    MORDOR_ASSERT(cur->m_state == EXEC);
209}
210
211void
212Fiber::inject(boost::exception_ptr exception)
213{
214    MORDOR_ASSERT(exception);
215    m_exception = exception;
216    call();
217}
218
219Fiber::ptr
220Fiber::yieldTo(bool yieldToCallerOnTerminate)
221{
222    return yieldTo(yieldToCallerOnTerminate, HOLD);
223}
224
225void
226Fiber::yield()
227{
228    ptr cur = getThis();
229    MORDOR_ASSERT(cur);
230    MORDOR_ASSERT(cur->m_state == EXEC);
231    MORDOR_ASSERT(cur->m_outer);
232    cur->m_outer->m_yielder = cur;
233    cur->m_outer->m_yielderNextState = Fiber::HOLD;
234    cur->switchContext(cur->m_outer.get());
235    if (cur->m_yielder) {
236        cur->m_yielder->m_state = cur->m_yielderNextState;
237        cur->m_yielder.reset();
238    }
239    if (cur->m_state == EXCEPT) {
240        MORDOR_ASSERT(cur->m_exception);
241        Mordor::rethrow_exception(cur->m_exception);
242    }
243    MORDOR_ASSERT(cur->m_state == EXEC);
244}
245
246Fiber::State
247Fiber::state()
248{
249    return m_state;
250}
251
252Fiber::ptr
253Fiber::yieldTo(bool yieldToCallerOnTerminate, State targetState)
254{
255    MORDOR_ASSERT(m_state == HOLD || m_state == INIT);
256    MORDOR_ASSERT(targetState == HOLD || targetState == TERM || targetState == EXCEPT);
257    ptr cur = getThis();
258    MORDOR_ASSERT(cur);
259    setThis(this);
260    if (yieldToCallerOnTerminate) {
261        Fiber::ptr outer = shared_from_this();
262        Fiber::ptr previous;
263        while (outer) {
264            previous = outer;
265            outer = outer->m_outer;
266        }
267        previous->m_terminateOuter = cur;
268    }
269    m_state = EXEC;
270    m_yielder = cur;
271    m_yielderNextState = targetState;
272    Fiber *curp = cur.get();
273    // Relinguish our reference
274    cur.reset();
275    curp->switchContext(this);
276#ifdef NATIVE_WINDOWS_FIBERS
277    if (targetState == TERM)
278        return Fiber::ptr();
279#endif
280    MORDOR_ASSERT(targetState != TERM);
281    setThis(curp);
282    if (curp->m_yielder) {
283        Fiber::ptr yielder = curp->m_yielder;
284        yielder->m_state = curp->m_yielderNextState;
285        curp->m_yielder.reset();
286        if (yielder->m_exception)
287            Mordor::rethrow_exception(yielder->m_exception);
288        return yielder;
289    }
290    if (curp->m_state == EXCEPT) {
291        MORDOR_ASSERT(curp->m_exception);
292        Mordor::rethrow_exception(curp->m_exception);
293    }
294    MORDOR_ASSERT(curp->m_state == EXEC);
295    return Fiber::ptr();
296}
297
298void
299Fiber::entryPoint()
300{
301    // This function never returns, so take care that smart pointers (or other resources)
302    // are properly released.
303    ptr cur = getThis();
304    MORDOR_ASSERT(cur);
305    if (cur->m_yielder) {
306        cur->m_yielder->m_state = cur->m_yielderNextState;
307        cur->m_yielder.reset();
308    }
309    MORDOR_ASSERT(cur->m_dg);
310    State nextState = TERM;
311    try {
312        if (cur->m_state == EXCEPT) {
313            MORDOR_ASSERT(cur->m_exception);
314            Mordor::rethrow_exception(cur->m_exception);
315        }
316        MORDOR_ASSERT(cur->m_state == EXEC);
317        cur->m_dg();
318        cur->m_dg = NULL;
319    } catch (boost::exception &ex) {
320        removeTopFrames(ex);
321        cur->m_exception = boost::current_exception();
322        nextState = EXCEPT;
323    } catch (...) {
324        cur->m_exception = boost::current_exception();
325        nextState = EXCEPT;
326    }
327
328    exitPoint(cur, nextState);
329#ifndef NATIVE_WINDOWS_FIBERS
330    MORDOR_NOTREACHED();
331#endif
332}
333
334void
335Fiber::exitPoint(Fiber::ptr &cur, State targetState)
336{
337    // This function never returns, so take care that smart pointers (or other resources)
338    // are properly released.
339    Fiber::ptr outer;
340    Fiber *rawPtr = NULL;
341    if (!cur->m_terminateOuter.expired() && !cur->m_outer) {
342        outer = cur->m_terminateOuter.lock();
343        rawPtr = outer.get();
344    } else {
345        outer = cur->m_outer;
346        rawPtr = cur.get();
347    }
348    MORDOR_ASSERT(outer);
349    MORDOR_ASSERT(rawPtr);
350    MORDOR_ASSERT(outer != cur);
351
352    // Have to set this reference before calling yieldTo()
353    // so we can reset cur before we call yieldTo()
354    // (since it's not ever going to destruct)
355    outer->m_yielder = cur;
356    outer->m_yielderNextState = targetState;
357    MORDOR_ASSERT(!cur.unique());
358    cur.reset();
359    if (rawPtr == outer.get()) {
360        rawPtr = outer.get();
361        MORDOR_ASSERT(!outer.unique());
362        outer.reset();
363        rawPtr->yieldTo(false, targetState);
364    } else {
365        outer.reset();
366        rawPtr->switchContext(rawPtr->m_outer.get());
367    }
368}
369
370#ifdef NATIVE_WINDOWS_FIBERS
371static VOID CALLBACK native_fiber_entryPoint(PVOID lpParameter)
372{
373    void (*entryPoint)() = (void (*)())lpParameter;
374    while (true) {
375        entryPoint();
376    }
377}
378#endif
379
380void
381Fiber::allocStack()
382{
383    if (m_stacksize == 0)
384        m_stacksize = g_defaultStackSize->val();
385#ifndef NATIVE_WINDOWS_FIBERS
386    TimeStatistic<AverageMinMaxStatistic<unsigned int> > time(g_statAlloc);
387#endif
388#ifdef NATIVE_WINDOWS_FIBERS
389    // Fibers are allocated in initStack
390#elif defined(WINDOWS)
391    m_stack = VirtualAlloc(NULL, m_stacksize + g_pagesize, MEM_RESERVE, PAGE_NOACCESS);
392    if (!m_stack)
393        MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("VirtualAlloc");
394    VirtualAlloc(m_stack, g_pagesize, MEM_COMMIT, PAGE_READWRITE | PAGE_GUARD);
395    // TODO: don't commit until referenced
396    VirtualAlloc((char*)m_stack + g_pagesize, m_stacksize, MEM_COMMIT, PAGE_READWRITE);
397    m_sp = (char*)m_stack + m_stacksize + g_pagesize;
398#elif defined(POSIX)
399    m_stack = mmap(NULL, m_stacksize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
400    if (m_stack == MAP_FAILED)
401        MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("mmap");
402#if defined(VALGRIND) && (defined(LINUX) || defined(OSX))
403    m_valgrindStackId = VALGRIND_STACK_REGISTER(m_stack, (char *)m_stack + m_stacksize);
404#endif
405    m_sp = (char*)m_stack + m_stacksize;
406#endif
407}
408
409void
410Fiber::freeStack()
411{
412    TimeStatistic<AverageMinMaxStatistic<unsigned int> > time(g_statFree);
413#ifdef NATIVE_WINDOWS_FIBERS
414    MORDOR_ASSERT(m_stack == &m_sp);
415    DeleteFiber(m_sp);
416#elif defined(WINDOWS)
417    VirtualFree(m_stack, 0, MEM_RELEASE);
418#elif defined(POSIX)
419#if defined(VALGRIND) && (defined(LINUX) || defined(OSX))
420    VALGRIND_STACK_DEREGISTER(m_valgrindStackId);
421#endif
422    munmap(m_stack, m_stacksize);
423#endif
424}
425
426void
427Fiber::switchContext(Fiber *to)
428{
429#ifdef NATIVE_WINDOWS_FIBERS
430    SwitchToFiber(to->m_sp);
431
432#elif defined(UCONTEXT_FIBERS)
433#  if defined(CXXABIV1_EXCEPTION)
434    this->m_eh.swap(to->m_eh);
435#  endif
436    if (swapcontext((ucontext_t*)(this->m_sp), (ucontext_t*)to->m_sp))
437        MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("swapcontext");
438
439#elif defined(SETJMP_FIBERS)
440    if (!setjmp(*(jmp_buf*)this->m_sp)) {
441#  if defined(CXXABIV1_EXCEPTION)
442        this->m_eh.swap(to->m_eh);
443#  endif
444        longjmp(*(jmp_buf*)to->m_sp, 1);
445    }
446#endif
447}
448
449void
450Fiber::initStack()
451{
452#ifdef NATIVE_WINDOWS_FIBERS
453    if (m_stack)
454        return;
455    TimeStatistic<AverageMinMaxStatistic<unsigned int> > stat(g_statAlloc);
456    m_sp = m_stack = pCreateFiberEx(0, m_stacksize, 0, &native_fiber_entryPoint, &Fiber::entryPoint);
457    stat.finish();
458    if (!m_stack)
459        MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("CreateFiber");
460    // This is so we can distinguish from a created fiber vs. the "root" fiber
461    m_stack = &m_sp;
462#elif defined(UCONTEXT_FIBERS)
463    if (getcontext(&m_ctx))
464        MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("getcontext");
465    m_ctx.uc_link = NULL;
466    m_ctx.uc_stack.ss_sp = m_stack;
467    m_ctx.uc_stack.ss_size = m_stacksize;
468#ifdef OSX
469    m_ctx.uc_mcsize = sizeof(m_mctx);
470    m_ctx.uc_mcontext = (mcontext_t)m_mctx;
471#endif
472    makecontext(&m_ctx, &Fiber::entryPoint, 0);
473#elif defined(SETJMP_FIBERS)
474    if (setjmp(m_env)) {
475        Fiber::entryPoint();
476        MORDOR_NOTREACHED();
477    }
478#ifdef OSX
479#ifdef X86
480    m_env[9] = (int)m_stack + m_stacksize; // ESP
481#if defined(__GNUC__) && defined(__llvm__)
482    // see following `rbp' note for the reason of setting ebp to esp
483    m_env[8] = m_env[9]; // EBP
484#else
485    m_env[8] = 0xffffffff; // EBP
486#endif
487#elif defined(X86_64)
488    long long *env = (long long *)m_env;
489    env[2] = (long long)m_stack + m_stacksize; // RSP
490#if defined(__GNUC__) && defined(__llvm__)
491    // NOTE: `rbp' register should be cleaned because after setjmp() returns 0,
492    // this initStack() call finished, the call frame will be poped, so when
493    // setjmp() returns the second time (by longjmp), the original `rbp'
494    // register can't be used anymore as its address is invalid now.  However,
495    // with -O0 gcc+llvm compiling, there are still additional assembly
496    // instructions that refers rbp to perform writting operation, this will
497    // cause segmentation fault if `rbp' is cleared to 0 here. To workaround
498    // the issue, set `rbp' to Fiber's own stack pointer, writting junk data
499    // to Fiber's own empty stack doesn't hurt anything.
500    // This issue only happens when compiling with gcc + llvm + `-O0'
501    env[1] = env[2]; // RBP
502#else
503    env[1] = 0x0LL; // RBP
504#endif
505#elif defined(PPC)
506    m_env[0] = (int)m_stack;
507#else
508#error Architecture not supported
509#endif
510#elif defined (LINUX)
511#ifdef ARM
512    int *env = (int *)m_env;
513    env[8] = (int)m_stack + m_stacksize;
514#else
515#error Platform not supported
516#endif
517#else
518#error Platform not supported
519#endif
520#endif
521}
522
523#ifdef WINDOWS
524static bool g_doesntHaveOSFLS;
525#endif
526
527size_t
528Fiber::flsAlloc()
529{
530#ifdef WINDOWS
531    while (!g_doesntHaveOSFLS) {
532        size_t result = pFlsAlloc(NULL);
533        if (result == FLS_OUT_OF_INDEXES && lastError() == ERROR_CALL_NOT_IMPLEMENTED) {
534            g_doesntHaveOSFLS = true;
535            break;
536        }
537        if (result == FLS_OUT_OF_INDEXES)
538            MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("FlsAlloc");
539        return result;
540    }
541#endif
542    boost::mutex::scoped_lock lock(g_flsMutex());
543    std::vector<bool>::iterator it = std::find(g_flsIndices().begin(),
544        g_flsIndices().end(), false);
545    // TODO: we don't clear out values when freeing, so we can't reuse
546    // force new
547    it = g_flsIndices().end();
548    if (it == g_flsIndices().end()) {
549        g_flsIndices().resize(g_flsIndices().size() + 1);
550        g_flsIndices()[g_flsIndices().size() - 1] = true;
551        return g_flsIndices().size() - 1;
552    } else {
553        size_t result = it - g_flsIndices().begin();
554        g_flsIndices()[result] = true;
555        return result;
556    }
557}
558
559void
560Fiber::flsFree(size_t key)
561{
562#ifdef WINDOWS
563    if (!g_doesntHaveOSFLS) {
564        if (!pFlsFree((DWORD)key))
565            MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("FlsFree");
566        return;
567    }
568#endif
569    boost::mutex::scoped_lock lock(g_flsMutex());
570    MORDOR_ASSERT(key < g_flsIndices().size());
571    MORDOR_ASSERT(g_flsIndices()[key]);
572    if (key + 1 == g_flsIndices().size()) {
573        g_flsIndices().resize(key);
574    } else {
575        // TODO: clear out current values
576        g_flsIndices()[key] = false;
577    }
578}
579
580void
581Fiber::flsSet(size_t key, intptr_t value)
582{
583#ifdef WINDOWS
584    if (!g_doesntHaveOSFLS) {
585        if (!pFlsSetValue((DWORD)key, (PVOID)value))
586            MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("FlsSetValue");
587        return;
588    }
589#endif
590    Fiber::ptr self = Fiber::getThis();
591    if (self->m_fls.size() <= key)
592        self->m_fls.resize(key + 1);
593    self->m_fls[key] = value;
594}
595
596intptr_t
597Fiber::flsGet(size_t key)
598{
599#ifdef WINDOWS
600    if (!g_doesntHaveOSFLS) {
601        error_t error = lastError();
602        intptr_t result = (intptr_t)pFlsGetValue((DWORD)key);
603        lastError(error);
604        return result;
605    }
606#endif
607    Fiber::ptr self = Fiber::getThis();
608    if (self->m_fls.size() <= key)
609        return 0;
610    return self->m_fls[key];
611}
612
613std::vector<void *>
614Fiber::backtrace()
615{
616    MORDOR_ASSERT(m_state != EXEC);
617    std::vector<void *> result;
618    if (m_state != HOLD)
619        return result;
620#ifdef WINDOWS
621    STACKFRAME64 frame;
622    DWORD type;
623    CONTEXT *context;
624#ifdef _M_IX86
625    context = (CONTEXT *)((char *)m_sp + 0x14);
626    type                   = IMAGE_FILE_MACHINE_I386;
627    frame.AddrPC.Offset    = context->Eip;
628    frame.AddrPC.Mode      = AddrModeFlat;
629    frame.AddrFrame.Offset = context->Ebp;
630    frame.AddrFrame.Mode   = AddrModeFlat;
631    frame.AddrStack.Offset = context->Esp;
632    frame.AddrStack.Mode   = AddrModeFlat;
633    context = NULL;
634#elif _M_X64
635    context = (CONTEXT *)((char *)m_sp + 0x30);
636    CONTEXT dupContext;
637    memcpy(&dupContext, context, sizeof(CONTEXT));
638    context = &dupContext;
639    type                   = IMAGE_FILE_MACHINE_AMD64;
640    frame.AddrPC.Offset    = dupContext.Rip;
641    frame.AddrPC.Mode      = AddrModeFlat;
642    frame.AddrFrame.Offset = dupContext.Rsp;
643    frame.AddrFrame.Mode   = AddrModeFlat;
644    frame.AddrStack.Offset = dupContext.Rsp;
645    frame.AddrStack.Mode   = AddrModeFlat;
646#else
647#error "Unsupported platform"
648#endif
649
650    while (result.size() < 64) {
651        if (!StackWalk64(type, GetCurrentProcess(), GetCurrentThread(),
652            &frame, context, NULL, &SymFunctionTableAccess64,
653            &SymGetModuleBase64, NULL)) {
654            error_t error = lastError();
655            break;
656        }
657        if (frame.AddrPC.Offset != 0) {
658            result.push_back((void *)frame.AddrPC.Offset);
659        }
660    }
661#endif
662    return result;
663}
664
665std::ostream &operator<<(std::ostream &os, Fiber::State state)
666{
667    switch (state) {
668        case Fiber::INIT:
669            return os << "INIT";
670        case Fiber::HOLD:
671            return os << "HOLD";
672        case Fiber::EXEC:
673            return os << "EXEC";
674        case Fiber::EXCEPT:
675            return os << "EXCEPT";
676        case Fiber::TERM:
677            return os << "TERM";
678        default:
679            return os << (int)state;
680    }
681}
682
683}