summaryrefslogtreecommitdiff
path: root/gs/psi/zcontext.c
diff options
context:
space:
mode:
Diffstat (limited to 'gs/psi/zcontext.c')
-rw-r--r--gs/psi/zcontext.c1297
1 files changed, 1297 insertions, 0 deletions
diff --git a/gs/psi/zcontext.c b/gs/psi/zcontext.c
new file mode 100644
index 000000000..151f833b4
--- /dev/null
+++ b/gs/psi/zcontext.c
@@ -0,0 +1,1297 @@
+/* Copyright (C) 2001-2006 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* $Id$ */
+/* Display PostScript context operators */
+#include "memory_.h"
+#include "ghost.h"
+#include "gp.h" /* for usertime */
+#include "oper.h"
+#include "gsexit.h"
+#include "gsgc.h"
+#include "gsstruct.h"
+#include "gsutil.h"
+#include "gxalloc.h"
+#include "gxstate.h" /* for copying gstate stack */
+#include "stream.h" /* for files.h */
+#include "files.h"
+#include "idict.h"
+#include "igstate.h"
+#include "icontext.h"
+#include "interp.h"
+#include "isave.h"
+#include "istruct.h"
+#include "dstack.h"
+#include "estack.h"
+#include "ostack.h"
+#include "store.h"
+
+/*
+ * Define the rescheduling interval. A value of max_int effectively
+ * disables scheduling. The only reason not to make this const is to
+ * allow it to be changed during testing.
+ */
+static int reschedule_interval = 100;
+
+/* Scheduling hooks in interp.c */
+extern int (*gs_interp_reschedule_proc)(i_ctx_t **);
+extern int (*gs_interp_time_slice_proc)(i_ctx_t **);
+extern int gs_interp_time_slice_ticks;
+
+/* Context structure */
+typedef enum {
+ cs_active,
+ cs_done
+} ctx_status_t;
+typedef long ctx_index_t; /* >= 0 */
+typedef struct gs_context_s gs_context_t;
+typedef struct gs_scheduler_s gs_scheduler_t;
+
+/*
+ * If several contexts share local VM, then if any one of them has done an
+ * unmatched save, the others are not allowed to run. We handle this by
+ * maintaining the following invariant:
+ * When control reaches the point in the scheduler that decides
+ * what context to run next, then for each group of contexts
+ * sharing local VM, if the save level for that VM is non-zero,
+ * saved_local_vm is only set in the context that has unmatched
+ * saves.
+ * We maintain this invariant as follows: when control enters the
+ * scheduler, if a context was running, we set its saved_local_vm flag
+ * to (save_level > 0). When selecting a context to run, we ignore
+ * contexts where saved_local_vm is false and the local VM save_level > 0.
+ */
+struct gs_context_s {
+ gs_context_state_t state; /* (must be first for subclassing) */
+ /* Private state */
+ gs_scheduler_t *scheduler;
+ ctx_status_t status;
+ ctx_index_t index; /* > 0 */
+ bool detach; /* true if a detach has been */
+ /* executed for this context */
+ bool saved_local_vm; /* (see above) */
+ bool visible; /* during GC, true if visible; */
+ /* otherwise, always true */
+ ctx_index_t next_index; /* next context with same status */
+ /* (active, waiting on same lock, */
+ /* waiting on same condition, */
+ /* waiting to be destroyed) */
+ ctx_index_t joiner_index; /* context waiting on a join */
+ /* for this one */
+ gs_context_t *table_next; /* hash table chain -- this must be a real */
+ /* pointer, for looking up indices */
+};
+static inline bool
+context_is_visible(const gs_context_t *pctx)
+{
+ return (pctx && pctx->visible);
+}
+static inline gs_context_t *
+visible_context(gs_context_t *pctx)
+{
+ return (pctx && pctx->visible ? pctx : (gs_context_t *)0);
+}
+
+/* GC descriptor */
+static
+CLEAR_MARKS_PROC(context_clear_marks)
+{
+ gs_context_t *const pctx = vptr;
+
+ (*st_context_state.clear_marks)
+ (cmem, &pctx->state, sizeof(pctx->state), &st_context_state);
+}
+static
+ENUM_PTRS_WITH(context_enum_ptrs, gs_context_t *pctx)
+ENUM_PREFIX(st_context_state, 2);
+case 0: return ENUM_OBJ(pctx->scheduler);
+case 1: {
+ /* Return the next *visible* context. */
+ const gs_context_t *next = pctx->table_next;
+
+ while (next && !next->visible)
+ next = next->table_next;
+ return ENUM_OBJ(next);
+}
+ENUM_PTRS_END
+static RELOC_PTRS_WITH(context_reloc_ptrs, gs_context_t *pctx)
+ RELOC_PREFIX(st_context_state);
+ RELOC_VAR(pctx->scheduler);
+ /* Don't relocate table_next -- the scheduler object handles that. */
+RELOC_PTRS_END
+gs_private_st_complex_only(st_context, gs_context_t, "gs_context_t",
+ context_clear_marks, context_enum_ptrs, context_reloc_ptrs, 0);
+
+/*
+ * Context list structure. Note that this uses context indices, not
+ * pointers, to avoid having to worry about pointers between local VMs.
+ */
+typedef struct ctx_list_s {
+ ctx_index_t head_index;
+ ctx_index_t tail_index;
+} ctx_list_t;
+
+/* Condition structure */
+typedef struct gs_condition_s {
+ ctx_list_t waiting; /* contexts waiting on this condition */
+} gs_condition_t;
+gs_private_st_simple(st_condition, gs_condition_t, "conditiontype");
+
+/* Lock structure */
+typedef struct gs_lock_s {
+ ctx_list_t waiting; /* contexts waiting for this lock, */
+ /* must be first for subclassing */
+ ctx_index_t holder_index; /* context holding the lock, if any */
+ gs_scheduler_t *scheduler;
+} gs_lock_t;
+gs_private_st_ptrs1(st_lock, gs_lock_t, "locktype",
+ lock_enum_ptrs, lock_reloc_ptrs, scheduler);
+
+/* Global state */
+/*typedef struct gs_scheduler_s gs_scheduler_t; *//* (above) */
+struct gs_scheduler_s {
+ gs_context_t *current;
+ long usertime_initial; /* usertime when current started running */
+ ctx_list_t active;
+ vm_reclaim_proc((*save_vm_reclaim));
+ ctx_index_t dead_index;
+#define CTX_TABLE_SIZE 19
+ gs_context_t *table[CTX_TABLE_SIZE];
+};
+
+/* Convert a context index to a context pointer. */
+static gs_context_t *
+index_context(const gs_scheduler_t *psched, long index)
+{
+ gs_context_t *pctx;
+
+ if (index == 0)
+ return 0;
+ pctx = psched->table[index % CTX_TABLE_SIZE];
+ while (pctx != 0 && pctx->index != index)
+ pctx = pctx->table_next;
+ return pctx;
+}
+
+/* Structure definition */
+gs_private_st_composite(st_scheduler, gs_scheduler_t, "gs_scheduler",
+ scheduler_enum_ptrs, scheduler_reloc_ptrs);
+/*
+ * The only cross-local-VM pointers in the context machinery are the
+ * table_next pointers in contexts, and the current and table[] pointers
+ * in the scheduler. We need to handle all of these specially.
+ */
+static ENUM_PTRS_WITH(scheduler_enum_ptrs, gs_scheduler_t *psched)
+{
+ index -= 1;
+ if (index < CTX_TABLE_SIZE) {
+ gs_context_t *pctx = psched->table[index];
+
+ while (pctx && !pctx->visible)
+ pctx = pctx->table_next;
+ return ENUM_OBJ(pctx);
+ }
+ return 0;
+}
+case 0: return ENUM_OBJ(visible_context(psched->current));
+ENUM_PTRS_END
+static RELOC_PTRS_WITH(scheduler_reloc_ptrs, gs_scheduler_t *psched)
+{
+ if (psched->current->visible)
+ RELOC_VAR(psched->current);
+ {
+ int i;
+
+ for (i = 0; i < CTX_TABLE_SIZE; ++i) {
+ gs_context_t **ppctx = &psched->table[i];
+ gs_context_t **pnext;
+
+ for (; *ppctx; ppctx = pnext) {
+ pnext = &(*ppctx)->table_next;
+ if ((*ppctx)->visible)
+ RELOC_VAR(*ppctx);
+ }
+ }
+ }
+}
+RELOC_PTRS_END
+
+/*
+ * The context scheduler requires special handling during garbage
+ * collection, since it is the only structure that can legitimately
+ * reference objects in multiple local VMs. To deal with this, we wrap the
+ * interpreter's garbage collector with code that prevents it from seeing
+ * contexts in other than the current local VM. ****** WORKS FOR LOCAL GC,
+ * NOT FOR GLOBAL ******
+ */
+static void
+context_reclaim(vm_spaces * pspaces, bool global)
+{
+ /*
+ * Search through the registered roots to find the current context.
+ * (This is a hack so we can find the scheduler.)
+ */
+ int i;
+ gs_context_t *pctx = 0; /* = 0 is bogus to pacify compilers */
+ gs_scheduler_t *psched = 0;
+ gs_ref_memory_t *lmem = 0; /* = 0 is bogus to pacify compilers */
+ chunk_locator_t loc;
+
+ for (i = countof(pspaces->memories.indexed) - 1; psched == 0 && i > 0; --i) {
+ gs_ref_memory_t *mem = pspaces->memories.indexed[i];
+ const gs_gc_root_t *root = mem->roots;
+
+ for (; root; root = root->next) {
+ if (gs_object_type((gs_memory_t *)mem, *root->p) == &st_context) {
+ pctx = *root->p;
+ psched = pctx->scheduler;
+ lmem = mem;
+ break;
+ }
+ }
+ }
+
+ /* Hide all contexts in other (local) VMs. */
+ /*
+ * See context_create below for why we look for the context
+ * in stable memory.
+ */
+ loc.memory = (gs_ref_memory_t *)gs_memory_stable((gs_memory_t *)lmem);
+ loc.cp = 0;
+ for (i = 0; i < CTX_TABLE_SIZE; ++i)
+ for (pctx = psched->table[i]; pctx; pctx = pctx->table_next)
+ pctx->visible = chunk_locate_ptr(pctx, &loc);
+
+#ifdef DEBUG
+ if (!psched->current->visible) {
+ lprintf("Current context is invisible!\n");
+ gs_abort((gs_memory_t *)lmem);
+ }
+#endif
+
+ /* Do the actual garbage collection. */
+ psched->save_vm_reclaim(pspaces, global);
+
+ /* Make all contexts visible again. */
+ for (i = 0; i < CTX_TABLE_SIZE; ++i)
+ for (pctx = psched->table[i]; pctx; pctx = pctx->table_next)
+ pctx->visible = true;
+}
+
+
+/* Forward references */
+static int context_create(gs_scheduler_t *, gs_context_t **,
+ const gs_dual_memory_t *,
+ const gs_context_state_t *, bool);
+static long context_usertime(void);
+static int context_param(const gs_scheduler_t *, os_ptr, gs_context_t **);
+static void context_destroy(gs_context_t *);
+static void stack_copy(ref_stack_t *, const ref_stack_t *, uint, uint);
+static int lock_acquire(os_ptr, gs_context_t *);
+static int lock_release(ref *);
+
+/* Internal procedures */
+static void
+context_load(gs_scheduler_t *psched, gs_context_t *pctx)
+{
+ if_debug1('"', "[\"]loading %ld\n", pctx->index);
+ if ( pctx->state.keep_usertime )
+ psched->usertime_initial = context_usertime();
+ context_state_load(&pctx->state);
+}
+static void
+context_store(gs_scheduler_t *psched, gs_context_t *pctx)
+{
+ if_debug1('"', "[\"]storing %ld\n", pctx->index);
+ context_state_store(&pctx->state);
+ if ( pctx->state.keep_usertime )
+ pctx->state.usertime_total +=
+ context_usertime() - psched->usertime_initial;
+}
+
+/* List manipulation */
+static void
+add_last(const gs_scheduler_t *psched, ctx_list_t *pl, gs_context_t *pc)
+{
+ pc->next_index = 0;
+ if (pl->head_index == 0)
+ pl->head_index = pc->index;
+ else
+ index_context(psched, pl->tail_index)->next_index = pc->index;
+ pl->tail_index = pc->index;
+}
+
+/* ------ Initialization ------ */
+
+static int ctx_initialize(i_ctx_t **);
+static int ctx_reschedule(i_ctx_t **);
+static int ctx_time_slice(i_ctx_t **);
+static int
+zcontext_init(i_ctx_t *i_ctx_p)
+{
+ /* Complete initialization after the interpreter is entered. */
+ gs_interp_reschedule_proc = ctx_initialize;
+ gs_interp_time_slice_proc = ctx_initialize;
+ gs_interp_time_slice_ticks = 0;
+ return 0;
+}
+/*
+ * The interpreter calls this procedure at the first reschedule point.
+ * It completes context initialization.
+ */
+static int
+ctx_initialize(i_ctx_t **pi_ctx_p)
+{
+ i_ctx_t *i_ctx_p = *pi_ctx_p; /* for gs_imemory */
+ gs_ref_memory_t *imem = iimemory_system;
+ gs_scheduler_t *psched =
+ gs_alloc_struct_immovable((gs_memory_t *) imem, gs_scheduler_t,
+ &st_scheduler, "gs_scheduler");
+
+ psched->current = 0;
+ psched->active.head_index = psched->active.tail_index = 0;
+ psched->save_vm_reclaim = i_ctx_p->memory.spaces.vm_reclaim;
+ i_ctx_p->memory.spaces.vm_reclaim = context_reclaim;
+ psched->dead_index = 0;
+ memset(psched->table, 0, sizeof(psched->table));
+ /* Create an initial context. */
+ if (context_create(psched, &psched->current, &gs_imemory, *pi_ctx_p, true) < 0) {
+ lprintf("Can't create initial context!");
+ gs_abort(imemory);
+ }
+ psched->current->scheduler = psched;
+ /* Hook into the interpreter. */
+ *pi_ctx_p = &psched->current->state;
+ gs_interp_reschedule_proc = ctx_reschedule;
+ gs_interp_time_slice_proc = ctx_time_slice;
+ gs_interp_time_slice_ticks = reschedule_interval;
+ return 0;
+}
+
+/* ------ 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. */
+static int
+ctx_reschedule(i_ctx_t **pi_ctx_p)
+{
+ gs_context_t *current = (gs_context_t *)*pi_ctx_p;
+ gs_scheduler_t *psched = current->scheduler;
+
+#ifdef DEBUG
+ if (*pi_ctx_p != &current->state) {
+ lprintf2("current->state = 0x%lx, != i_ctx_p = 0x%lx!\n",
+ (ulong)&current->state, (ulong)*pi_ctx_p);
+ }
+#endif
+ /* If there are any dead contexts waiting to be released, */
+ /* take care of that now. */
+ while (psched->dead_index != 0) {
+ gs_context_t *dead = index_context(psched, psched->dead_index);
+ long next_index = dead->next_index;
+
+ if (current == dead) {
+ if_debug1('"', "[\"]storing dead %ld\n", current->index);
+ context_state_store(&current->state);
+ current = 0;
+ }
+ context_destroy(dead);
+ psched->dead_index = next_index;
+ }
+ /* Update saved_local_vm. See above for the invariant. */
+ if (current != 0)
+ current->saved_local_vm =
+ current->state.memory.space_local->saved != 0;
+ /* Run the first ready context, taking the 'save' lock into account. */
+ {
+ gs_context_t *prev = 0;
+ gs_context_t *ready;
+
+ for (ready = index_context(psched, psched->active.head_index);;
+ prev = ready, ready = index_context(psched, ready->next_index)
+ ) {
+ if (ready == 0) {
+ if (current != 0)
+ context_store(psched, current);
+ lprintf("No context to run!");
+ return_error(e_Fatal);
+ }
+ /* See above for an explanation of the following test. */
+ if (ready->state.memory.space_local->saved != 0 &&
+ !ready->saved_local_vm
+ )
+ continue;
+ /* Found a context to run. */
+ {
+ ctx_index_t next_index = ready->next_index;
+
+ if (prev)
+ prev->next_index = next_index;
+ else
+ psched->active.head_index = next_index;
+ if (!next_index)
+ psched->active.tail_index = (prev ? prev->index : 0);
+ }
+ break;
+ }
+ if (ready == current)
+ return 0; /* no switch */
+ /*
+ * Save the state of the current context in psched->current,
+ * if any context is current.
+ */
+ if (current != 0)
+ context_store(psched, current);
+ psched->current = ready;
+ /* Load the state of the new current context. */
+ context_load(psched, ready);
+ /* Switch the interpreter's context state pointer. */
+ *pi_ctx_p = &ready->state;
+ }
+ return 0;
+}
+
+/* If the interpreter wants to time-slice, it saves its state, */
+/* calls ctx_time_slice, and reloads its state. */
+static int
+ctx_time_slice(i_ctx_t **pi_ctx_p)
+{
+ gs_scheduler_t *psched = ((gs_context_t *)*pi_ctx_p)->scheduler;
+
+ if (psched->active.head_index == 0)
+ return 0;
+ if_debug0('"', "[\"]time-slice\n");
+ add_last(psched, &psched->active, psched->current);
+ return ctx_reschedule(pi_ctx_p);
+}
+
+/* ------ Context operators ------ */
+
+/* - currentcontext <context> */
+static int
+zcurrentcontext(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ const gs_context_t *current = (const gs_context_t *)i_ctx_p;
+
+ push(1);
+ make_int(op, current->index);
+ return 0;
+}
+
+/* <context> detach - */
+static int
+zdetach(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ const gs_scheduler_t *psched = ((gs_context_t *)i_ctx_p)->scheduler;
+ gs_context_t *pctx;
+ int code;
+
+ if ((code = context_param(psched, op, &pctx)) < 0)
+ return code;
+ if_debug2('\'', "[']detach %ld, status = %d\n",
+ pctx->index, pctx->status);
+ if (pctx->joiner_index != 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;
+}
+
+static int
+ do_fork(i_ctx_t *i_ctx_p, os_ptr op, const ref * pstdin,
+ const ref * pstdout, uint mcount, bool local),
+ values_older_than(const ref_stack_t * pstack, uint first, uint last,
+ int max_space);
+static int
+ fork_done(i_ctx_t *),
+ fork_done_with_error(i_ctx_t *),
+ finish_join(i_ctx_t *),
+ reschedule_now(i_ctx_t *);
+
+/* <mark> <obj1> ... <objN> <proc> .fork <context> */
+/* <mark> <obj1> ... <objN> <proc> <stdin|null> <stdout|null> */
+/* .localfork <context> */
+static int
+zfork(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ uint mcount = ref_stack_counttomark(&o_stack);
+ ref rnull;
+
+ if (mcount == 0)
+ return_error(e_unmatchedmark);
+ make_null(&rnull);
+ return do_fork(i_ctx_p, op, &rnull, &rnull, mcount, false);
+}
+static int
+zlocalfork(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ uint mcount = ref_stack_counttomark(&o_stack);
+ int code;
+
+ if (mcount == 0)
+ return_error(e_unmatchedmark);
+ code = values_older_than(&o_stack, 1, mcount - 1, avm_local);
+ if (code < 0)
+ return code;
+ code = do_fork(i_ctx_p, op - 2, op - 1, op, mcount - 2, true);
+ if (code < 0)
+ return code;
+ op = osp;
+ op[-2] = *op;
+ pop(2);
+ return code;
+}
+
+/* Internal procedure to actually do the fork operation. */
+static int
+do_fork(i_ctx_t *i_ctx_p, os_ptr op, const ref * pstdin, const ref * pstdout,
+ uint mcount, bool local)
+{
+ gs_context_t *pcur = (gs_context_t *)i_ctx_p;
+ gs_scheduler_t *psched = pcur->scheduler;
+ stream *s;
+ gs_dual_memory_t dmem;
+ gs_context_t *pctx;
+ ref old_userdict, new_userdict;
+ int code;
+
+ check_proc(*op);
+ if (iimemory_local->save_level)
+ return_error(e_invalidcontext);
+ if (r_has_type(pstdout, t_null)) {
+ code = zget_stdout(i_ctx_p, &s);
+ if (code < 0)
+ return code;
+ pstdout = &ref_stdio[1];
+ } else
+ check_read_file(s, pstdout);
+ if (r_has_type(pstdin, t_null)) {
+ code = zget_stdin(i_ctx_p, &s);
+ if (code < 0)
+ return code;
+ pstdin = &ref_stdio[0];
+ } else
+ check_read_file(s, pstdin);
+ dmem = gs_imemory;
+ if (local) {
+ /* Share global VM, private local VM. */
+ ref *puserdict;
+ uint userdict_size;
+ gs_memory_t *parent = iimemory_local->non_gc_memory;
+ gs_ref_memory_t *lmem;
+ gs_ref_memory_t *lmem_stable;
+
+ if (dict_find_string(systemdict, "userdict", &puserdict) <= 0 ||
+ !r_has_type(puserdict, t_dictionary)
+ )
+ return_error(e_Fatal);
+ old_userdict = *puserdict;
+ userdict_size = dict_maxlength(&old_userdict);
+ lmem = ialloc_alloc_state(parent, iimemory_local->chunk_size);
+ lmem_stable = ialloc_alloc_state(parent, iimemory_local->chunk_size);
+ if (lmem == 0 || lmem_stable == 0) {
+ gs_free_object(parent, lmem_stable, "do_fork");
+ gs_free_object(parent, lmem, "do_fork");
+ return_error(e_VMerror);
+ }
+ lmem->space = avm_local;
+ lmem_stable->space = avm_local;
+ lmem->stable_memory = (gs_memory_t *)lmem_stable;
+ dmem.space_local = lmem;
+ code = context_create(psched, &pctx, &dmem, &pcur->state, false);
+ if (code < 0) {
+ /****** FREE lmem ******/
+ return code;
+ }
+ /*
+ * Create a new userdict. PostScript code will take care of
+ * the rest of the initialization of the new context.
+ */
+ code = dict_alloc(lmem, userdict_size, &new_userdict);
+ if (code < 0) {
+ context_destroy(pctx);
+ /****** FREE lmem ******/
+ return code;
+ }
+ } else {
+ /* Share global and local VM. */
+ code = context_create(psched, &pctx, &dmem, &pcur->state, false);
+ if (code < 0) {
+ /****** FREE lmem ******/
+ return code;
+ }
+ /*
+ * Copy the gstate stack. The current method is not elegant;
+ * in fact, I'm not entirely sure it works.
+ */
+ {
+ int n;
+ const gs_state *old;
+ gs_state *new;
+
+ for (n = 0, old = igs; old != 0; old = gs_state_saved(old))
+ ++n;
+ for (old = pctx->state.pgs; old != 0; old = gs_state_saved(old))
+ --n;
+ for (; n > 0 && code >= 0; --n)
+ code = gs_gsave(pctx->state.pgs);
+ if (code < 0) {
+/****** FREE lmem & GSTATES ******/
+ return code;
+ }
+ for (old = igs, new = pctx->state.pgs;
+ old != 0 /* (== new != 0) */ && code >= 0;
+ old = gs_state_saved(old), new = gs_state_saved(new)
+ )
+ code = gs_setgstate(new, old);
+ if (code < 0) {
+/****** FREE lmem & GSTATES ******/
+ return code;
+ }
+ }
+ }
+ pctx->state.language_level = i_ctx_p->language_level;
+ pctx->state.dict_stack.min_size = idict_stack.min_size;
+ pctx->state.dict_stack.userdict_index = idict_stack.userdict_index;
+ pctx->state.stdio[0] = *pstdin;
+ pctx->state.stdio[1] = *pstdout;
+ pctx->state.stdio[2] = pcur->state.stdio[2];
+ /* Initialize the interpreter stacks. */
+ {
+ ref_stack_t *dstack = (ref_stack_t *)&pctx->state.dict_stack;
+ uint count = ref_stack_count(&d_stack);
+ uint copy = (local ? min_dstack_size : count);
+
+ ref_stack_push(dstack, copy);
+ stack_copy(dstack, &d_stack, copy, count - copy);
+ if (local) {
+ /* Substitute the new userdict for the old one. */
+ long i;
+
+ for (i = 0; i < copy; ++i) {
+ ref *pdref = ref_stack_index(dstack, i);
+
+ if (obj_eq(imemory, pdref, &old_userdict))
+ *pdref = new_userdict;
+ }
+ }
+ }
+ {
+ ref_stack_t *estack = (ref_stack_t *)&pctx->state.exec_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_with_error);
+ make_oper(estack->p - 1, 0, fork_done);
+ *estack->p = *op;
+ }
+ {
+ ref_stack_t *ostack = (ref_stack_t *)&pctx->state.op_stack;
+ uint count = mcount - 2;
+
+ ref_stack_push(ostack, count);
+ stack_copy(ostack, &o_stack, count, osp - op + 1);
+ }
+ pctx->state.binary_object_format = pcur->state.binary_object_format;
+ add_last(psched, &psched->active, pctx);
+ pop(mcount - 1);
+ op = osp;
+ make_int(op, pctx->index);
+ return 0;
+}
+
+/*
+ * Check that all values being passed by fork or join are old enough
+ * to be valid in the environment to which they are being transferred.
+ */
+static int
+values_older_than(const ref_stack_t * pstack, uint first, uint last,
+ int next_space)
+{
+ uint i;
+
+ for (i = first; i <= last; ++i)
+ if (r_space(ref_stack_index(pstack, (long)i)) >= next_space)
+ return_error(e_invalidaccess);
+ return 0;
+}
+
+/* This gets executed when a context terminates normally. */
+/****** MUST DO ALL RESTORES ******/
+/****** WHAT IF invalidrestore? ******/
+static int
+fork_done(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ gs_context_t *pcur = (gs_context_t *)i_ctx_p;
+ gs_scheduler_t *psched = pcur->scheduler;
+
+ if_debug2('\'', "[']done %ld%s\n", pcur->index,
+ (pcur->detach ? ", detached" : ""));
+ /*
+ * Clear the context's dictionary, execution and graphics stacks
+ * now, to retain as little as possible in case of a garbage
+ * collection or restore. We know that fork_done is the
+ * next-to-bottom entry on the execution stack.
+ */
+ ref_stack_pop_to(&d_stack, min_dstack_size);
+ pop_estack(&pcur->state, ref_stack_count(&e_stack) - 1);
+ gs_grestoreall(igs);
+ /*
+ * If there are any unmatched saves, we need to execute restores
+ * until there aren't. An invalidrestore is possible and will
+ * result in an error termination.
+ */
+ if (iimemory_local->save_level) {
+ ref *prestore;
+
+ if (dict_find_string(systemdict, "restore", &prestore) <= 0) {
+ lprintf("restore not found in systemdict!");
+ return_error(e_Fatal);
+ }
+ if (pcur->detach) {
+ ref_stack_clear(&o_stack); /* help avoid invalidrestore */
+ op = osp;
+ }
+ push(1);
+ make_tv(op, t_save, saveid, alloc_save_current_id(&gs_imemory));
+ push_op_estack(fork_done);
+ ++esp;
+ ref_assign(esp, prestore);
+ return o_push_estack;
+ }
+ if (pcur->detach) {
+ /*
+ * We would like to free the context's memory, but we can't do
+ * it yet, because the interpreter still has references to it.
+ * Instead, queue the context to be freed the next time we
+ * reschedule. We can, however, clear its operand stack now.
+ */
+ ref_stack_clear(&o_stack);
+ context_store(psched, pcur);
+ pcur->next_index = psched->dead_index;
+ psched->dead_index = pcur->index;
+ psched->current = 0;
+ } else {
+ gs_context_t *pctx = index_context(psched, pcur->joiner_index);
+
+ pcur->status = cs_done;
+ /* Schedule the context waiting to join this one, if any. */
+ if (pctx != 0)
+ add_last(psched, &psched->active, pctx);
+ }
+ return o_reschedule;
+}
+/*
+ * This gets executed when the stack is being unwound for an error
+ * termination.
+ */
+static int
+fork_done_with_error(i_ctx_t *i_ctx_p)
+{
+/****** WHAT TO DO? ******/
+ return fork_done(i_ctx_p);
+}
+
+/* <context> join <mark> <obj1> ... <objN> */
+static int
+zjoin(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ gs_context_t *current = (gs_context_t *)i_ctx_p;
+ gs_scheduler_t *psched = current->scheduler;
+ gs_context_t *pctx;
+ int code;
+
+ if ((code = context_param(psched, op, &pctx)) < 0)
+ return code;
+ if_debug2('\'', "[']join %ld, status = %d\n",
+ pctx->index, pctx->status);
+ /*
+ * It doesn't seem logically necessary, but the Red Book says that
+ * the context being joined must share both global and local VM with
+ * the current context.
+ */
+ if (pctx->joiner_index != 0 || pctx->detach || pctx == current ||
+ pctx->state.memory.space_global !=
+ current->state.memory.space_global ||
+ pctx->state.memory.space_local !=
+ current->state.memory.space_local ||
+ iimemory_local->save_level != 0
+ )
+ return_error(e_invalidcontext);
+ switch (pctx->status) {
+ case cs_active:
+ /*
+ * We need to re-execute the join after the joined
+ * context is done. Since we can't return both
+ * o_push_estack and o_reschedule, we push a call on
+ * reschedule_now, which accomplishes the latter.
+ */
+ check_estack(2);
+ push_op_estack(finish_join);
+ push_op_estack(reschedule_now);
+ pctx->joiner_index = current->index;
+ return o_push_estack;
+ case cs_done:
+ {
+ const ref_stack_t *ostack =
+ (ref_stack_t *)&pctx->state.op_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;
+}
+
+/* Finish a deferred join. */
+static int
+finish_join(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ gs_context_t *current = (gs_context_t *)i_ctx_p;
+ gs_scheduler_t *psched = current->scheduler;
+ gs_context_t *pctx;
+ int code;
+
+ if ((code = context_param(psched, op, &pctx)) < 0)
+ return code;
+ if_debug2('\'', "[']finish_join %ld, status = %d\n",
+ pctx->index, pctx->status);
+ if (pctx->joiner_index != current->index)
+ return_error(e_invalidcontext);
+ pctx->joiner_index = 0;
+ return zjoin(i_ctx_p);
+}
+
+/* Reschedule now. */
+static int
+reschedule_now(i_ctx_t *i_ctx_p)
+{
+ return o_reschedule;
+}
+
+/* - yield - */
+static int
+zyield(i_ctx_t *i_ctx_p)
+{
+ gs_context_t *current = (gs_context_t *)i_ctx_p;
+ gs_scheduler_t *psched = current->scheduler;
+
+ if (psched->active.head_index == 0)
+ return 0;
+ if_debug0('"', "[\"]yield\n");
+ add_last(psched, &psched->active, current);
+ return o_reschedule;
+}
+
+/* ------ Condition and lock operators ------ */
+
+static int
+ monitor_cleanup(i_ctx_t *),
+ monitor_release(i_ctx_t *),
+ await_lock(i_ctx_t *);
+static void
+ activate_waiting(gs_scheduler_t *, ctx_list_t * pcl);
+
+/* - condition <condition> */
+static int
+zcondition(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ gs_condition_t *pcond =
+ ialloc_struct(gs_condition_t, &st_condition, "zcondition");
+
+ if (pcond == 0)
+ return_error(e_VMerror);
+ pcond->waiting.head_index = pcond->waiting.tail_index = 0;
+ push(1);
+ make_istruct(op, a_all, pcond);
+ return 0;
+}
+
+/* - lock <lock> */
+static int
+zlock(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ gs_lock_t *plock = ialloc_struct(gs_lock_t, &st_lock, "zlock");
+
+ if (plock == 0)
+ return_error(e_VMerror);
+ plock->holder_index = 0;
+ plock->waiting.head_index = plock->waiting.tail_index = 0;
+ push(1);
+ make_istruct(op, a_all, plock);
+ return 0;
+}
+
+/* <lock> <proc> monitor - */
+static int
+zmonitor(i_ctx_t *i_ctx_p)
+{
+ gs_context_t *current = (gs_context_t *)i_ctx_p;
+ os_ptr op = osp;
+ gs_lock_t *plock;
+ gs_context_t *pctx;
+ int code;
+
+ check_stype(op[-1], st_lock);
+ check_proc(*op);
+ plock = r_ptr(op - 1, gs_lock_t);
+ pctx = index_context(current->scheduler, plock->holder_index);
+ if_debug1('\'', "[']monitor 0x%lx\n", (ulong) plock);
+ if (pctx != 0) {
+ if (pctx == current ||
+ (iimemory_local->save_level != 0 &&
+ pctx->state.memory.space_local ==
+ 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, current);
+ 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 o_push_estack;
+}
+/* Release the monitor lock when unwinding for an error or exit. */
+static int
+monitor_cleanup(i_ctx_t *i_ctx_p)
+{
+ int code = lock_release(esp);
+
+ if (code < 0)
+ return code;
+ --esp;
+ return o_pop_estack;
+}
+/* Release the monitor lock when the procedure completes. */
+static int
+monitor_release(i_ctx_t *i_ctx_p)
+{
+ int code = lock_release(esp - 1);
+
+ if (code < 0)
+ return code;
+ esp -= 2;
+ return o_pop_estack;
+}
+
+/* <condition> notify - */
+static int
+znotify(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ gs_context_t *current = (gs_context_t *)i_ctx_p;
+ gs_condition_t *pcond;
+
+ check_stype(*op, st_condition);
+ pcond = r_ptr(op, gs_condition_t);
+ if_debug1('"', "[\"]notify 0x%lx\n", (ulong) pcond);
+ pop(1);
+ op--;
+ if (pcond->waiting.head_index == 0) /* nothing to do */
+ return 0;
+ activate_waiting(current->scheduler, &pcond->waiting);
+ return zyield(i_ctx_p);
+}
+
+/* <lock> <condition> wait - */
+static int
+zwait(i_ctx_t *i_ctx_p)
+{
+ os_ptr op = osp;
+ gs_context_t *current = (gs_context_t *)i_ctx_p;
+ gs_scheduler_t *psched = current->scheduler;
+ gs_lock_t *plock;
+ gs_context_t *pctx;
+ gs_condition_t *pcond;
+
+ check_stype(op[-1], st_lock);
+ plock = r_ptr(op - 1, gs_lock_t);
+ check_stype(*op, st_condition);
+ pcond = r_ptr(op, gs_condition_t);
+ if_debug2('"', "[\"]wait lock 0x%lx, condition 0x%lx\n",
+ (ulong) plock, (ulong) pcond);
+ pctx = index_context(psched, plock->holder_index);
+ if (pctx == 0 || pctx != psched->current ||
+ (iimemory_local->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(psched, &pcond->waiting, pctx);
+ push_op_estack(await_lock);
+ return o_reschedule;
+}
+/* When the condition is signaled, wait for acquiring the lock. */
+static int
+await_lock(i_ctx_t *i_ctx_p)
+{
+ gs_context_t *current = (gs_context_t *)i_ctx_p;
+ os_ptr op = osp;
+ int code = lock_acquire(op - 1, current);
+
+ if (code == 0) {
+ pop(2);
+ return 0;
+ }
+ /* We didn't acquire the lock. Re-execute the wait. */
+ push_op_estack(await_lock);
+ return code; /* o_reschedule */
+}
+
+/* Activate a list of waiting contexts, and reset the list. */
+static void
+activate_waiting(gs_scheduler_t *psched, ctx_list_t * pcl)
+{
+ gs_context_t *pctx = index_context(psched, pcl->head_index);
+ gs_context_t *next;
+
+ for (; pctx != 0; pctx = next) {
+ next = index_context(psched, pctx->next_index);
+ add_last(psched, &psched->active, pctx);
+ }
+ pcl->head_index = pcl->tail_index = 0;
+}
+
+/* ------ Miscellaneous operators ------ */
+
+/* - usertime <int> */
+static int
+zusertime_context(i_ctx_t *i_ctx_p)
+{
+ gs_context_t *current = (gs_context_t *)i_ctx_p;
+ gs_scheduler_t *psched = current->scheduler;
+ os_ptr op = osp;
+ long utime = context_usertime();
+
+ push(1);
+ if (!current->state.keep_usertime) {
+ /*
+ * This is the first time this context has executed usertime:
+ * we must track its execution time from now on.
+ */
+ psched->usertime_initial = utime;
+ current->state.keep_usertime = true;
+ }
+ make_int(op, current->state.usertime_total + utime -
+ psched->usertime_initial);
+ return 0;
+}
+
+/* ------ Internal procedures ------ */
+
+/* Create a context. */
+static int
+context_create(gs_scheduler_t * psched, gs_context_t ** ppctx,
+ const gs_dual_memory_t * dmem,
+ const gs_context_state_t *i_ctx_p, bool copy_state)
+{
+ /*
+ * Contexts are always created at the outermost save level, so they do
+ * not need to be allocated in stable memory for the sake of
+ * save/restore. However, context_reclaim needs to be able to test
+ * whether a given context belongs to a given local VM, and allocating
+ * contexts in stable local VM avoids the need to scan multiple save
+ * levels when making this test.
+ */
+ gs_memory_t *mem = gs_memory_stable((gs_memory_t *)dmem->space_local);
+ gs_context_t *pctx;
+ int code;
+ long ctx_index;
+ gs_context_t **pte;
+
+ pctx = gs_alloc_struct(mem, gs_context_t, &st_context, "context_create");
+ if (pctx == 0)
+ return_error(e_VMerror);
+ if (copy_state) {
+ pctx->state = *i_ctx_p;
+ } else {
+ gs_context_state_t *pctx_st = &pctx->state;
+
+ code = context_state_alloc(&pctx_st, systemdict, dmem);
+ if (code < 0) {
+ gs_free_object(mem, pctx, "context_create");
+ return code;
+ }
+ }
+ ctx_index = gs_next_ids(mem, 1);
+ pctx->scheduler = psched;
+ pctx->status = cs_active;
+ pctx->index = ctx_index;
+ pctx->detach = false;
+ pctx->saved_local_vm = false;
+ pctx->visible = true;
+ pctx->next_index = 0;
+ pctx->joiner_index = 0;
+ pte = &psched->table[ctx_index % CTX_TABLE_SIZE];
+ pctx->table_next = *pte;
+ *pte = pctx;
+ *ppctx = pctx;
+ if (gs_debug_c('\'') | gs_debug_c('"'))
+ dlprintf2("[']create %ld at 0x%lx\n", ctx_index, (ulong) pctx);
+ return 0;
+}
+
+/* Check a context ID. Note that we do not check for context validity. */
+static int
+context_param(const gs_scheduler_t * psched, os_ptr op, gs_context_t ** ppctx)
+{
+ gs_context_t *pctx;
+
+ check_type(*op, t_integer);
+ pctx = index_context(psched, op->value.intval);
+ if (pctx == 0)
+ return_error(e_invalidcontext);
+ *ppctx = pctx;
+ return 0;
+}
+
+/* Read the usertime as a single value. */
+static 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. */
+static void
+context_destroy(gs_context_t * pctx)
+{
+ gs_ref_memory_t *mem = pctx->state.memory.space_local;
+ gs_scheduler_t *psched = pctx->scheduler;
+ gs_context_t **ppctx = &psched->table[pctx->index % CTX_TABLE_SIZE];
+
+ while (*ppctx != pctx)
+ ppctx = &(*ppctx)->table_next;
+ *ppctx = (*ppctx)->table_next;
+ if (gs_debug_c('\'') | gs_debug_c('"'))
+ dlprintf3("[']destroy %ld at 0x%lx, status = %d\n",
+ pctx->index, (ulong) pctx, pctx->status);
+ if (!context_state_free(&pctx->state))
+ gs_free_object((gs_memory_t *) mem, 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. */
+static void
+stack_copy(ref_stack_t * to, const ref_stack_t * 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. */
+static int
+lock_acquire(os_ptr op, gs_context_t * pctx)
+{
+ gs_lock_t *plock = r_ptr(op, gs_lock_t);
+
+ if (plock->holder_index == 0) {
+ plock->holder_index = pctx->index;
+ plock->scheduler = pctx->scheduler;
+ return 0;
+ }
+ add_last(pctx->scheduler, &plock->waiting, pctx);
+ return o_reschedule;
+}
+
+/* Release a lock. Return 0 if OK, e_invalidcontext if not. */
+static int
+lock_release(ref * op)
+{
+ gs_lock_t *plock = r_ptr(op, gs_lock_t);
+ gs_scheduler_t *psched = plock->scheduler;
+ gs_context_t *pctx = index_context(psched, plock->holder_index);
+
+ if (pctx != 0 && pctx == psched->current) {
+ plock->holder_index = 0;
+ activate_waiting(psched, &plock->waiting);
+ return 0;
+ }
+ return_error(e_invalidcontext);
+}
+
+/* ------ Initialization procedure ------ */
+
+/* We need to split the table because of the 16-element limit. */
+const op_def zcontext1_op_defs[] = {
+ {"0condition", zcondition},
+ {"0currentcontext", zcurrentcontext},
+ {"1detach", zdetach},
+ {"2.fork", zfork},
+ {"1join", zjoin},
+ {"4.localfork", zlocalfork},
+ {"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 */
+ op_def_end(0)
+};
+const op_def zcontext2_op_defs[] = {
+ /* Internal operators */
+ {"0%fork_done", fork_done},
+ {"1%finish_join", finish_join},
+ {"0%monitor_cleanup", monitor_cleanup},
+ {"0%monitor_release", monitor_release},
+ {"2%await_lock", await_lock},
+ op_def_end(zcontext_init)
+};