text: /*
   *  Thread stack (mainly call stack) primitives: allocation of activations,
   *  unwinding catchers and activations, etc.
   *
   *  Value stack handling is a part of the API implementation.
   */
  
  #include "duk_internal.h"
  
  /* Unwind the topmost catcher of the current activation (caller must check that
   * both exist) without side effects.
   */
  DUK_INTERNAL void duk_hthread_catcher_unwind_norz(duk_hthread *thr, duk_activation *act) {
  	duk_catcher *cat;
  
  	DUK_ASSERT(thr != NULL);
  	DUK_ASSERT(act != NULL);
  	DUK_ASSERT(act->cat != NULL); /* caller must check */
  	cat = act->cat;
  	DUK_ASSERT(cat != NULL);
  
  	DUK_DDD(DUK_DDDPRINT("unwinding catch stack entry %p (lexenv check is done)", (void *) cat));
  
  	if (DUK_CAT_HAS_LEXENV_ACTIVE(cat)) {
  		duk_hobject *env;
  
  		env = act->lex_env; /* current lex_env of the activation (created for catcher) */
  		DUK_ASSERT(env != NULL); /* must be, since env was created when catcher was created */
  		act->lex_env = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, env); /* prototype is lex_env before catcher created */
  		DUK_HOBJECT_INCREF(thr, act->lex_env);
  		DUK_HOBJECT_DECREF_NORZ(thr, env);
  
  		/* There is no need to decref anything else than 'env': if 'env'
  		 * becomes unreachable, refzero will handle decref'ing its prototype.
  		 */
  	}
  
  	act->cat = cat->parent;
  	duk_hthread_catcher_free(thr, cat);
  }
  
  /* Same as above, but caller is certain no catcher-related lexenv may exist. */
  DUK_INTERNAL void duk_hthread_catcher_unwind_nolexenv_norz(duk_hthread *thr, duk_activation *act) {
  	duk_catcher *cat;
  
  	DUK_ASSERT(thr != NULL);
  	DUK_ASSERT(act != NULL);
  	DUK_ASSERT(act->cat != NULL); /* caller must check */
  	cat = act->cat;
  	DUK_ASSERT(cat != NULL);
  
  	DUK_DDD(DUK_DDDPRINT("unwinding catch stack entry %p (lexenv check is not done)", (void *) cat));
  
  	DUK_ASSERT(!DUK_CAT_HAS_LEXENV_ACTIVE(cat));
  
  	act->cat = cat->parent;
  	duk_hthread_catcher_free(thr, cat);
  }
  
  DUK_LOCAL
  #if defined(DUK_USE_CACHE_CATCHER)
  DUK_NOINLINE
  #endif
  duk_catcher *duk__hthread_catcher_alloc_slow(duk_hthread *thr) {
  	duk_catcher *cat;
  
  	cat = (duk_catcher *) DUK_ALLOC_CHECKED(thr, sizeof(duk_catcher));
  	DUK_ASSERT(cat != NULL);
  	return cat;
  }
  
  #if defined(DUK_USE_CACHE_CATCHER)
  DUK_INTERNAL DUK_INLINE duk_catcher *duk_hthread_catcher_alloc(duk_hthread *thr) {
  	duk_catcher *cat;
  
  	DUK_ASSERT(thr != NULL);
  
  	cat = thr->heap->catcher_free;
  	if (DUK_LIKELY(cat != NULL)) {
  		thr->heap->catcher_free = cat->parent;
  		return cat;
  	}
  
  	return duk__hthread_catcher_alloc_slow(thr);
  }
  #else /* DUK_USE_CACHE_CATCHER */
  DUK_INTERNAL duk_catcher *duk_hthread_catcher_alloc(duk_hthread *thr) {
  	return duk__hthread_catcher_alloc_slow(thr);
  }
  #endif /* DUK_USE_CACHE_CATCHER */
  
  DUK_INTERNAL void duk_hthread_catcher_free(duk_hthread *thr, duk_catcher *cat) {
  	DUK_ASSERT(thr != NULL);
  	DUK_ASSERT(cat != NULL);
  
  #if defined(DUK_USE_CACHE_CATCHER)
  	/* Unconditional caching for now; freed in mark-and-sweep. */
  	cat->parent = thr->heap->catcher_free;
  	thr->heap->catcher_free = cat;
  #else
  	DUK_FREE_CHECKED(thr, (void *) cat);
  #endif
  }
  
  DUK_LOCAL
  #if defined(DUK_USE_CACHE_ACTIVATION)
  DUK_NOINLINE
  #endif
  duk_activation *duk__hthread_activation_alloc_slow(duk_hthread *thr) {
  	duk_activation *act;
  
  	act = (duk_activation *) DUK_ALLOC_CHECKED(thr, sizeof(duk_activation));
  	DUK_ASSERT(act != NULL);
  	return act;
  }
  
  #if defined(DUK_USE_CACHE_ACTIVATION)
  DUK_INTERNAL DUK_INLINE duk_activation *duk_hthread_activation_alloc(duk_hthread *thr) {
  	duk_activation *act;
  
  	DUK_ASSERT(thr != NULL);
  
  	act = thr->heap->activation_free;
  	if (DUK_LIKELY(act != NULL)) {
  		thr->heap->activation_free = act->parent;
  		return act;
  	}
  
  	return duk__hthread_activation_alloc_slow(thr);
  }
  #else /* DUK_USE_CACHE_ACTIVATION */
  DUK_INTERNAL duk_activation *duk_hthread_activation_alloc(duk_hthread *thr) {
  	return duk__hthread_activation_alloc_slow(thr);
  }
  #endif /* DUK_USE_CACHE_ACTIVATION */
  
  DUK_INTERNAL void duk_hthread_activation_free(duk_hthread *thr, duk_activation *act) {
  	DUK_ASSERT(thr != NULL);
  	DUK_ASSERT(act != NULL);
  
  #if defined(DUK_USE_CACHE_ACTIVATION)
  	/* Unconditional caching for now; freed in mark-and-sweep. */
  	act->parent = thr->heap->activation_free;
  	thr->heap->activation_free = act;
  #else
  	DUK_FREE_CHECKED(thr, (void *) act);
  #endif
  }
  
  /* Internal helper: process the unwind for the topmost activation of a thread,
   * but leave the duk_activation in place for possible tailcall reuse.
   */
  DUK_LOCAL void duk__activation_unwind_nofree_norz(duk_hthread *thr) {
  #if defined(DUK_USE_DEBUGGER_SUPPORT)
  	duk_heap *heap;
  #endif
  	duk_activation *act;
  	duk_hobject *func;
  	duk_hobject *tmp;
  
  	DUK_ASSERT(thr != NULL);
  	DUK_ASSERT(thr->callstack_curr != NULL); /* caller must check */
  	DUK_ASSERT(thr->callstack_top > 0);
  	act = thr->callstack_curr;
  	DUK_ASSERT(act != NULL);
  	/* With lightfuncs, act 'func' may be NULL. */
  
  	/* With duk_activation records allocated separately, 'act' is a stable
  	 * pointer and not affected by side effects.
  	 */
  
  #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
  	/*
  	 *  Restore 'caller' property for non-strict callee functions.
  	 */
  
  	func = DUK_ACT_GET_FUNC(act);
  	if (func != NULL && !DUK_HOBJECT_HAS_STRICT(func)) {
  		duk_tval *tv_caller;
  		duk_tval tv_tmp;
  		duk_hobject *h_tmp;
  
  		tv_caller = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, func, DUK_STRIDX_CALLER);
  
  		/* The act->prev_caller should only be set if the entry for 'caller'
  		 * exists (as it is only set in that case, and the property is not
  		 * configurable), but handle all the cases anyway.
  		 */
  
  		if (tv_caller) {
  			DUK_TVAL_SET_TVAL(&tv_tmp, tv_caller);
  			if (act->prev_caller) {
  				/* Just transfer the refcount from act->prev_caller to tv_caller,
  				 * so no need for a refcount update.  This is the expected case.
  				 */
  				DUK_TVAL_SET_OBJECT(tv_caller, act->prev_caller);
  				act->prev_caller = NULL;
  			} else {
  				DUK_TVAL_SET_NULL(tv_caller); /* no incref needed */
  				DUK_ASSERT(act->prev_caller == NULL);
  			}
  			DUK_TVAL_DECREF_NORZ(thr, &tv_tmp);
  		} else {
  			h_tmp = act->prev_caller;
  			if (h_tmp) {
  				act->prev_caller = NULL;
  				DUK_HOBJECT_DECREF_NORZ(thr, h_tmp);
  			}
  		}
  		DUK_ASSERT(act->prev_caller == NULL);
  	}
  #endif
  
  	/*
  	 *  Unwind debugger state.  If we unwind while stepping
  	 *  (for any step type), pause execution.  This is the
  	 *  only place explicitly handling a step out.
  	 */
  
  #if defined(DUK_USE_DEBUGGER_SUPPORT)
  	heap = thr->heap;
  	if (heap->dbg_pause_act == thr->callstack_curr) {
  		if (heap->dbg_pause_flags & DUK_PAUSE_FLAG_FUNC_EXIT) {
  			DUK_D(DUK_DPRINT("PAUSE TRIGGERED by function exit"));
  			duk_debug_set_paused(heap);
  		} else {
  			DUK_D(DUK_DPRINT("unwound past dbg_pause_act, set to NULL"));
  			heap->dbg_pause_act = NULL; /* avoid stale pointers */
  		}
  		DUK_ASSERT(heap->dbg_pause_act == NULL);
  	}
  #endif
  
  	/*
  	 *  Unwind catchers.
  	 *
  	 *  Since there are no references in the catcher structure,
  	 *  unwinding is quite simple.  The only thing we need to
  	 *  look out for is popping a possible lexical environment
  	 *  established for an active catch clause.
  	 */
  
  	while (act->cat != NULL) {
  		duk_hthread_catcher_unwind_norz(thr, act);
  	}
  
  	/*
  	 *  Close environment record(s) if they exist.
  	 *
  	 *  Only variable environments are closed.  If lex_env != var_env, it
  	 *  cannot currently contain any register bound declarations.
  	 *
  	 *  Only environments created for a NEWENV function are closed.  If an
  	 *  environment is created for e.g. an eval call, it must not be closed.
  	 */
  
  	func = DUK_ACT_GET_FUNC(act);
  	if (func != NULL && !DUK_HOBJECT_HAS_NEWENV(func)) {
  		DUK_DDD(DUK_DDDPRINT("skip closing environments, envs not owned by this activation"));
  		goto skip_env_close;
  	}
  	/* func is NULL for lightfunc */
  
  	/* Catch sites are required to clean up their environments
  	 * in FINALLY part before propagating, so this should
  	 * always hold here.
  	 */
  	DUK_ASSERT(act->lex_env == act->var_env);
  
  	/* XXX: Closing the environment record copies values from registers
  	 * into the scope object.  It's side effect free as such, but may
  	 * currently run out of memory which causes an error throw.  This is
  	 * an actual sandboxing problem for error unwinds, and needs to be
  	 * fixed e.g. by preallocating the scope property slots.
  	 */
  	if (act->var_env != NULL) {
  		DUK_DDD(DUK_DDDPRINT("closing var_env record %p -> %!O", (void *) act->var_env, (duk_heaphdr *) act->var_env));
  		duk_js_close_environment_record(thr, act->var_env);
  	}
  
  skip_env_close:
  
  	/*
  	 *  Update preventcount
  	 */
  
  	if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
  		DUK_ASSERT(thr->callstack_preventcount >= 1);
  		thr->callstack_preventcount--;
  	}
  
  	/*
  	 *  Reference count updates, using NORZ macros so we don't
  	 *  need to handle side effects.
  	 *
  	 *  duk_activation pointers like act->var_env are intentionally
  	 *  left as garbage and not NULLed.  Without side effects they
  	 *  can't be used when the values are dangling/garbage.
  	 */
  
  	DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, act->var_env);
  	DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, act->lex_env);
  	tmp = DUK_ACT_GET_FUNC(act);
  	DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, tmp);
  	DUK_UNREF(tmp);
  }
  
  /* Unwind topmost duk_activation of a thread, caller must ensure that an
   * activation exists.  The call is side effect free, except that scope
   * closure may currently throw an out-of-memory error.
   */
  DUK_INTERNAL void duk_hthread_activation_unwind_norz(duk_hthread *thr) {
  	duk_activation *act;
  
  	duk__activation_unwind_nofree_norz(thr);
  
  	DUK_ASSERT(thr->callstack_curr != NULL);
  	DUK_ASSERT(thr->callstack_top > 0);
  	act = thr->callstack_curr;
  	thr->callstack_curr = act->parent;
  	thr->callstack_top--;
  
  	/* Ideally we'd restore value stack reserve here to caller's value.
  	 * This doesn't work for current unwind call sites however, because
  	 * the current (unwound) value stack top may be above the reserve.
  	 * Thus value stack reserve is restored by the call sites.
  	 */
  
  	/* XXX: inline for performance builds? */
  	duk_hthread_activation_free(thr, act);
  
  	/* We could clear the book-keeping variables like retval_byteoff for
  	 * the topmost activation, but don't do so now as it's not necessary.
  	 */
  }
  
  DUK_INTERNAL void duk_hthread_activation_unwind_reuse_norz(duk_hthread *thr) {
  	duk__activation_unwind_nofree_norz(thr);
  }
  
  /* Get duk_activation for given callstack level or NULL if level is invalid
   * or deeper than the call stack.  Level -1 refers to current activation, -2
   * to its caller, etc.  Starting from Duktape 2.2 finding the activation is
   * a linked list scan which gets more expensive the deeper the lookup is.
   */
  DUK_INTERNAL duk_activation *duk_hthread_get_activation_for_level(duk_hthread *thr, duk_int_t level) {
  	duk_activation *act;
  
  	if (level >= 0) {
  		return NULL;
  	}
  	act = thr->callstack_curr;
  	for (;;) {
  		if (act == NULL) {
  			return act;
  		}
  		if (level == -1) {
  			return act;
  		}
  		level++;
  		act = act->parent;
  	}
  	/* never here */
  }
  
  #if defined(DUK_USE_FINALIZER_TORTURE)
  DUK_INTERNAL void duk_hthread_valstack_torture_realloc(duk_hthread *thr) {
  	duk_size_t alloc_size;
  	duk_tval *new_ptr;
  	duk_ptrdiff_t alloc_end_off;
  	duk_ptrdiff_t end_off;
  	duk_ptrdiff_t bottom_off;
  	duk_ptrdiff_t top_off;
  
  	if (thr->valstack == NULL) {
  		DUK_D(DUK_DPRINT("skip valstack torture realloc, valstack is NULL"));
  		return;
  	}
  
  	alloc_end_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_alloc_end - (duk_uint8_t *) thr->valstack);
  	end_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_end - (duk_uint8_t *) thr->valstack);
  	bottom_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_bottom - (duk_uint8_t *) thr->valstack);
  	top_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_top - (duk_uint8_t *) thr->valstack);
  	alloc_size = (duk_size_t) alloc_end_off;
  	if (alloc_size == 0) {
  		DUK_D(DUK_DPRINT("skip valstack torture realloc, alloc_size is zero"));
  		return;
  	}
  
  	/* Use DUK_ALLOC_RAW() to avoid side effects. */
  	new_ptr = (duk_tval *) DUK_ALLOC_RAW(thr->heap, alloc_size);
  	if (new_ptr != NULL) {
  		duk_memcpy((void *) new_ptr, (const void *) thr->valstack, alloc_size);
  		duk_memset((void *) thr->valstack, 0x55, alloc_size);
  		DUK_FREE_CHECKED(thr, (void *) thr->valstack);
  		thr->valstack = new_ptr;
  		thr->valstack_alloc_end = (duk_tval *) ((duk_uint8_t *) new_ptr + alloc_end_off);
  		thr->valstack_end = (duk_tval *) ((duk_uint8_t *) new_ptr + end_off);
  		thr->valstack_bottom = (duk_tval *) ((duk_uint8_t *) new_ptr + bottom_off);
  		thr->valstack_top = (duk_tval *) ((duk_uint8_t *) new_ptr + top_off);
  	} else {
  		DUK_D(DUK_DPRINT("failed to realloc valstack for torture, ignore"));
  	}
  }
  #endif /* DUK_USE_FINALIZER_TORTURE */
  