Skip to content

Commit 2366098

Browse files
#262 Phase-3 D1: slot-key the observer loop-stall check
LOOP_STALL_CHECK (the observer-based loop-halting reader) read the global last-observer Value's dH/entropy directly — a Value-observer dependency that blocks deleting the Value observer fields (Step E). Route it through the slot model instead. New shared helper obs_stall_trajectory(&dH, &ent): prefers the last-observed slot (g_last_obs_slot_env/idx, eagerly maintained by observer_slot_update); falls back to the global last-observer Value when the slot isn't populated or the value path is active (EIGS_OBS_SHADOW=0). Both the interpreter CASE(LOOP_STALL_CHECK) and jit_helper_loop_stall_check call it, so the two engines stay byte-identical on loop classification (the same lockstep invariant the opcode encoding enforces). Behavior-preserving under the shadow (slot and value agree for the canonical `loop while not converged: x is f(x)` shape — both sample the same x at the same point). Validated: tests/test_loop_halting.sh all pass; suite 2080/2080 default(slot) + escape(=0); ASan 2080/2080 leak tally 4; observer corpus 12/12 both paths; solve corpus matches golden both paths. First of the Step-D reader migrations (decouple the value representation from observer state). Remaining D readers: bare OP_INTERROGATE on non-ident exprs (semantic decision — no binding => no slot trajectory), the temporal trace observer snapshots (state_at / where-at), then gating the value-path writers + flipping both promotion sites (slot_from_value, slot_bridge_wrap). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1e32dab commit 2366098

1 file changed

Lines changed: 29 additions & 6 deletions

File tree

src/vm.c

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@ static inline int obs_shadow_on(void) {
3232
}
3333
return g_obs_shadow;
3434
}
35+
36+
/* #262 Phase-3 D: read the last-observed trajectory (dH, entropy) for the
37+
* observer loop-stall check. Prefers the slot model (the default); falls back
38+
* to the global last-observer Value when the slot isn't populated (e.g. a
39+
* module-scope observe not yet routed to a slot) or the value path is active
40+
* (EIGS_OBS_SHADOW=0). Shared by the interpreter CASE(LOOP_STALL_CHECK) and
41+
* jit_helper_loop_stall_check so the two can never disagree on loop
42+
* classification (the same lockstep invariant the opcode encoding enforces).
43+
* Returns 1 if (*dH, *ent) were filled. */
44+
static inline int obs_stall_trajectory(double *dH, double *ent) {
45+
if (obs_shadow_on() && g_last_obs_slot_env &&
46+
g_last_obs_slot_idx >= 0 &&
47+
g_last_obs_slot_idx < g_last_obs_slot_env->obs_cap &&
48+
g_last_obs_slot_env->obs[g_last_obs_slot_idx].used) {
49+
const ObserverSlot *s = &g_last_obs_slot_env->obs[g_last_obs_slot_idx];
50+
*dH = s->dH; *ent = s->entropy;
51+
return 1;
52+
}
53+
Value *obs = g_last_observer;
54+
if (obs) observer_ensure_fresh(obs);
55+
if (obs) { *dH = obs->dH; *ent = obs->entropy; return 1; }
56+
return 0;
57+
}
3558
void vm_obs_slot_dropped(Env *e) {
3659
if (g_last_obs_slot_env == e) { g_last_obs_slot_env = NULL; g_last_obs_slot_idx = -1; }
3760
}
@@ -1233,9 +1256,9 @@ int jit_helper_loop_stall_check(void) {
12331256
g_loop_iterations++;
12341257
int should_exit = 0;
12351258
if (g_unobserved_depth == 0) {
1236-
Value *obs = g_last_observer;
1237-
if (obs) observer_ensure_fresh(obs);
1238-
if (obs && fabs(obs->dH) < g_obs_dh_zero && obs->entropy >= g_obs_h_low) {
1259+
double dH, ent;
1260+
if (obs_stall_trajectory(&dH, &ent)
1261+
&& fabs(dH) < g_obs_dh_zero && ent >= g_obs_h_low) {
12391262
g_loop_stall_count++;
12401263
if (g_loop_stall_count >= 100) {
12411264
g_loop_exit_reason = "stalled";
@@ -4212,9 +4235,9 @@ static Value *vm_run(EigsChunk *chunk, Env *env) {
42124235
g_loop_iterations++;
42134236
int should_exit = 0;
42144237
if (g_unobserved_depth == 0) {
4215-
Value *obs = g_last_observer;
4216-
if (obs) observer_ensure_fresh(obs);
4217-
if (obs && fabs(obs->dH) < g_obs_dh_zero && obs->entropy >= g_obs_h_low) {
4238+
double dH, ent;
4239+
if (obs_stall_trajectory(&dH, &ent)
4240+
&& fabs(dH) < g_obs_dh_zero && ent >= g_obs_h_low) {
42184241
g_loop_stall_count++;
42194242
if (g_loop_stall_count >= 100) {
42204243
g_loop_exit_reason = "stalled";

0 commit comments

Comments
 (0)