Rewrote CoroX. Simplified APIs. Improved stack size checks. default tip
authorJens Alfke <jens@mooseyard.com>
Wed Apr 30 14:18:49 2008 -0700 (2008-04-30)
changeset 12475f871c218
parent 0 deb0ee0c5b21
Rewrote CoroX. Simplified APIs. Improved stack size checks.
Actors.xcodeproj/project.pbxproj
Coroutines/CoroX.c
Coroutines/CoroX.h
Coroutines/MYCoroutine.h
Coroutines/MYCoroutine.m
Coroutines/MYCoroutineTest.m
     1.1 --- a/Actors.xcodeproj/project.pbxproj	Tue Apr 29 17:05:32 2008 -0700
     1.2 +++ b/Actors.xcodeproj/project.pbxproj	Wed Apr 30 14:18:49 2008 -0700
     1.3 @@ -86,9 +86,9 @@
     1.4  			children = (
     1.5  				2703CFE20DC796DC00DD026B /* MYCoroutine.h */,
     1.6  				2703CFE30DC796DC00DD026B /* MYCoroutine.m */,
     1.7 -				2703D28A0DC7EC5A00DD026B /* MYCoroutineTest.m */,
     1.8  				2703D1BC0DC7DAD100DD026B /* CoroX.h */,
     1.9  				2703D1BD0DC7DAD100DD026B /* CoroX.c */,
    1.10 +				2703D28A0DC7EC5A00DD026B /* MYCoroutineTest.m */,
    1.11  			);
    1.12  			path = Coroutines;
    1.13  			sourceTree = "<group>";
     2.1 --- a/Coroutines/CoroX.c	Tue Apr 29 17:05:32 2008 -0700
     2.2 +++ b/Coroutines/CoroX.c	Wed Apr 30 14:18:49 2008 -0700
     2.3 @@ -5,9 +5,6 @@
     2.4   *  Created by Jens Alfke on 4/29/08.
     2.5   *  Adapted from Steve Dekorte's libCoroutine:
     2.6   *  <http://www.dekorte.com/projects/opensource/libCoroutine/>
     2.7 - *  by putting it on a piece of wood and banging a few nails through it.
     2.8 - *  No, actually I removed all the stuff for cross-platform support, leaving only the simple
     2.9 - *  code that works on Mac OS X 10.5, and then cleaned things up a bit.
    2.10   *
    2.11   *  Copyright 2008 Jens Alfke. All rights reserved.
    2.12   *  Copyright (c) 2002, 2003 Steve Dekorte. All rights reserved.
    2.13 @@ -15,142 +12,142 @@
    2.14   *
    2.15   */
    2.16  
    2.17 +#define _XOPEN_SOURCE   /* decl of ucontext_t in <sys/_structs.h> is wrong unless this is defined */
    2.18 +
    2.19  #include "CoroX.h"
    2.20 +#include <stddef.h>
    2.21 +#include <assert.h>
    2.22 +#include <pthread.h>
    2.23  #include <ucontext.h>
    2.24 -#include <stddef.h>
    2.25 -#include <stdio.h>
    2.26 -#include <assert.h>
    2.27  
    2.28 +// Experimentally, the coroutine bus-errors when its stack space drops below about 10k,
    2.29 +// implying that the kernel unmaps the very end of it. So allow extra room:
    2.30 +#define STACK_OVERHEAD (12*1024)
    2.31  
    2.32 -#define CORO_DEFAULT_STACK_SIZE (65536*4)
    2.33 -#define CORO_STACK_SIZE_MIN     8192
    2.34 +// Even with that overhead, stack sizes less than this cause an immediate crash:
    2.35 +const size_t kCoroX_minStackSize = 20*1024;
    2.36  
    2.37  
    2.38 -struct Coro
    2.39 +struct CoroX
    2.40  {        
    2.41      size_t stackSize;
    2.42      void *stack;
    2.43 +    CoroEntryPoint entryPoint;
    2.44 +    void *userData;
    2.45      ucontext_t env;
    2.46 -    // The following field works around bug(?) in sys/_structs.h. This field should be part of ucontext_t:
    2.47 -    _STRUCT_MCONTEXT	env_registers; 
    2.48 -    unsigned char isMain;
    2.49  };
    2.50  
    2.51  
    2.52 -typedef struct CallbackBlock
    2.53 +CoroX *CoroX_new(CoroEntryPoint entryPoint, void *userData)
    2.54  {
    2.55 -    void *context;
    2.56 -    CoroStartCallback *func;
    2.57 -} CallbackBlock;
    2.58 -
    2.59 -
    2.60 -Coro *Coro_new(void)
    2.61 -{
    2.62 -    Coro *self = (Coro *)calloc(1, sizeof(Coro));
    2.63 -    self->stackSize = CORO_DEFAULT_STACK_SIZE;
    2.64 -    self->stack = NULL;
    2.65 +    CoroX *self = (CoroX *)calloc(1, sizeof(CoroX));
    2.66 +    self->stackSize = CoroX_getDefaultStackSize();
    2.67 +    self->entryPoint = entryPoint;
    2.68 +    self->userData = userData;
    2.69      return self;
    2.70  }
    2.71  
    2.72 -void Coro_free(Coro *self)
    2.73 +void CoroX_free(CoroX *self)
    2.74  {
    2.75 -    if (self->stack)
    2.76 -        free(self->stack);
    2.77 -    free(self);
    2.78 +    if( self ) {
    2.79 +        if (self->stack)
    2.80 +            free(self->stack);
    2.81 +        free(self);
    2.82 +    }
    2.83  }
    2.84  
    2.85 +void*  CoroX_userData(CoroX *self)                            {return self->userData;}
    2.86 +bool   CoroX_isMain(CoroX *self)                              {return self->entryPoint==NULL;}
    2.87  
    2.88 -void *Coro_stack(Coro *self)
    2.89 +
    2.90 +#pragma mark STACK:
    2.91 +
    2.92 +
    2.93 +void*  CoroX_stack(CoroX *self)                               {return self->stack;}
    2.94 +size_t CoroX_stackSize(CoroX *self)                           {return self->stackSize;}
    2.95 +
    2.96 +void   CoroX_setStackSize_(CoroX *self, size_t sizeInBytes)   
    2.97  {
    2.98 -    return self->stack;
    2.99 -}
   2.100 -
   2.101 -size_t Coro_stackSize(Coro *self)
   2.102 -{
   2.103 -    return self->stackSize;
   2.104 -}
   2.105 -
   2.106 -void Coro_setStackSize_(Coro *self, size_t sizeInBytes)
   2.107 -{
   2.108 +    if( sizeInBytes < kCoroX_minStackSize )
   2.109 +        sizeInBytes = kCoroX_minStackSize;
   2.110      self->stackSize = sizeInBytes;
   2.111  }
   2.112  
   2.113 -uint8_t *Coro_CurrentStackPointer(void) __attribute__ ((noinline));
   2.114  
   2.115 -uint8_t *Coro_CurrentStackPointer(void)
   2.116 +static size_t sDefaultStackSize;
   2.117 +
   2.118 +size_t CoroX_getDefaultStackSize(void)
   2.119 +{
   2.120 +    if( sDefaultStackSize == 0 ) {
   2.121 +        pthread_attr_t pthreadAttrs;        // Find out default pthread stack size
   2.122 +        int err = pthread_attr_init(&pthreadAttrs);
   2.123 +        if( ! err )
   2.124 +            err = pthread_attr_getstacksize(&pthreadAttrs, &sDefaultStackSize);
   2.125 +        if (err)
   2.126 +            sDefaultStackSize = 512*1024;
   2.127 +    }
   2.128 +    return sDefaultStackSize;
   2.129 +}
   2.130 +
   2.131 +void CoroX_setDefaultStackSize( size_t size )
   2.132 +{
   2.133 +    sDefaultStackSize = size;
   2.134 +}
   2.135 +
   2.136 +
   2.137 + __attribute__ ((noinline))
   2.138 +static uint8_t *CoroX_CurrentStackPointer(void)
   2.139  {
   2.140      uint8_t a;
   2.141      uint8_t *b = &a;
   2.142      return b;
   2.143  }
   2.144  
   2.145 -size_t Coro_bytesLeftOnStack(Coro *self)
   2.146 +size_t CoroX_bytesLeftOnStack(CoroX *self)
   2.147  {
   2.148      uint8_t dummy;
   2.149      ptrdiff_t p1 = (ptrdiff_t)(&dummy);
   2.150 -    ptrdiff_t p2 = (ptrdiff_t)Coro_CurrentStackPointer();
   2.151 +    ptrdiff_t p2 = (ptrdiff_t)CoroX_CurrentStackPointer();
   2.152      int stackMovesUp = p2 > p1;
   2.153 -    ptrdiff_t start = ((ptrdiff_t)self->stack);
   2.154 +    ptrdiff_t start = ((ptrdiff_t)self->stack) ;
   2.155      ptrdiff_t end = start + self->stackSize;
   2.156      
   2.157      if (stackMovesUp)
   2.158 -    {
   2.159 -        return end - p1;
   2.160 -    }
   2.161 +        return end - p1 - STACK_OVERHEAD;
   2.162      else
   2.163 -    {
   2.164 -        return p1 - start;
   2.165 -    }
   2.166 +        return p1 - start - STACK_OVERHEAD;
   2.167  }
   2.168  
   2.169 -int Coro_stackSpaceAlmostGone(Coro *self)
   2.170 +int CoroX_stackSpaceAlmostGone(CoroX *self)
   2.171  {
   2.172 -    return Coro_bytesLeftOnStack(self) < CORO_STACK_SIZE_MIN;
   2.173 +    return CoroX_bytesLeftOnStack(self) < 2048;
   2.174  }
   2.175  
   2.176  
   2.177 -void Coro_initializeMainCoro(Coro *self)
   2.178 -{
   2.179 -    self->isMain = 1;
   2.180 -}
   2.181 +#pragma mark SETUP & SWITCH:
   2.182  
   2.183  
   2.184 -static void Coro_StartWithArg(CallbackBlock *block)
   2.185 -{
   2.186 -    (block->func)(block->context);
   2.187 -    fprintf(stderr,"CoroX error: returned from coro start function\n");
   2.188 -    abort();
   2.189 -}
   2.190 -
   2.191  typedef void (*makecontext_func)(void);
   2.192  
   2.193 -static void Coro_setup(Coro *self, void *arg)
   2.194 +static void CoroX_setup_(CoroX *self)
   2.195  {
   2.196 -    ucontext_t *ucp = (ucontext_t *) &self->env;
   2.197 -    getcontext(ucp);
   2.198 +    size_t stackSize = self->stackSize + STACK_OVERHEAD;
   2.199 +    self->stack = valloc(stackSize);
   2.200 +    assert(self->stack);
   2.201      
   2.202 -    ucp->uc_stack.ss_sp = Coro_stack(self);
   2.203 -    ucp->uc_stack.ss_size = Coro_stackSize(self);
   2.204 -    ucp->uc_stack.ss_flags = 0;
   2.205 -    ucp->uc_link = NULL;
   2.206 -    
   2.207 -    makecontext(ucp, (makecontext_func)Coro_StartWithArg, 1, arg);
   2.208 +    getcontext(&self->env);
   2.209 +    self->env.uc_stack.ss_sp = self->stack;
   2.210 +    self->env.uc_stack.ss_size = stackSize;
   2.211 +    self->env.uc_stack.ss_flags = 0;
   2.212 +    self->env.uc_link = NULL;
   2.213 +    makecontext(&self->env, (makecontext_func)self->entryPoint, 1, self);
   2.214  }
   2.215  
   2.216  
   2.217 -void Coro_startCoro_(Coro *self, Coro *other, void *context, CoroStartCallback *callback)
   2.218 +void CoroX_switchTo_(CoroX *self, CoroX *next)
   2.219  {
   2.220 -    assert(other->stack==NULL);
   2.221 -    other->stack = calloc(1, other->stackSize + 16);
   2.222 -
   2.223 -    CallbackBlock callbackBlock = {context,callback};
   2.224 -    Coro_setup(other, &callbackBlock);
   2.225 -    
   2.226 -    Coro_switchTo_(self, other);
   2.227 -}
   2.228 -
   2.229 -void Coro_switchTo_(Coro *self, Coro *next)
   2.230 -{
   2.231 +    if( ! next->stack && next->entryPoint )
   2.232 +        CoroX_setup_(next);
   2.233      swapcontext(&self->env, &next->env);
   2.234  }
   2.235  
     3.1 --- a/Coroutines/CoroX.h	Tue Apr 29 17:05:32 2008 -0700
     3.2 +++ b/Coroutines/CoroX.h	Wed Apr 30 14:18:49 2008 -0700
     3.3 @@ -5,9 +5,6 @@
     3.4   *  Created by Jens Alfke on 4/29/08.
     3.5   *  Adapted from Steve Dekorte's libCoroutine:
     3.6   *  <http://www.dekorte.com/projects/opensource/libCoroutine/>
     3.7 - *  by putting it on a piece of wood and banging a few nails through it.
     3.8 - *  No, actually I removed all the stuff for cross-platform support, leaving only the simple
     3.9 - *  code that works on Mac OS X 10.5, and then cleaned things up a bit.
    3.10   *
    3.11   *  Copyright 2008 Jens Alfke. All rights reserved.
    3.12   *  Copyright (c) 2002, 2003 Steve Dekorte. All rights reserved.
    3.13 @@ -18,6 +15,8 @@
    3.14  #pragma once
    3.15  #include <stdlib.h>
    3.16  #include <stdint.h>
    3.17 +#include <stdbool.h>
    3.18 +
    3.19  
    3.20  /** C coroutine implementation for Mac OS X.
    3.21      Based on, and API-compatible with, Steve Dekorte's libCoroutine.
    3.22 @@ -28,30 +27,30 @@
    3.23  extern "C" {
    3.24  #endif
    3.25      
    3.26 -    typedef struct Coro Coro;
    3.27 +    typedef struct CoroX CoroX;
    3.28      
    3.29 -    Coro *Coro_new(void);
    3.30 -    void Coro_free(Coro *self);
    3.31 +    typedef void (*CoroEntryPoint)(CoroX*);
    3.32 +        
    3.33 +    CoroX*  CoroX_new(CoroEntryPoint, void *userData);      // use entryPoint==NULL to init main Coro
    3.34 +    void    CoroX_free(CoroX*);
    3.35      
    3.36 -    // stack
    3.37 +    void    CoroX_switchTo_(CoroX *current, CoroX *next);
    3.38      
    3.39 -    void *Coro_stack(Coro *self);
    3.40 -    size_t Coro_stackSize(Coro *self);
    3.41 -    void Coro_setStackSize_(Coro *self, size_t sizeInBytes);
    3.42 -    size_t Coro_bytesLeftOnStack(Coro *self);
    3.43 -    int Coro_stackSpaceAlmostGone(Coro *self);
    3.44 +    void*   CoroX_userData(CoroX*);
    3.45 +    bool    CoroX_isMain(CoroX*);
    3.46      
    3.47 -    // initialization
    3.48 +    // Stack:
    3.49 +
    3.50 +    extern const size_t kCoroX_minStackSize;                // Minimum useable stack size    
    3.51      
    3.52 -    void Coro_initializeMainCoro(Coro *self);
    3.53 -    
    3.54 -    typedef void (CoroStartCallback)(void *);
    3.55 -    
    3.56 -    void Coro_startCoro_(Coro *self, Coro *other, void *context, CoroStartCallback *callback);
    3.57 -    
    3.58 -    // context-switch
    3.59 -    
    3.60 -    void Coro_switchTo_(Coro *self, Coro *next);
    3.61 +    size_t  CoroX_getDefaultStackSize(void);
    3.62 +    void    CoroX_setDefaultStackSize(size_t);
    3.63 +
    3.64 +    void*   CoroX_stack(CoroX*);
    3.65 +    size_t  CoroX_stackSize(CoroX*);
    3.66 +    void    CoroX_setStackSize_(CoroX*, size_t);
    3.67 +    size_t  CoroX_bytesLeftOnStack(CoroX*);
    3.68 +    int     CoroX_stackSpaceAlmostGone(CoroX*);
    3.69      
    3.70  #ifdef __cplusplus
    3.71  }
     4.1 --- a/Coroutines/MYCoroutine.h	Tue Apr 29 17:05:32 2008 -0700
     4.2 +++ b/Coroutines/MYCoroutine.h	Wed Apr 30 14:18:49 2008 -0700
     4.3 @@ -15,7 +15,8 @@
     4.4  @interface MYCoroutine : NSObject
     4.5  {
     4.6      @private
     4.7 -    struct Coro *_coro;
     4.8 +    struct CoroX *_coro;
     4.9 +    NSInvocation *_invocation;
    4.10      NSString *_name;
    4.11  }
    4.12  
    4.13 @@ -33,42 +34,42 @@
    4.14  /** Creates but does not start a coroutine. */
    4.15  - (id) init;
    4.16  
    4.17 -/** Starts a new coroutine, and performs the given invocation on it.
    4.18 -    The current coroutine will block (i.e. this call won't return)
    4.19 -    until some other coroutine tells it to resume. */
    4.20 -- (void) startWithInvocation: (NSInvocation*)invocation;
    4.21  
    4.22 -/** Starts a new coroutine, invoking its -main method as its body.
    4.23 -    Since the default implementation of -main is empty, this only makes sense to call
    4.24 -    on an instance of a subclass of MYCoroutine that's overridden -main.
    4.25 -    The current coroutine will block (i.e. this call won't return)
    4.26 -    until some other coroutine tells it to resume. */
    4.27 -- (void) start;
    4.28 +@property (retain) NSInvocation *invocation;
    4.29 +
    4.30 +/** The stack size of the coroutine. You can only change this before calling -start! */
    4.31 +@property size_t stackSize;
    4.32 +
    4.33 +/** The coroutine's name. You can put anything you want here, as a debugging aid. */
    4.34 +@property (copy) NSString* name;
    4.35 +
    4.36  
    4.37  /** The "main" method that will be called if the coroutine is started with -start.
    4.38      The default implementation is empty, so a subclass using -start must override this. */
    4.39  - (void) main;
    4.40  
    4.41 -/** Returns control to an already-started (but blocked) coroutine.
    4.42 +
    4.43 +/** Returns control to a coroutine.
    4.44      The most recent -resume call made from within that coroutine will return.
    4.45      The current coroutine will block (i.e. this call won't return)
    4.46      until some other coroutine tells it to resume. */
    4.47  - (void) resume;
    4.48  
    4.49 +- (id) call;
    4.50  
    4.51 -/** The coroutine's name. You can put anything you want here, as a debugging aid. */
    4.52 -@property (copy) NSString* name;
    4.53 ++ (void) yieldToCaller: (id)value;
    4.54 +
    4.55  
    4.56  /** Returns YES if this is the currently executing coroutine. */
    4.57  @property (readonly) BOOL isCurrent;
    4.58  
    4.59 -/** The stack size of the coroutine. You can only change this before calling -start! */
    4.60 -@property size_t stackSize;
    4.61 -
    4.62  /** The number of bytes of stack space left on this coroutine. */
    4.63  @property (readonly) size_t bytesLeftOnStack;
    4.64  
    4.65  /** Returns YES if this coroutine is almost out of stack space (less than 8k left) */
    4.66  @property (readonly) BOOL stackSpaceAlmostGone;
    4.67  
    4.68 +
    4.69 ++ (void) setDefaultStackSize: (size_t)stackSize;
    4.70 +
    4.71  @end
     5.1 --- a/Coroutines/MYCoroutine.m	Tue Apr 29 17:05:32 2008 -0700
     5.2 +++ b/Coroutines/MYCoroutine.m	Wed Apr 30 14:18:49 2008 -0700
     5.3 @@ -12,7 +12,7 @@
     5.4  
     5.5  
     5.6  #ifndef LogTo
     5.7 -#define kEnableLog 0    /* 1 enables logging, 0 disables it */
     5.8 +#define kEnableLog 1    /* 1 enables logging, 0 disables it */
     5.9  #define LogTo(DOMAIN,MSG,...) do{ if(kEnableLog) NSLog(@""#DOMAIN ": " MSG,__VA_ARGS__); }while(0)
    5.10  #endif
    5.11  
    5.12 @@ -21,20 +21,29 @@
    5.13  #endif
    5.14  
    5.15  
    5.16 +static void MYCoroutineStart( CoroX *coro );
    5.17 +
    5.18 +
    5.19  @implementation MYCoroutine
    5.20  
    5.21  
    5.22  static MYCoroutine *sMain, *sCurrent;
    5.23  
    5.24 +static NSMutableArray *sCallers;
    5.25 +static id sYieldResult;
    5.26  
    5.27 -+ (void) initialize
    5.28 +
    5.29 +- (id) _initMain
    5.30  {
    5.31 -    if( self == [MYCoroutine class] ) {
    5.32 -        sMain = [[self alloc] init];
    5.33 -        Coro_initializeMainCoro(sMain->_coro);
    5.34 -        sMain.name = @"MAIN";
    5.35 -        sCurrent = sMain;
    5.36 +    self = [super init];
    5.37 +    if (self != nil) {
    5.38 +        NSAssert(!sMain,@"Already created a main coroutine");
    5.39 +        _coro = CoroX_new(NULL,NULL);
    5.40 +        self.name = @"MAIN";
    5.41 +        sMain = self;
    5.42 +        LogTo(Coroutine,@"INIT %@ : _coro=%p",self,_coro);
    5.43      }
    5.44 +    return self;
    5.45  }
    5.46  
    5.47  
    5.48 @@ -42,7 +51,7 @@
    5.49  {
    5.50      self = [super init];
    5.51      if (self != nil) {
    5.52 -        _coro = Coro_new();
    5.53 +        _coro = CoroX_new(&MYCoroutineStart,self);
    5.54          LogTo(Coroutine,@"INIT %@ : _coro=%p",self,_coro);
    5.55      }
    5.56      return self;
    5.57 @@ -52,11 +61,20 @@
    5.58  - (void) dealloc
    5.59  {
    5.60      LogTo(Coroutine,@"DEALLOC %@",self);
    5.61 -    Coro_free(_coro);
    5.62 +    CoroX_free(_coro);
    5.63      [super dealloc];
    5.64  }
    5.65  
    5.66  
    5.67 ++ (void) initialize
    5.68 +{
    5.69 +    if( self == [MYCoroutine class] ) {
    5.70 +        sCurrent = [[self alloc] _initMain];
    5.71 +        sCallers = [[NSMutableArray alloc] init];
    5.72 +    }
    5.73 +}
    5.74 +
    5.75 +
    5.76  - (NSString*) description
    5.77  {
    5.78      if( _name )
    5.79 @@ -66,7 +84,7 @@
    5.80  }
    5.81  
    5.82  
    5.83 -@synthesize name=_name;
    5.84 +@synthesize name=_name, invocation=_invocation;
    5.85  
    5.86  
    5.87  + (MYCoroutine*) mainCoroutine          {return sMain;}
    5.88 @@ -74,70 +92,88 @@
    5.89  
    5.90  - (BOOL) isCurrent                      {return self==sCurrent;}
    5.91  
    5.92 -- (const void*) stack                   {return Coro_stack(_coro);}
    5.93 -- (size_t) stackSize                    {return Coro_stackSize(_coro);}
    5.94 -- (void) setStackSize: (size_t)size     {Coro_setStackSize_(_coro,size);}
    5.95 +- (const void*) stack                   {return CoroX_stack(_coro);}
    5.96 +- (size_t) stackSize                    {return CoroX_stackSize(_coro);}
    5.97 +- (void) setStackSize: (size_t)size     {CoroX_setStackSize_(_coro,size);}
    5.98  
    5.99 -- (size_t) bytesLeftOnStack             {return Coro_bytesLeftOnStack(_coro);}
   5.100 -- (BOOL) stackSpaceAlmostGone           {return Coro_stackSpaceAlmostGone(_coro);}
   5.101 ++ (void) setDefaultStackSize: (size_t)s {CoroX_setDefaultStackSize(s);}
   5.102  
   5.103 +- (size_t) bytesLeftOnStack             {return CoroX_bytesLeftOnStack(_coro);}
   5.104 +- (BOOL) stackSpaceAlmostGone           {return CoroX_stackSpaceAlmostGone(_coro);}
   5.105  
   5.106 -static void startWithInvocation( void *context )
   5.107 +- (BOOL) hasStarted                     {return CoroX_stack(_coro)!=NULL || self==sMain;}
   5.108 +
   5.109 +
   5.110 +- (void) _start
   5.111  {
   5.112 -    [(NSInvocation*)context invoke];
   5.113 +    if( _invocation )
   5.114 +        [_invocation invoke];
   5.115 +    else
   5.116 +        [self main];
   5.117 +    // If body returns, yield control to caller:
   5.118 +    LogTo(Coroutine,@"%@ finished",self);
   5.119 +    [MYCoroutine yieldToCaller: nil];
   5.120  }
   5.121  
   5.122 -static void startWithMain( void *context )
   5.123 +static void MYCoroutineStart( CoroX *coro )
   5.124  {
   5.125 -    [(MYCoroutine*)context main];
   5.126 -}
   5.127 -
   5.128 -- (void) startWithInvocation: (NSInvocation*)invocation
   5.129 -{
   5.130 -    LogTo(Coroutine,@"Starting %@ (currently in %@)",self,sCurrent);
   5.131 -    MYCoroutine *current = sCurrent;
   5.132 -    sCurrent = self;
   5.133 -    
   5.134 -    if( invocation )
   5.135 -        Coro_startCoro_(current->_coro, _coro, invocation, &startWithInvocation);
   5.136 -    else
   5.137 -        Coro_startCoro_(current->_coro, _coro, self, &startWithMain);
   5.138 -    
   5.139 -    sCurrent = current;
   5.140 -    LogTo(Coroutine,@"...resumed %@ after starting %@",sCurrent,self);
   5.141 -}
   5.142 -
   5.143 -- (void) start
   5.144 -{
   5.145 -    [self startWithInvocation: nil];
   5.146 +    MYCoroutine *self = CoroX_userData(coro);
   5.147 +    [self _start];
   5.148  }
   5.149  
   5.150  
   5.151  + (MYCoroutine*) startWithInvocation: (NSInvocation*)invocation
   5.152  {
   5.153      MYCoroutine *cr = [[self alloc] init];
   5.154 -    [cr startWithInvocation: invocation];
   5.155 +    cr.invocation = invocation;
   5.156 +    [cr resume];
   5.157      return cr;
   5.158  }
   5.159  
   5.160  
   5.161 +- (void) main
   5.162 +{
   5.163 +    // subclasses should override this if they don't use an invocation.
   5.164 +}
   5.165 +
   5.166 +
   5.167 +#pragma mark -
   5.168 +#pragma mark CALLING:
   5.169 +
   5.170 +
   5.171  - (void) resume
   5.172  {
   5.173      LogTo(Coroutine,@"Resuming %@ (currently in %@)",self,sCurrent);
   5.174      MYCoroutine *current = sCurrent;
   5.175      sCurrent = self;
   5.176 -    Coro_switchTo_(current->_coro,_coro);
   5.177 +    CoroX_switchTo_(current->_coro,_coro);
   5.178      sCurrent = current;
   5.179      LogTo(Coroutine,@"...resumed %@",sCurrent);
   5.180  }
   5.181  
   5.182  
   5.183 -- (void) main
   5.184 +- (id) call
   5.185  {
   5.186 -    // subclasses should override this.
   5.187 +    NSAssert(sCurrent!=self,@"Cannot call the current coroutine");
   5.188 +    [sCallers addObject: sCurrent];
   5.189 +    [self resume];
   5.190 +    id result = sYieldResult;
   5.191 +    sYieldResult = nil;
   5.192 +    return result;
   5.193  }
   5.194  
   5.195  
   5.196 ++ (void) yieldToCaller: (id)value
   5.197 +{
   5.198 +    MYCoroutine *caller = sCallers.lastObject;
   5.199 +    NSAssert(caller, @"No caller to yield to");
   5.200 +    sYieldResult = value;
   5.201 +    [sCallers removeLastObject];
   5.202 +    [caller resume];
   5.203 +}
   5.204 +
   5.205 +
   5.206 +
   5.207  @end
   5.208  
   5.209  
     6.1 --- a/Coroutines/MYCoroutineTest.m	Tue Apr 29 17:05:32 2008 -0700
     6.2 +++ b/Coroutines/MYCoroutineTest.m	Wed Apr 30 14:18:49 2008 -0700
     6.3 @@ -7,6 +7,7 @@
     6.4  //
     6.5  
     6.6  #import "MYCoroutine.h"
     6.7 +#import "CoroX.h"
     6.8  
     6.9  
    6.10  @interface CoroTest1 : MYCoroutine
    6.11 @@ -45,6 +46,17 @@
    6.12  
    6.13  @synthesize value;
    6.14  
    6.15 +- (void) regress: (int)depth
    6.16 +{
    6.17 +    char useUpSpace[1024];
    6.18 +    useUpSpace[0] = 0;
    6.19 +    NSLog(@"infinite regress:  depth=%i, stack space=%d", depth,self.bytesLeftOnStack);
    6.20 +    if( [[MYCoroutine currentCoroutine] stackSpaceAlmostGone] )
    6.21 +        NSLog(@"infinite regress: bailing out!");
    6.22 +    else
    6.23 +        [self regress: depth+1];
    6.24 +}
    6.25 +
    6.26  - (void) main
    6.27  {
    6.28  	int num = 0;
    6.29 @@ -53,7 +65,6 @@
    6.30  	secondCoro = [[CoroTest2 alloc] init];
    6.31      secondCoro.name = @"second";
    6.32      secondCoro.value = 2;
    6.33 -	[secondCoro start];
    6.34  	
    6.35  	while ( num < 100 ) 
    6.36  	{
    6.37 @@ -63,26 +74,78 @@
    6.38      
    6.39      [secondCoro release];
    6.40      
    6.41 +    NSLog(@"*** TESTING STACK LIMITS ***");
    6.42 +    [self regress: 1];
    6.43 +    
    6.44      [[MYCoroutine mainCoroutine] resume];
    6.45  }
    6.46  
    6.47 ++ (void) test
    6.48 +{
    6.49 +    NSLog(@"*** TESTING COROUTINES ***");
    6.50 +	firstCoro = [[CoroTest1 alloc] init];
    6.51 +    firstCoro.name = @"first";
    6.52 +    firstCoro.value = 1;
    6.53 +    [firstCoro resume];
    6.54 +    
    6.55 +    NSLog(@"Returned from coroutines; exiting");
    6.56 +    
    6.57 +    [firstCoro release];
    6.58 +}
    6.59 +
    6.60  @end
    6.61  
    6.62  
    6.63 +
    6.64 +
    6.65 +@interface Generator : MYCoroutine
    6.66 +{ int _count; }
    6.67 +- (id) initWithCount: (int)count;
    6.68 +@end
    6.69 +
    6.70 +@implementation Generator
    6.71 +
    6.72 +- (id) initWithCount: (int)count
    6.73 +{
    6.74 +    self = [super init];
    6.75 +    if (self != nil) {
    6.76 +        _count = count;
    6.77 +    }
    6.78 +    return self;
    6.79 +}
    6.80 +
    6.81 +
    6.82 +- (void) main
    6.83 +{
    6.84 +    for( int i=1; i<=_count; i++ )
    6.85 +        [MYCoroutine yieldToCaller: [NSNumber numberWithInt: i]];
    6.86 +}
    6.87 +
    6.88 ++ (void) test
    6.89 +{
    6.90 +    NSLog(@"*** TESTING GENERATOR ***");
    6.91 +    Generator *g = [[Generator alloc] initWithCount: 10];
    6.92 +    id value;
    6.93 +    while( nil != (value = [g call]) )
    6.94 +        NSLog(@"Generator yielded %@",value);
    6.95 +    NSLog(@"Generator returned nil");
    6.96 +}
    6.97 +
    6.98 +@end
    6.99 +
   6.100 +
   6.101 +
   6.102  int main()
   6.103  {
   6.104      NSAutoreleasePool *pool = [NSAutoreleasePool new];
   6.105 +    NSLog(@"Starting test...");
   6.106      
   6.107 -    NSLog(@"Starting test...");
   6.108 -    //[[[NSThread alloc] init] start];
   6.109 -	firstCoro = [[CoroTest1 alloc] init];
   6.110 -    firstCoro.name = @"first";
   6.111 -    firstCoro.value = 1;
   6.112 -    [firstCoro start];
   6.113 +    [MYCoroutine setDefaultStackSize: kCoroX_minStackSize];
   6.114      
   6.115 -    NSLog(@"Returned from coroutines; exiting");
   6.116 +    [CoroTest1 test];
   6.117      
   6.118 -    [firstCoro release];
   6.119 +    [Generator test];
   6.120      
   6.121 +    NSLog(@"FINISHED");
   6.122      [pool drain];
   6.123  }