summaryrefslogtreecommitdiff
path: root/gs/src/zcontext.c
diff options
context:
space:
mode:
authorHenry Stiles <henry.stiles@artifex.com>1998-07-26 07:36:41 +0000
committerHenry Stiles <henry.stiles@artifex.com>1998-07-26 07:36:41 +0000
commiteec0ef527f18c5978c4476c9490f4de4c4249628 (patch)
tree5588d5e1300a245186594893c930949a19bcbbce /gs/src/zcontext.c
parentd4bdba93ef34f68d27148e1b31088d1d3e786e8c (diff)
downloadghostpdl-eec0ef527f18c5978c4476c9490f4de4c4249628.tar.gz
Initial revision
git-svn-id: http://svn.ghostscript.com/ghostpcl/trunk/ghostpcl@246 06663e23-700e-0410-b217-a244a6096597
Diffstat (limited to 'gs/src/zcontext.c')
-rw-r--r--gs/src/zcontext.c709
1 files changed, 709 insertions, 0 deletions
diff --git a/gs/src/zcontext.c b/gs/src/zcontext.c
new file mode 100644
index 000000000..c570f54ce
--- /dev/null
+++ b/gs/src/zcontext.c
@@ -0,0 +1,709 @@
+/* Copyright (C) 1991, 1995, 1997 Aladdin Enterprises. All rights reserved.
+
+ This file is part of Aladdin Ghostscript.
+
+ Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND. No author
+ or distributor accepts any responsibility for the consequences of using it,
+ or for whether it serves any particular purpose or works at all, unless he
+ or she says so in writing. Refer to the Aladdin Ghostscript Free Public
+ License (the "License") for full details.
+
+ Every copy of Aladdin Ghostscript must include a copy of the License,
+ normally in a plain ASCII text file named PUBLIC. The License grants you
+ the right to copy, modify and redistribute Aladdin Ghostscript, but only
+ under certain conditions described in the License. Among other things, the
+ License requires that the copyright notice and this notice be preserved on
+ all copies.
+*/
+
+/* zcontext.c */
+/* Display PostScript context operators */
+#include "memory_.h"
+#include "ghost.h"
+#include "gp.h" /* for usertime */
+#include "errors.h"
+#include "oper.h"
+#include "gsexit.h"
+#include "gsstruct.h"
+#include "gsutil.h"
+#include "gxalloc.h"
+#include "idict.h"
+#include "igstate.h"
+#include "icontext.h"
+#include "istruct.h"
+#include "dstack.h"
+#include "estack.h"
+#include "ostack.h"
+#include "store.h"
+
+/* Still needs:
+ fork: copy gstate stack
+ fork_done: do all necessary restores
+ */
+
+/* Scheduling hooks in interp.c */
+extern int (*gs_interp_reschedule_proc)(P0());
+extern int (*gs_interp_time_slice_proc)(P0());
+extern int gs_interp_time_slice_ticks;
+
+/* Per-context state stored in statics */
+extern ref ref_binary_object_format;
+
+/* Context state structure */
+/* (defined in icontext.h) */
+extern_st(st_ref_stack);
+private_st_context_state();
+#define pcst ((gs_context_state_t *)vptr)
+private ENUM_PTRS_BEGIN(context_state_enum_ptrs) return 0;
+ ENUM_REF(0, gs_context_state_t, dstack);
+ ENUM_REF(1, gs_context_state_t, estack);
+ ENUM_REF(2, gs_context_state_t, ostack);
+ ENUM_PTR(3, gs_context_state_t, pgs);
+ENUM_PTRS_END
+private RELOC_PTRS_BEGIN(context_state_reloc_ptrs) {
+ RELOC_REF(gs_context_state_t, dstack);
+ RELOC_REF(gs_context_state_t, estack);
+ RELOC_REF(gs_context_state_t, ostack);
+ RELOC_PTR(gs_context_state_t, pgs);
+} RELOC_PTRS_END
+#undef pcst
+
+/* Context structure */
+typedef enum {
+ cs_active,
+ cs_done
+} ctx_status;
+typedef struct gs_context_s gs_context;
+struct gs_context_s {
+ gs_context_state_t state; /* (must be first for subclassing) */
+ /* Private state */
+ ctx_status status;
+ ulong index;
+ bool detach; /* true if a detach has been */
+ /* executed for this context */
+ gs_context *next; /* next context with same status */
+ /* (active, waiting on same lock, */
+ /* waiting on same condition, */
+ /* waiting to be destroyed) */
+ gs_context *joiner; /* context waiting on a join */
+ /* for this one */
+ gs_context *table_next; /* hash table chain */
+};
+gs_private_st_suffix_add3(st_context, gs_context, "context",
+ context_enum_ptrs, context_reloc_ptrs, st_context_state,
+ next, joiner, table_next);
+
+/* Context list structure */
+typedef struct ctx_list_s {
+ gs_context *head;
+ gs_context *tail;
+} ctx_list;
+
+/* Condition structure */
+typedef struct gs_condition_s {
+ ctx_list waiting; /* contexts waiting on this condition */
+} gs_condition;
+gs_private_st_ptrs2(st_condition, gs_condition, "conditiontype",
+ condition_enum_ptrs, condition_reloc_ptrs, waiting.head, waiting.tail);
+
+/* Lock structure */
+typedef struct gs_lock_s {
+ ctx_list waiting; /* contexts waiting for this lock, */
+ /* must be first for subclassing */
+ gs_context *holder; /* context holding the lock, if any */
+} gs_lock;
+gs_private_st_suffix_add1(st_lock, gs_lock, "locktype",
+ lock_enum_ptrs, lock_reloc_ptrs, st_condition, holder);
+
+/* Global state */
+typedef struct gs_scheduler_s {
+ gs_context *current;
+ long usertime_initial; /* usertime when current started running */
+ ctx_list active;
+ gs_context *dead;
+#define ctx_table_size 19
+ gs_context *table[ctx_table_size];
+} gs_scheduler_t;
+private gs_scheduler_t *gs_scheduler;
+#define ctx_current gs_scheduler->current
+#define ctx_active gs_scheduler->active
+#define ctx_dead gs_scheduler->dead
+#define ctx_table gs_scheduler->table
+private gs_gc_root_t gs_scheduler_root;
+/* Structure definition */
+gs_private_st_composite(st_scheduler, gs_scheduler_t, "gs_scheduler",
+ scheduler_enum_ptrs, scheduler_reloc_ptrs);
+private ENUM_PTRS_BEGIN(scheduler_enum_ptrs) {
+ index -= 4;
+ if ( index < ctx_table_size )
+ ENUM_RETURN_PTR(gs_scheduler_t, table[index]);
+ return 0;
+}
+ ENUM_PTR(0, gs_scheduler_t, current);
+ ENUM_PTR(1, gs_scheduler_t, active.head);
+ ENUM_PTR(2, gs_scheduler_t, active.tail);
+ ENUM_PTR(3, gs_scheduler_t, dead);
+ENUM_PTRS_END
+private RELOC_PTRS_BEGIN(scheduler_reloc_ptrs) {
+ RELOC_PTR(gs_scheduler_t, current);
+ RELOC_PTR(gs_scheduler_t, active.head);
+ RELOC_PTR(gs_scheduler_t, active.tail);
+ RELOC_PTR(gs_scheduler_t, dead);
+ { int i;
+ for ( i = 0; i < ctx_table_size; ++i )
+ RELOC_PTR(gs_scheduler_t, table[i]);
+ }
+} RELOC_PTRS_END
+
+/* Forward references */
+private int context_create(P2(gs_context **, gs_ref_memory_t *));
+private long context_usertime(P0());
+#define context_load(psched, pctx)\
+ do {\
+ if ( (pctx)->state.keep_usertime )\
+ (psched)->usertime_initial = context_usertime();\
+ context_state_load(&(pctx)->state);\
+ } while (0)
+#define context_store(psched, pctx)\
+ do {\
+ context_state_store(&(pctx)->state);\
+ if ( (pctx)->state.keep_usertime )\
+ (pctx)->state.usertime_total +=\
+ context_usertime() - (psched)->usertime_initial;\
+ } while (0)
+private int context_param(P2(os_ptr, gs_context **));
+#define check_context(op, vpc)\
+ if ( (code = context_param(op, &vpc)) < 0 ) return code
+private void context_destroy(P1(gs_context *));
+private void stack_copy(P4(ref_stack *, const ref_stack *, uint, uint));
+private int lock_acquire(P1(os_ptr));
+private int lock_release(P1(ref *));
+
+/* List manipulation macros */
+#define add_last(pl,pc)\
+ (((pl)->head == 0 ? ((pl)->head = pc) : ((pl)->tail->next = pc)),\
+ (pl)->tail = pc, (pc)->next = 0)
+#define add_last_all(pl,pcl) /* pcl->head != 0 */\
+ (((pl)->head == 0 ? ((pl)->head = (pcl)->head) :\
+ ((pl)->tail->next = (pcl)->head)),\
+ (pl)->tail = (pcl)->tail, (pcl)->head = (pcl)->tail = 0)
+
+/* ------ Initialization ------ */
+
+private int ctx_reschedule(P0());
+private int ctx_time_slice(P0());
+private void
+zcontext_init(void)
+{ gs_ref_memory_t *imem = iimemory_global;
+
+ gs_scheduler = gs_alloc_struct((gs_memory_t *)imem, gs_scheduler_t,
+ &st_scheduler, "gs_scheduler");
+ gs_register_struct_root((gs_memory_t *)imem, &gs_scheduler_root,
+ (void **)&gs_scheduler, "gs_scheduler");
+ ctx_current = 0;
+ ctx_active.head = ctx_active.tail = 0;
+ ctx_dead = 0;
+ memset(ctx_table, 0, sizeof(ctx_table));
+ /* Create an initial context. */
+ if ( context_create(&ctx_current, imem) < 0 )
+ { lprintf("Can't create initial context!");
+ gs_abort();
+ }
+ ctx_current->state.memory = gs_imemory;
+ /* Hook into the interpreter. */
+ gs_interp_reschedule_proc = ctx_reschedule;
+ gs_interp_time_slice_proc = ctx_time_slice;
+ gs_interp_time_slice_ticks = 100;
+}
+
+/* ------ Interpreter interface to scheduler ------ */
+
+/* When an operator decides it is time to run a new context, */
+/* it returns o_reschedule. The interpreter saves all its state in */
+/* memory, calls ctx_reschedule, and then loads the state from memory. */
+private int
+ctx_reschedule(void)
+{ /* Save the state of the current context in ctx_current, */
+ /* if any context is current at all. */
+ if ( ctx_current != 0 )
+ context_store(gs_scheduler, ctx_current);
+ /* If there are any dead contexts waiting to be released, */
+ /* take care of that now. */
+ while ( ctx_dead != 0 )
+ { gs_context *next = ctx_dead->next;
+
+ context_destroy(ctx_dead);
+ ctx_dead = next;
+ }
+ /* Run the first ready context. */
+ if ( ctx_active.head == 0 )
+ { lprintf("No context to run!");
+ return_error(e_Fatal);
+ }
+ ctx_current = ctx_active.head;
+ if ( !(ctx_active.head = ctx_active.head->next) )
+ ctx_active.tail = 0;
+ /* Load the state of the new current context. */
+ context_load(gs_scheduler, ctx_current);
+ esfile_clear_cache();
+ dict_set_top(); /* reload dict stack cache */
+ return 0;
+}
+
+/* If the interpreter wants to time-slice, it saves its state, */
+/* calls ctx_time_slice, and reloads its state. */
+private int
+ctx_time_slice(void)
+{ if ( ctx_active.head == 0 )
+ return 0;
+ add_last(&ctx_active, ctx_current);
+ return ctx_reschedule();
+}
+
+/* ------ Context operators ------ */
+
+private int fork_done(P1(os_ptr));
+
+/* - currentcontext <context> */
+private int
+zcurrentcontext(register os_ptr op)
+{ push(1);
+ make_int(op, ctx_current->index);
+ return 0;
+}
+
+/* <context> detach - */
+private int
+zdetach(register os_ptr op)
+{ gs_context *pctx;
+ int code;
+
+ check_context(op, pctx);
+ if ( pctx->joiner != 0 || pctx->detach )
+ return_error(e_invalidcontext);
+ switch ( pctx->status )
+ {
+ case cs_active:
+ pctx->detach = true;
+ break;
+ case cs_done:
+ context_destroy(pctx);
+ }
+ pop(1);
+ return 0;
+}
+
+/* <mark> <obj1> ... <objN> <proc> fork <context> */
+private int
+zfork(register os_ptr op)
+{ uint mcount = ref_stack_counttomark(&o_stack);
+ gs_context *pctx;
+ int code;
+
+ check_proc(*op);
+ if ( mcount == 0 )
+ return_error(e_unmatchedmark);
+ if ( gs_imemory.save_level )
+ return_error(e_invalidcontext);
+ code = context_create(&pctx, iimemory_global);
+ if ( code < 0 )
+ return code;
+ /* Share local and global VM. */
+ pctx->state.memory = gs_imemory;
+ { int i;
+ for ( i = 0; i < countof(gs_imemory.spaces.indexed); ++i )
+ if ( gs_imemory.spaces.indexed[i] != 0 )
+ gs_imemory.spaces.indexed[i]->num_contexts++;
+ }
+ /* Initialize the stacks. */
+ { ref_stack *dstack = r_ptr(&pctx->state.dstack, ref_stack);
+ uint count = ref_stack_count(&d_stack);
+
+ ref_stack_push(dstack, count - ref_stack_count(dstack));
+ stack_copy(dstack, &d_stack, count, 0);
+ }
+ { ref_stack *estack = r_ptr(&pctx->state.estack, ref_stack);
+
+ ref_stack_push(estack, 3);
+ /* fork_done must be executed in both normal and error cases. */
+ make_mark_estack(estack->p - 2, es_other, fork_done);
+ make_oper(estack->p - 1, 0, fork_done);
+ *estack->p = *op;
+ }
+ { ref_stack *ostack = r_ptr(&pctx->state.ostack, ref_stack);
+ uint count = mcount - 2;
+
+ ref_stack_push(ostack, count);
+ stack_copy(ostack, &o_stack, count, 1);
+ }
+ /****** COPY GSTATE STACK ******/
+ pctx->state.binary_object_format = ref_binary_object_format;
+ add_last(&ctx_active, pctx);
+ pop(mcount - 1);
+ op = osp;
+ make_int(op, pctx->index);
+ return 0;
+}
+/* This gets executed when a context terminates normally */
+/* or when the stack is being unwound for an error termination. */
+/****** MUST DO ALL RESTORES ******/
+/****** WHAT IF invalidrestore? ******/
+private int
+fork_done(os_ptr op)
+{ if ( ctx_current->detach )
+ { /*
+ * We can't free the context's memory yet, because the
+ * interpreter still has references to it. Instead, queue the
+ * context to be freed the next time we reschedule.
+ */
+ context_store(gs_scheduler, ctx_current);
+ ctx_current->next = ctx_dead;
+ ctx_dead = ctx_current;
+ ctx_current = 0;
+ }
+ else
+ { gs_context *pctx = ctx_current->joiner;
+
+ ctx_current->status = cs_done;
+ /* Schedule the context waiting to join this one, if any. */
+ if ( pctx != 0 )
+ add_last(&ctx_active, pctx);
+ }
+ return o_reschedule;
+}
+
+/* <context> join <mark> <obj1> ... <objN> */
+private int
+zjoin(register os_ptr op)
+{ gs_context *pctx;
+ int code;
+
+ check_context(op, pctx);
+ if ( pctx->joiner != 0 || pctx->detach || pctx == ctx_current ||
+ pctx->state.memory.space_global !=
+ ctx_current->state.memory.space_global ||
+ pctx->state.memory.space_local !=
+ ctx_current->state.memory.space_local ||
+ gs_imemory.save_level != 0
+ )
+ return_error(e_invalidcontext);
+ switch ( pctx->status )
+ {
+ case cs_active:
+ pctx->joiner = ctx_current;
+ return o_reschedule;
+ case cs_done:
+ { const ref_stack *ostack =
+ r_ptr(&pctx->state.ostack, ref_stack);
+ uint count = ref_stack_count(ostack);
+
+ push(count);
+ { ref *rp = ref_stack_index(&o_stack, count);
+ make_mark(rp);
+ }
+ stack_copy(&o_stack, ostack, count, 0);
+ context_destroy(pctx);
+ }
+ }
+ return 0;
+}
+
+/* - yield - */
+private int
+zyield(register os_ptr op)
+{ if ( ctx_active.head == 0 )
+ return 0;
+ add_last(&ctx_active, ctx_current);
+ return o_reschedule;
+}
+
+/* ------ Condition and lock operators ------ */
+
+private int
+ monitor_cleanup(P1(os_ptr)),
+ monitor_release(P1(os_ptr)),
+ await_lock(P1(os_ptr));
+
+/* - condition <condition> */
+private int
+zcondition(register os_ptr op)
+{ gs_condition *pcond =
+ ialloc_struct(gs_condition, &st_condition, "zcondition");
+
+ if ( pcond == 0 )
+ return_error(e_VMerror);
+ pcond->waiting.head = pcond->waiting.tail = 0;
+ push(1);
+ make_istruct(op, a_all, pcond);
+ return 0;
+}
+
+/* - lock <lock> */
+private int
+zlock(register os_ptr op)
+{ gs_lock *plock = ialloc_struct(gs_lock, &st_lock, "zlock");
+
+ if ( plock == 0 )
+ return_error(e_VMerror);
+ plock->holder = 0;
+ plock->waiting.head = plock->waiting.tail = 0;
+ push(1);
+ make_istruct(op, a_all, plock);
+ return 0;
+}
+
+/* <lock> <proc> monitor - */
+private int
+zmonitor(register os_ptr op)
+{ gs_lock *plock;
+ int code;
+
+ check_stype(op[-1], st_lock);
+ check_proc(*op);
+ plock = r_ptr(op - 1, gs_lock);
+ if ( plock->holder == ctx_current ||
+ (gs_imemory.save_level != 0 &&
+ plock->holder != 0 &&
+ plock->holder->state.memory.space_local ==
+ ctx_current->state.memory.space_local)
+ )
+ return_error(e_invalidcontext);
+ /*
+ * We push on the e-stack:
+ * The lock object
+ * An e-stack mark with monitor_cleanup, to release the lock
+ * in case of an error
+ * monitor_release, to release the lock in the normal case
+ * The procedure to execute
+ */
+ check_estack(4);
+ code = lock_acquire(op - 1);
+ if ( code != 0 )
+ { /* We didn't acquire the lock. Re-execute this later. */
+ push_op_estack(zmonitor);
+ return code; /* o_reschedule */
+ }
+ *++esp = op[-1];
+ push_mark_estack(es_other, monitor_cleanup);
+ push_op_estack(monitor_release);
+ *++esp = *op;
+ pop(2);
+ return code;
+}
+/* Release the monitor lock when unwinding for an error or exit. */
+private int
+monitor_cleanup(os_ptr op)
+{ int code = lock_release(esp);
+ --esp;
+ return code;
+}
+/* Release the monitor lock when the procedure completes. */
+private int
+monitor_release(os_ptr op)
+{ --esp;
+ return monitor_cleanup(op);
+}
+
+/* <condition> notify - */
+private int
+znotify(register os_ptr op)
+{ gs_condition *pcond;
+
+ check_stype(*op, st_condition);
+ pcond = r_ptr(op, gs_condition);
+ pop(1); op--;
+ if ( pcond->waiting.head == 0 ) /* nothing to do */
+ return 0;
+ add_last_all(&ctx_active, &pcond->waiting);
+ return zyield(op);
+}
+
+/* <lock> <condition> wait - */
+private int
+zwait(register os_ptr op)
+{ gs_lock *plock;
+ gs_condition *pcond;
+
+ check_stype(op[-1], st_lock);
+ plock = r_ptr(op - 1, gs_lock);
+ check_stype(*op, st_condition);
+ pcond = r_ptr(op, gs_condition);
+ if ( plock->holder != ctx_current ||
+ (gs_imemory.save_level != 0 &&
+ (r_space(op - 1) == avm_local || r_space(op) == avm_local))
+ )
+ return_error(e_invalidcontext);
+ check_estack(1);
+ lock_release(op - 1);
+ add_last(&pcond->waiting, ctx_current);
+ push_op_estack(await_lock);
+ return o_reschedule;
+}
+/* When the condition is signaled, wait for acquiring the lock. */
+private int
+await_lock(os_ptr op)
+{ int code = lock_acquire(op - 1);
+
+ if ( code == 0 )
+ { pop(2);
+ return code;
+ }
+ /* We didn't acquire the lock. Re-execute the wait. */
+ push_op_estack(await_lock);
+ return code; /* o_reschedule */
+}
+
+/* ------ Miscellaneous operators ------ */
+
+/* - usertime <int> */
+private int
+zusertime_context(register os_ptr op)
+{ long utime = context_usertime();
+
+ push(1);
+ if ( !ctx_current->state.keep_usertime ) {
+ /*
+ * This is the first time this context has executed usertime:
+ * we must track its execution time from now on.
+ */
+ gs_scheduler->usertime_initial = utime;
+ ctx_current->state.keep_usertime = true;
+ }
+ make_int(op, ctx_current->state.usertime_total + utime -
+ gs_scheduler->usertime_initial);
+ return 0;
+}
+
+/* ------ Internal routines ------ */
+
+/* Create a context. */
+private int
+context_create(gs_context **ppctx, gs_ref_memory_t *mem)
+{ gs_context *pctx;
+ int code;
+ long ctx_index;
+ gs_context **pte;
+
+ pctx = gs_alloc_struct((gs_memory_t *)mem, gs_context, &st_context,
+ "context_create");
+ if ( pctx == 0 )
+ return_error(e_VMerror);
+ code = context_state_alloc(&pctx->state, mem);
+ if ( code < 0 )
+ { gs_free_object((gs_memory_t *)mem, pctx, "context_create");
+ return code;
+ }
+ ctx_index = gs_next_ids(1);
+ pctx->status = cs_active;
+ pctx->index = ctx_index;
+ pctx->detach = false;
+ pctx->next = 0;
+ pctx->joiner = 0;
+ pte = &ctx_table[ctx_index % ctx_table_size];
+ pctx->table_next = *pte;
+ *pte = pctx;
+ *ppctx = pctx;
+ return 0;
+}
+
+/* Check a context ID. Note that we do not check for context validity. */
+private int
+context_param(os_ptr op, gs_context **ppctx)
+{ gs_context *pctx;
+ long index;
+
+ check_type(*op, t_integer);
+ index = op->value.intval;
+ if ( index < 0 )
+ return_error(e_invalidcontext);
+ pctx = ctx_table[index % ctx_table_size];
+ for ( ; ; pctx = pctx->table_next )
+ { if ( pctx == 0 )
+ return_error(e_invalidcontext);
+ if ( pctx->index == index )
+ break;
+ }
+ *ppctx = pctx;
+ return 0;
+}
+
+/* Read the usertime as a single value. */
+private long
+context_usertime(void)
+{ long secs_ns[2];
+
+ gp_get_usertime(secs_ns);
+ return secs_ns[0] * 1000 + secs_ns[1] / 1000000;
+}
+
+/* Destroy a context. */
+private void
+context_destroy(gs_context *pctx)
+{ gs_context **ppctx = &ctx_table[pctx->index % ctx_table_size];
+
+ while ( *ppctx != pctx )
+ ppctx = &(*ppctx)->table_next;
+ *ppctx = (*ppctx)->table_next;
+ context_state_free(&pctx->state, iimemory);
+ ifree_object(pctx, "context_destroy");
+}
+
+/* Copy the top elements of one stack to another. */
+/* Note that this does not push the elements: */
+/* the destination stack must have enough space preallocated. */
+private void
+stack_copy(ref_stack *to, const ref_stack *from, uint count, uint from_index)
+{ long i;
+
+ for ( i = (long)count - 1; i >= 0; --i )
+ *ref_stack_index(to, i) = *ref_stack_index(from, i + from_index);
+}
+
+/* Acquire a lock. Return 0 if acquired, o_reschedule if not. */
+private int
+lock_acquire(os_ptr op)
+{ gs_lock *plock = r_ptr(op, gs_lock);
+
+ if ( plock->holder == 0 )
+ { plock->holder = ctx_current;
+ return 0;
+ }
+ add_last(&plock->waiting, ctx_current);
+ return o_reschedule;
+}
+
+/* Release a lock. Return 0 if OK, e_invalidcontext if not. */
+private int
+lock_release(ref *op)
+{ gs_lock *plock = r_ptr(op, gs_lock);
+
+ if ( plock->holder == ctx_current )
+ { plock->holder = 0;
+ add_last_all(&ctx_active, &plock->waiting);
+ return 0;
+ }
+ return_error(e_invalidcontext);
+}
+
+/* ------ Initialization procedure ------ */
+
+BEGIN_OP_DEFS(zcontext_op_defs) {
+ {"0condition", zcondition},
+ {"0currentcontext", zcurrentcontext},
+ {"1detach", zdetach},
+ {"2fork", zfork},
+ {"1join", zjoin},
+ {"0lock", zlock},
+ {"2monitor", zmonitor},
+ {"1notify", znotify},
+ {"2wait", zwait},
+ {"0yield", zyield},
+ /* Note that the following replace prior definitions */
+ /* in the indicated files: */
+ {"0usertime", zusertime_context}, /* zmisc.c */
+ /* Internal operators */
+ {"0%fork_done", fork_done},
+ {"0%monitor_cleanup", monitor_cleanup},
+ {"0%monitor_release", monitor_release},
+ {"2%await_lock", await_lock},
+END_OP_DEFS(zcontext_init) }