Fixed to compile and run on origin2000 and origin-pthreads.
[charm.git] / src / conv-core / threads.c
1  /**************************************************************************
2  *
3  * typedef CthThread
4  *
5  *   - a first-class thread object.
6  *
7  * CthThread CthSelf()
8  *
9  *   - returns the current thread.
10  *
11  * void CthResume(CthThread t)
12  *
13  *   - Immediately transfers control to thread t.  Note: normally, the user
14  *     of a thread package wouldn't explicitly choose which thread to transfer
15  *     to.  Instead, the user would rely upon a "scheduler" to choose the
16  *     next thread.  Therefore, this routine is primarily intended for people
17  *     who are implementing schedulers, not for end-users.  End-users should
18  *     probably call CthSuspend or CthAwaken (see below).
19  *
20  * CthThread CthCreate(CthVoidFn fn, void *arg, int size)
21  *
22  *   - Creates a new thread object.  The thread is not given control yet.
23  *     The thread is not passed to the scheduler.  When (and if) the thread
24  *     eventually receives control, it will begin executing the specified 
25  *     function 'fn' with the specified argument.  The 'size' parameter
26  *     specifies the stack size, 0 means use the default size.
27  *
28  * void CthFree(CthThread t)
29  *
30  *   - Frees thread t.  You may free the currently-executing thread, although
31  *     the free will actually be postponed until the thread suspends.
32  *
33  *
34  * In addition to the routines above, the threads package assumes that there
35  * will be a "scheduler" of some sort, whose job is to select which threads
36  * to execute.  The threads package does not provide a scheduler (although
37  * converse may provide one or more schedulers separately).  However, for
38  * standardization reasons, it does define an interface to which all schedulers
39  * can comply.  A scheduler consists of a pair of functions:
40  *
41  *   - An awaken-function.  The awaken-function is called to
42  *     notify the scheduler that a particular thread needs the CPU.  The
43  *     scheduler is expected to respond to this by inserting the thread
44  *     into a ready-pool of some sort.
45  *
46  *   - A choose-next function.  The choose-next function is called to
47  *     to ask the scheduler which thread to execute next.
48  *
49  * The interface to the scheduler is formalized in the following functions:
50  *
51  * void CthSchedInit()
52  *
53  *   - you must call this before any of the following functions will work.
54  *
55  * void CthSuspend()
56  *
57  *   - The thread calls this function, which in turn calls the scheduler's
58  *     choose-next function.  It then resumes whatever thread is returned
59  *     by the choose-next function.
60  *
61  * void CthAwaken(CthThread t)
62  *
63  *   - The thread-package user calls this function, which in turn calls the
64  *     scheduler's awaken-function to awaken thread t.  This probably causes
65  *     the thread t to be inserted in the ready-pool.
66  *
67  * void CthSetStrategy(CthThread t, CthVoidFn awakenfn, CthThFn choosefn)
68  *
69  *     This specifies the scheduling functions to be used for thread 't'.
70  *     The scheduling functions must have the following prototypes:
71  *
72  *          void awakenfn(CthThread t);
73  *          CthThread choosefn();
74  *
75  *     These functions must be provided on a per-thread basis.  (Eg, if you
76  *     CthAwaken a thread X, then X's awakefn will be called.  If a thread Y
77  *     calls CthSuspend, then Y's choosefn will be called to pick the next
78  *     thread.)  Of course, you may use the same functions for all threads
79  *     (the common case), but the specification on a per-thread basis gives
80  *     you maximum flexibility in controlling scheduling.
81  *
82  *     See also: common code, CthSetStrategyDefault.
83  *
84  * void CthYield()
85  *
86  *   - simply executes { CthAwaken(CthSelf()); CthSuspend(); }.  This
87  *     combination gives up control temporarily, but ensures that control
88  *     will eventually return.
89  *
90  *
91  * Note: there are several possible ways to implement threads.   No one
92  * way works on all machines.  Instead, we provide one implementation which
93  * covers a lot of machines, and a second implementation that simply prints
94  * "Not Implemented".  We may provide other implementations in the future.
95  *
96  *****************************************************************************/
97  
98 #include "converse.h"
99 #include "qt.h"
100 #include "conv-trace.h"
101 #include <sys/types.h>
102
103 #if CMK_THREADS_COPY_STACK
104
105 #define SWITCHBUF_SIZE 16384
106
107 typedef struct CthProcInfo *CthProcInfo;
108
109 CthThread x;
110
111 typedef struct CthThreadStruct
112 {
113   char cmicore[CmiMsgHeaderSizeBytes];
114   CthVoidFn  awakenfn;
115   CthThFn    choosefn;
116   CthVoidFn  startfn;    /* function that thread will execute */
117   void      *startarg;   /* argument that start function will be passed */
118   int        insched;    /* is this thread in scheduler queue */
119   int        killed;     /* thread is marked for death */
120   char      *data;       /* thread private data */
121   int        datasize;   /* size of thread-private data, in bytes */
122   int        suspendable;
123 /** addition for tracing */
124   int        Event;
125 /** End Addition */
126   CthThread  qnext;      /* for cthsetnext and cthgetnext */
127   qt_t      *savedstack; /* pointer to saved stack */
128   int        savedsize;  /* length of saved stack (zero when running) */
129   int        stacklen;   /* length of the allocated savedstack >= savedsize */
130   qt_t      *savedptr;   /* stack pointer */
131 } CthThreadStruct;
132
133 void CthSetSuspendable(CthThread t, int val)
134 {
135   t->suspendable = val;
136 }
137
138 int CthIsSuspendable(CthThread t)
139 {
140   return t->suspendable;
141 }
142
143 int CthPackBufSize(CthThread t)
144 {
145 #ifndef CMK_OPTIMIZE
146   if (t->savedsize == 0)
147     CmiAbort("Trying to pack a running thread!!\n");
148 #endif
149   return sizeof(CthThreadStruct) + t->datasize + t->savedsize;
150 }
151
152 void CthPackThread(CthThread t, void *buffer)
153 {
154 #ifndef CMK_OPTIMIZE
155   if (t->savedsize == 0)
156     CmiAbort("Trying to pack a running thread!!\n");
157   if (t->insched)
158     CmiAbort("Trying to pack a thread in scheduler queue!!\n");
159 #endif
160   memcpy(buffer, (void *)t, sizeof(CthThreadStruct));
161   memcpy(((char*)buffer)+sizeof(CthThreadStruct), 
162          (void *)t->data, t->datasize);
163   free((void *)t->data);
164   memcpy(((char*)buffer)+sizeof(CthThreadStruct)+t->datasize, 
165          (void *)t->savedstack, t->savedsize);
166   free((void *)t->savedstack);
167   free(t);
168 }
169
170 CthThread CthUnpackThread(void *buffer)
171 {
172   CthThread t = (CthThread) malloc(sizeof(CthThreadStruct));
173   _MEMCHECK(t);
174   memcpy((void*) t, buffer, sizeof(CthThreadStruct));
175   t->data = (char *) malloc(t->datasize);
176   _MEMCHECK(t->data);
177   memcpy((void*)t->data, ((char*)buffer)+sizeof(CthThreadStruct),
178          t->datasize);
179   t->savedstack = (qt_t*) malloc(t->savedsize);
180   _MEMCHECK(t->savedstack);
181   t->stacklen = t->savedsize;
182   memcpy((void*)t->savedstack, 
183          ((char*)buffer)+sizeof(CthThreadStruct)+t->datasize, t->savedsize);
184   return t;
185 }
186
187 /** addition for tracing */
188 void setEvent(CthThread t, int event)
189 {
190   t->Event = event;
191 }
192
193 int getEvent(CthThread t)
194 {
195   return t->Event;
196 }
197 /** End Addition */
198
199 struct CthProcInfo
200 {
201   CthThread  current;
202   int        datasize;
203   qt_t      *stackbase;
204   qt_t      *switchbuf_sp;
205   qt_t      *switchbuf;
206 };
207
208 CthCpvDeclare(char *, CthData);
209 CthCpvDeclare(CthProcInfo, CthProc);
210
211 int CthImplemented()
212 { return 1; }
213
214 static void CthThreadInit(CthThread t, CthVoidFn fn, void *arg)
215 {
216   t->awakenfn = 0;
217   t->choosefn = 0;
218   t->startfn = fn;
219   t->startarg = arg;
220   t->insched = 0;
221   t->killed = 0;
222   t->data = 0;
223   t->datasize = 0;
224   t->qnext = 0;
225   t->savedstack = 0;
226   t->savedsize = 0;
227   t->stacklen = 0;
228   t->savedptr = 0;
229   t->suspendable = 1;
230   CthSetStrategyDefault(t);
231 }
232
233 void CthFixData(CthThread t)
234 {
235   CthProcInfo proc = CthCpvAccess(CthProc);
236   int datasize = proc->datasize;
237   if (t->data == 0) {
238     t->datasize = datasize;
239     t->data = (char *)malloc(datasize);
240     _MEMCHECK(t->data);
241     return;
242   }
243   if (t->datasize != datasize) {
244     t->datasize = datasize;
245     t->data = (char *)realloc(t->data, datasize);
246     return;
247   }
248 }
249
250 static void CthFreeNow(CthThread t)
251 {
252   if (t->data) free(t->data);
253   if (t->savedstack) free(t->savedstack);
254   free(t);
255 }
256
257 void CthFree(t)
258 CthThread t;
259 {
260   CthProcInfo proc = CthCpvAccess(CthProc);
261   if ((t->insched == 0)&&(t != proc->current)) {
262     CthFreeNow(t);
263     return;
264   }
265   t->killed = 1;
266 }
267
268 void CthDummy() { }
269
270 void CthInit()
271 {
272   CthThread t; CthProcInfo p; qt_t *switchbuf, *sp;
273
274   CthCpvInitialize(char *, CthData);
275   CthCpvInitialize(CthProcInfo, CthProc);
276
277   t = (CthThread)malloc(sizeof(struct CthThreadStruct));
278   _MEMCHECK(t);
279   p = (CthProcInfo)malloc(sizeof(struct CthProcInfo));
280   _MEMCHECK(p);
281   CthThreadInit(t,0,0);
282   CthCpvAccess(CthData)=0;
283   CthCpvAccess(CthProc)=p;
284   /* leave some space for current stack frame < 256 bytes */
285   sp = (qt_t*)(((size_t)&t) & ~((size_t)0xFF));
286   p->stackbase = QT_SP(sp, 0x100);
287   p->current = t;
288   p->datasize = 0;
289   switchbuf = (qt_t*)malloc(QT_STKALIGN + SWITCHBUF_SIZE);
290   _MEMCHECK(switchbuf);
291   switchbuf = (qt_t*)((((size_t)switchbuf)+QT_STKALIGN) & ~(QT_STKALIGN-1));
292   p->switchbuf = switchbuf;
293   sp = QT_SP(switchbuf, SWITCHBUF_SIZE);
294   sp = QT_ARGS(sp,0,0,0,(qt_only_t*)CthDummy);
295   p->switchbuf_sp = sp;
296   CthSetStrategyDefault(t);
297 }
298
299 CthThread CthSelf()
300 {
301   CthThread result = CthCpvAccess(CthProc)->current;
302   if (result==0) CmiAbort("BARF!\n");
303   return result;
304 }
305
306 static void CthOnly(CthThread t, void *dum1, void *dum2)
307 {
308   t->startfn(t->startarg);
309   t->killed = 1;
310   CthSuspend();
311 }
312
313 static void CthResume1(qt_t *sp, CthProcInfo proc, CthThread t)
314 {
315   int bytes; qt_t *lo, *hi;
316   CthThread old = proc->current;
317   if (old->killed) {
318     if (old->insched==0) CthFreeNow(old);
319   } else {
320 #ifdef QT_GROW_DOWN
321     lo = sp; hi = proc->stackbase;
322 #else
323     hi = sp; lo = proc->stackbase;
324 #endif
325     bytes = ((size_t)hi)-((size_t)lo);
326     if(bytes > old->stacklen) {
327       if(old->savedstack) free((void *)old->savedstack);
328       old->savedstack = (qt_t*)malloc(bytes);
329       _MEMCHECK(old->savedstack);
330       old->stacklen = bytes;
331     }
332     old->savedsize = bytes;
333     old->savedptr = sp;
334     memcpy(old->savedstack, lo, bytes);
335   }
336   CthFixData(t);
337   CthCpvAccess(CthData) = t->data;
338   if (t->savedstack) {
339 #ifdef QT_GROW_DOWN
340     lo = t->savedptr;
341 #else
342     lo = proc->stackbase;
343 #endif
344     memcpy(lo, t->savedstack, t->savedsize);
345     t->savedsize=0;
346     sp = t->savedptr;
347   } else {
348     sp = proc->stackbase;
349     sp = QT_ARGS(sp,t,0,0,(qt_only_t*)CthOnly);
350   }
351   proc->current = t;
352   t->insched = 0;
353   QT_ABORT((qt_helper_t*)CthDummy,0,0,sp);
354 }
355
356 void CthResume(t)
357 CthThread t;
358 {
359   CthProcInfo proc = CthCpvAccess(CthProc);
360   QT_BLOCK((qt_helper_t*)CthResume1, proc, t, proc->switchbuf_sp);
361 }
362
363 CthThread CthCreate(fn, arg, size)
364 CthVoidFn fn; void *arg; int size;
365 {
366   CthThread result = (CthThread)malloc(sizeof(struct CthThreadStruct));
367   _MEMCHECK(result);
368   CthThreadInit(result, fn, arg);
369   return result;
370 }
371
372 static void CthNoStrategy()
373 {
374   CmiAbort("Called CthAwaken or CthSuspend before calling CthSetStrategy.\n");
375 }
376
377 void CthSuspend()
378 {
379   CthThread current, next;
380   current = CthCpvAccess(CthProc)->current;
381   if(!(current->suspendable))
382     CmiAbort("trying to suspend main thread!!\n");
383   if (current->choosefn == 0) CthNoStrategy();
384   /* Pick a thread, discarding dead ones */
385   while (1) {
386     next = current->choosefn();
387     if (next->killed == 0) break;
388     CmiAbort("picked dead thread.\n");
389     if (next==current)
390       CmiAbort("Current thread dead, cannot pick new thread.\n");
391     CthFreeNow(next);
392   }
393   CthResume(next);
394 }
395
396 void CthAwaken(th)
397 CthThread th;
398 {
399   if (th->awakenfn == 0) CthNoStrategy();
400   if (th->insched) CmiAbort("CthAwaken: thread already awake.\n");
401   th->awakenfn(th);
402   th->insched = 1;
403 }
404
405 void CthSetStrategy(t, awkfn, chsfn)
406 CthThread t;
407 CthVoidFn awkfn;
408 CthThFn chsfn;
409 {
410   t->awakenfn = awkfn;
411   t->choosefn = chsfn;
412 }
413
414 void CthYield()
415 {
416   CthAwaken(CthCpvAccess(CthProc)->current);
417   CthSuspend();
418 }
419
420 int CthRegister(size)
421 int size;
422 {
423   CthProcInfo proc = CthCpvAccess(CthProc);
424   int result;
425   proc->datasize = (proc->datasize + 7) & (~7);
426   result = proc->datasize;
427   proc->datasize += size;
428   CthFixData(proc->current);
429   CthCpvAccess(CthData) = proc->current->data;
430   return result;
431 }
432
433 void CthSetNext(CthThread t, CthThread v)
434 {
435   t->qnext = v;
436 }
437
438 CthThread CthGetNext(CthThread t) 
439 {
440   return t->qnext;
441 }
442
443 #else
444
445 #define STACKSIZE (32768)
446
447 #if CMK_MEMORY_PROTECTABLE
448
449 #include "sys/mman.h"
450 #define CthMemAlign(x,n) memalign((x),(n))
451 #define CthMemoryProtect(p,l) mprotect(p,l,PROT_NONE)
452 #define CthMemoryUnprotect(p,l) mprotect(p,l,PROT_READ | PROT_WRITE)
453
454 #else
455
456 #define CthMemAlign(x,n) malloc(n)
457 #define CthMemoryProtect(p,l) 
458 #define CthMemoryUnprotect(p,l)
459 #define memalign(m, a) valloc(a)
460
461 #endif
462
463 struct CthThreadStruct
464 {
465   char cmicore[CmiMsgHeaderSizeBytes];
466   CthVoidFn  awakenfn;
467   CthThFn    choosefn;
468   int        autoyield_enable;
469   int        autoyield_blocks;
470   char      *data;
471   int        datasize;
472   int        suspendable;
473 /** addition for tracing */
474   int        Event;
475 /** End Addition */
476   CthThread  qnext;
477   char      *protect;
478   int        protlen;
479   qt_t      *stack;
480   qt_t      *stackp;
481 };
482
483 void CthSetSuspendable(CthThread t, int val)
484 {
485   t->suspendable = val;
486 }
487
488 int CthIsSuspendable(CthThread t)
489 {
490   return t->suspendable;
491 }
492
493 /** addition for tracing */
494 void setEvent(CthThread t, int event)
495 {
496   t->Event = event;
497 }
498
499 int getEvent(CthThread t)
500 {
501   return t->Event;
502 }
503 /** End Addition */
504
505 CthCpvDeclare(char *,    CthData);
506 CthCpvStatic(CthThread,  CthCurrent);
507 CthCpvStatic(int,        CthExiting);
508 CthCpvStatic(int,        CthDatasize);
509
510 int CthImplemented()
511 { return 1; }
512
513 static void CthNoStrategy()
514 {
515   CmiPrintf("Called CthAwaken or CthSuspend before calling CthSetStrategy.\n");
516   exit(1);
517 }
518
519 static void CthThreadInit(t)
520 CthThread t;
521 {
522   t->awakenfn = 0;
523   t->choosefn = 0;
524   t->data=0;
525   t->datasize=0;
526   t->qnext=0;
527   t->autoyield_enable = 0;
528   t->autoyield_blocks = 0;
529   t->suspendable = 1;
530 }
531
532 void CthFixData(t)
533 CthThread t;
534 {
535   int datasize = CthCpvAccess(CthDatasize);
536   if (t->data == 0) {
537     t->datasize = datasize;
538     t->data = (char *)malloc(datasize);
539     _MEMCHECK(t->data);
540     return;
541   }
542   if (t->datasize != datasize) {
543     t->datasize = datasize;
544     t->data = (char *)realloc(t->data, datasize);
545     return;
546   }
547 }
548
549 void CthInit()
550 {
551   CthThread t;
552
553   CthCpvInitialize(char *,     CthData);
554   CthCpvInitialize(CthThread,  CthCurrent);
555   CthCpvInitialize(int,        CthDatasize);
556   CthCpvInitialize(int,        CthExiting);
557
558   t = (CthThread)malloc(sizeof(struct CthThreadStruct));
559   _MEMCHECK(t);
560   t->protect = 0;
561   t->protlen = 0;
562   CthThreadInit(t);
563   CthCpvAccess(CthData)=0;
564   CthCpvAccess(CthCurrent)=t;
565   CthCpvAccess(CthDatasize)=1;
566   CthCpvAccess(CthExiting)=0;
567   CthSetStrategyDefault(t);
568 }
569
570 CthThread CthSelf()
571 {
572   return CthCpvAccess(CthCurrent);
573 }
574
575 void CthFree(t)
576 CthThread t;
577 {
578   if (t==CthCpvAccess(CthCurrent)) {
579     CthCpvAccess(CthExiting) = 1;
580   } else {
581     CmiError("Not implemented CthFree.\n");
582     exit(1);
583   }
584 }
585
586 static void *CthAbortHelp(qt_t *sp, CthThread old, void *null)
587 {
588   CthMemoryUnprotect(old->protect, old->protlen);
589   if (old->data) free(old->data);
590   free(old->stack);
591   free(old);
592   return (void *) 0;
593 }
594
595 static void *CthBlockHelp(qt_t *sp, CthThread old, void *null)
596 {
597   old->stackp = sp;
598   return (void *) 0;
599 }
600
601 void CthResume(t)
602 CthThread t;
603 {
604   CthThread tc;
605   tc = CthCpvAccess(CthCurrent);
606   if (t == tc) return;
607   CthFixData(t);
608   CthCpvAccess(CthCurrent) = t;
609   CthCpvAccess(CthData) = t->data;
610   if (CthCpvAccess(CthExiting)) {
611     CthCpvAccess(CthExiting)=0;
612     QT_ABORT((qt_helper_t*)CthAbortHelp, tc, 0, t->stackp);
613   } else {
614     QT_BLOCK((qt_helper_t*)CthBlockHelp, tc, 0, t->stackp);
615   }
616   if (tc!=CthCpvAccess(CthCurrent)) { CmiError("Fugged up.\n"); exit(1); }
617 }
618
619 static void CthOnly(void *arg, void *vt, qt_userf_t fn)
620 {
621   fn(arg);
622   CthCpvAccess(CthExiting) = 1;
623   CthSuspend();
624 }
625
626 CthThread CthCreate(fn, arg, size)
627 CthVoidFn fn; void *arg; int size;
628 {
629   CthThread result; qt_t *stack, *stackbase, *stackp;
630   if (size==0) size = STACKSIZE;
631   size = (size+(CMK_MEMORY_PAGESIZE*2)-1) & ~(CMK_MEMORY_PAGESIZE-1);
632   stack = (qt_t*)CthMemAlign(CMK_MEMORY_PAGESIZE, size);
633   _MEMCHECK(stack);
634   result = (CthThread)malloc(sizeof(struct CthThreadStruct));
635   _MEMCHECK(result);
636   CthThreadInit(result);
637   stackbase = QT_SP(stack, size);
638   stackp = QT_ARGS(stackbase, arg, result, (qt_userf_t *)fn, CthOnly);
639   result->stack = stack;
640   result->stackp = stackp;
641   if (stack==stackbase) {
642     result->protect = ((char*)stack) + size - CMK_MEMORY_PAGESIZE;
643     result->protlen = CMK_MEMORY_PAGESIZE;
644   } else {
645     result->protect = ((char*)stack);
646     result->protlen = CMK_MEMORY_PAGESIZE;
647   }
648   CthMemoryProtect(result->protect, result->protlen);
649   CthSetStrategyDefault(result);
650   return result;
651 }
652
653 void CthSuspend()
654 {
655   CthThread next;
656 #if CMK_WEB_MODE
657   void usageStop();
658 #endif
659   if(!(CthCpvAccess(CthCurrent)->suspendable))
660     CmiAbort("trying to suspend main thread!!\n");
661   if (CthCpvAccess(CthCurrent)->choosefn == 0) CthNoStrategy();
662   next = CthCpvAccess(CthCurrent)->choosefn();
663   /** addition for tracing */
664 #ifndef CMK_OPTIMIZE
665   if(CpvAccess(traceOn))
666     traceSuspend();
667 #endif
668   /* end addition */
669 #if CMK_WEB_MODE
670   usageStop();
671 #endif
672   CthResume(next);
673 }
674
675 void CthAwaken(th)
676 CthThread th;
677 {
678   if (th->awakenfn == 0) CthNoStrategy();
679   /** addition for tracing */
680   CpvAccess(curThread) = th;
681 #ifndef CMK_OPTIMIZE
682   if(CpvAccess(traceOn))
683     traceAwaken();
684 #endif
685   /* end addition */
686   th->awakenfn(th);
687 }
688
689 void CthSetStrategy(t, awkfn, chsfn)
690 CthThread t;
691 CthVoidFn awkfn;
692 CthThFn chsfn;
693 {
694   t->awakenfn = awkfn;
695   t->choosefn = chsfn;
696 }
697
698 void CthYield()
699 {
700   CthAwaken(CthCpvAccess(CthCurrent));
701   CthSuspend();
702 }
703
704 int CthRegister(size)
705 int size;
706 {
707   int result;
708   int align = 1;
709   while (size>align) align<<=1;
710   CthCpvAccess(CthDatasize) = (CthCpvAccess(CthDatasize)+align-1) & ~(align-1);
711   result = CthCpvAccess(CthDatasize);
712   CthCpvAccess(CthDatasize) += size;
713   CthFixData(CthCpvAccess(CthCurrent));
714   CthCpvAccess(CthData) = CthCpvAccess(CthCurrent)->data;
715   return result;
716 }
717
718 void CthAutoYield(CthThread t, int flag)
719 {
720   t->autoyield_enable = flag;
721 }
722
723 int CthAutoYielding(CthThread t)
724 {
725   return t->autoyield_enable;
726 }
727
728 void CthAutoYieldBlock()
729 {
730   CthCpvAccess(CthCurrent)->autoyield_blocks ++;
731 }
732
733 void CthAutoYieldUnblock()
734 {
735   CthCpvAccess(CthCurrent)->autoyield_blocks --;
736 }
737
738 void CthSetNext(CthThread t, CthThread v)
739 {
740   t->qnext = v;
741 }
742
743 CthThread CthGetNext(CthThread t) 
744 {
745   return t->qnext;
746 }
747
748
749 int CthPackBufSize(CthThread t)
750 {
751   CmiAbort("CthPackBufSize not implemented.\n");
752   return 0;
753 }
754
755 void CthPackThread(CthThread t, void *buffer)
756 {
757   CmiAbort("CthPackThread not implemented.\n");
758 }
759
760 CthThread CthUnpackThread(void *buffer)
761 {
762   CmiAbort("CthUnpackThread not implemented.\n");
763   return (CthThread) 0;
764 }
765
766 #endif