From 82556611daa2aa1965c6ebb53e8205e4221c739e Mon Sep 17 00:00:00 2001 From: x Date: Sat, 10 Oct 2026 11:34:07 -0500 Subject: [PATCH] Core runtime onto the slot API (#1665 arm D B1) Batch B1 of the arm D step-2 design. Storage stays Value**. - eigenscript.h / eigenscript.c / trace.c leave the legacy borrow API and join tools/container_migrated_files.txt. - eigs_bool_gate moves below the container.h include and tests list_elem_type (its container_access allowlist row is removed). - The typed number readers (eigs_list_num / eigs_elem_num / eigs_opt_num) are slot-native. - Walkers migrated: ent_child / compute_entropy_impl, free_value, chan_clone_rec, values_equal_impl, value_to_string, write_value_ptr_full, eigs_arg_has_bool's nested scan. - GC edge rows for list items and dict values walk slot_is_ptr / slot_as_ptr and clear by take + slot_decref (edge count snapshotted). - Storage alloc/grow use sizeof(*storage). - Re-pinned 7 exempt shape hashes (helper closure includes a rewritten core helper; no policy change); num_read allowlist updated. Builder gates (Codex Cloud): release 4205/4205, slot-rehearsal 4206/4206, jit-smoke, jit_diff (256 programs), trace-corpus tape bytes identical to B0, bench_dmg_shape Ir +0.0051%, freestanding-check, werror_switch_check. Full ASan did not finish in the builder's window; CI's ASan shards are the leak gate. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01KC99CmwatKssQYggkBCgwF --- changes/internal/1665-core-slot-api.md | 1 + src/container.h | 9 ++ src/eigenscript.c | 164 ++++++++++++++----------- src/eigenscript.h | 34 ++--- src/trace.c | 6 +- tools/container_access_check.sh | 4 - tools/container_migrated_files.txt | 5 +- tools/num_read_allowlist.txt | 5 +- tools/strict_shape_contracts.json | 14 +-- 9 files changed, 137 insertions(+), 105 deletions(-) create mode 100644 changes/internal/1665-core-slot-api.md diff --git a/changes/internal/1665-core-slot-api.md b/changes/internal/1665-core-slot-api.md new file mode 100644 index 00000000..cfcfa668 --- /dev/null +++ b/changes/internal/1665-core-slot-api.md @@ -0,0 +1 @@ +- Internal: the core runtime (eigenscript.c/.h, trace.c) reads containers, walks values and traces GC edges through the slot API, ahead of NaN-boxed container storage (#1665 arm D B1). diff --git a/src/container.h b/src/container.h index beab8e34..577c9d1d 100644 --- a/src/container.h +++ b/src/container.h @@ -135,9 +135,18 @@ static inline Value *dict_value_get_return(const Value *dict, int i) { return v->type == VAL_NUM ? make_num(VAL_NUM_RAW(v)) : v; } +/* Compatibility reads for files not yet migrated in later batches. */ +static inline Value *dict_get(Value *dict, const char *key) { + return dict_get_ref_legacy(dict, key); +} +static inline Value *dict_get_hashed(Value *dict, const char *key, uint32_t h) { + return dict_get_hashed_ref_legacy(dict, key, h); +} + /* Low-level compatibility accessors retain their pre-flip meaning. */ static inline Value *list_get_borrow(const Value *list, int i) { return list->data.list.items[i]; } static inline Value *dict_value_get_borrow(const Value *dict, int i) { return dict->data.dict.vals[i]; } +static inline Value *dict_value_compat_ref(const Value *dict, int i) { return dict->data.dict.vals[i]; } static inline void list_set_owned(Value *list, int i, Value *v) { list->data.list.items[i] = v; } static inline void dict_value_set_owned(Value *dict, int i, Value *v) { dict->data.dict.vals[i] = v; } static inline void list_set_borrow(Value *list, int i, Value *v) { val_incref(v); list_set_owned(list, i, v); } diff --git a/src/eigenscript.c b/src/eigenscript.c index c4b2bd39..bf4c3dc2 100644 --- a/src/eigenscript.c +++ b/src/eigenscript.c @@ -427,6 +427,11 @@ static double ent_child(Value *c) { return compute_entropy_impl(c); } +static double ent_child_slot(EigsSlot s) { + if (slot_is_num(s)) return entropy_of_num(SLOT_NUM_RAW(s)); + return ent_child(eigs_slot_ref(s)); +} + static double compute_entropy_impl(Value *v) { if (!v) return 0.0; switch (v->type) { @@ -438,7 +443,7 @@ static double compute_entropy_impl(Value *v) { if (v->data.list.count == 0) return 0.0; double sum = 0.0; for (int i = 0; i < v->data.list.count; i++) - sum += ent_child(list_get_borrow(v, i)); + sum += ent_child_slot(list_slot(v, i)); return sum / v->data.list.count + log2((double)v->data.list.count + 1.0); } case VAL_DICT: { @@ -446,7 +451,7 @@ static double compute_entropy_impl(Value *v) { if (v->data.dict.count == 0) return 0.0; double sum = 0.0; for (int i = 0; i < v->data.dict.count; i++) - sum += ent_child(dict_value_get_borrow(v, i)); + sum += ent_child_slot(dict_value_slot(v, i)); return sum / v->data.dict.count + log2((double)v->data.dict.count + 1.0); } case VAL_FN: return 1.0; /* #708: a constant, so dH never moves */ @@ -1444,6 +1449,11 @@ Value *observer_slot_trajectory(const ObserverSlot *s) { return out; } +static Value *dict_get_view_local(Value *dict, const char *key, EigsView *view) { + Value *v = dict_get_ref_legacy(dict, key); + return v ? slot_view(eigs_container_slot(v), view) : NULL; +} + /* Rebuild a classifiable slot from a snapshot dict. Returns 1 and fills * *out (windows malloc'd — caller frees dh_window/v_window/vr_window) when * the dict is a well-formed trajectory; 0 otherwise. Wrong shapes are the @@ -1451,12 +1461,13 @@ Value *observer_slot_trajectory(const ObserverSlot *s) { int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { memset(out, 0, sizeof *out); if (!dict || dict->type != VAL_DICT) return 0; - Value *kind = dict_get(dict, "kind"); + EIGS_VIEW(kind_view); EIGS_VIEW(rel_view); EIGS_VIEW(raw_view); EIGS_VIEW(dh_view); + Value *kind = dict_get_view_local(dict, "kind", &kind_view); if (!kind || kind->type != VAL_STR || strcmp(kind->data.str, "trajectory") != 0) return 0; - Value *rel = dict_get(dict, "rel"); - Value *raw = dict_get(dict, "raw"); - Value *dh = dict_get(dict, "dh"); + Value *rel = dict_get_view_local(dict, "rel", &rel_view); + Value *raw = dict_get_view_local(dict, "raw", &raw_view); + Value *dh = dict_get_view_local(dict, "dh", &dh_view); if (!rel || rel->type != VAL_LIST || !raw || raw->type != VAL_LIST || !dh || dh->type != VAL_LIST) return 0; @@ -1464,7 +1475,8 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { * may omit it — the state default then applies). Out of range means a * malformed snapshot, refused like any other wrong shape. */ { - Value *w = dict_get(dict, "window"); + EIGS_VIEW(w_view); + Value *w = dict_get_view_local(dict, "window", &w_view); if (w) { if (w->type != VAL_NUM || VAL_NUM_RAW(w) < OBSERVER_WINDOW_MIN || VAL_NUM_RAW(w) > OBSERVER_WINDOW_MAX || VAL_NUM_RAW(w) != (int)VAL_NUM_RAW(w)) @@ -1482,14 +1494,14 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { out->vr_window = xcalloc((size_t)N, sizeof(double)); out->v_cap = out->dh_cap = (uint8_t)N; for (int i = vstart; i < vcnt; i++) { - Value *a = list_get_borrow(rel, i), *b = list_get_borrow(raw, i); - if (!a || a->type != VAL_NUM || !b || b->type != VAL_NUM) { + EigsSlot a = list_slot(rel, i), b = list_slot(raw, i); + if (slot_type(a) != VAL_NUM || slot_type(b) != VAL_NUM) { free(out->v_window); free(out->vr_window); memset(out, 0, sizeof *out); return 0; } - out->v_window[out->v_window_count] = VAL_NUM_RAW(a); - out->vr_window[out->v_window_count] = VAL_NUM_RAW(b); + out->v_window[out->v_window_count] = eigs_slot_num(a, __func__); + out->vr_window[out->v_window_count] = eigs_slot_num(b, __func__); out->v_window_count++; } out->v_window_head = (uint8_t)(out->v_window_count % N); @@ -1497,13 +1509,13 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { int dstart = dcnt > N ? dcnt - N : 0; out->dh_window = xcalloc((size_t)N, sizeof(double)); for (int i = dstart; i < dcnt; i++) { - Value *a = list_get_borrow(dh, i); - if (!a || a->type != VAL_NUM) { + EigsSlot a = list_slot(dh, i); + if (slot_type(a) != VAL_NUM) { free(out->v_window); free(out->vr_window); free(out->dh_window); memset(out, 0, sizeof *out); return 0; } - out->dh_window[out->dh_window_count++] = VAL_NUM_RAW(a); + out->dh_window[out->dh_window_count++] = eigs_slot_num(a, __func__); } out->dh_window_head = (uint8_t)(out->dh_window_count % N); /* #1637: the scalar fields are numbers and the two flags are bools, as @@ -1513,7 +1525,8 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { static const char *const nums[] = {"entropy", "dH", "last_entropy", "last_value"}; double *dsts[] = {&out->entropy, &out->dH, &out->last_entropy, &out->last_value}; for (int k = 0; k < 4; k++) { - Value *v = dict_get(dict, nums[k]); + EIGS_VIEW(v_view); + Value *v = dict_get_view_local(dict, nums[k], &v_view); if (!v) continue; if (v->type != VAL_NUM) { free(out->v_window); free(out->vr_window); free(out->dh_window); @@ -1522,7 +1535,9 @@ int observer_slot_from_trajectory(ObserverSlot *out, Value *dict) { } *dsts[k] = VAL_NUM_RAW(v); } - Value *fo = dict_get(dict, "observed"), *fn = dict_get(dict, "numeric"); + EIGS_VIEW(fo_view); EIGS_VIEW(fn_view); + Value *fo = dict_get_view_local(dict, "observed", &fo_view); + Value *fn = dict_get_view_local(dict, "numeric", &fn_view); if ((fo && fo->type != VAL_BOOL) || (fn && fn->type != VAL_BOOL)) { free(out->v_window); free(out->vr_window); free(out->dh_window); memset(out, 0, sizeof *out); @@ -1644,9 +1659,8 @@ void free_value(Value *v) { break; case VAL_LIST: for (int i = 0; i < v->data.list.count; i++) - val_decref(list_get_borrow(v, i)); - if (list_values_storage(v)) - free(list_values_storage(v)); + slot_decref(list_slot(v, i)); + free(*list_values_storage_ref(v)); break; case VAL_DICT: if (v->module_ns) { @@ -1658,7 +1672,7 @@ void free_value(Value *v) { } for (int i = 0; i < v->data.dict.count; i++) { /* Keys have table/private-node ownership; do not free individually. */ - val_decref(dict_value_get_borrow(v, i)); + slot_decref(dict_value_slot(v, i)); } free(v->data.dict.keys); free(dict_values_storage(v)); @@ -1797,20 +1811,15 @@ double eigs_list_num(const Value *list, int i, const char *who) { num_who(who), i); return 0.0; } - return eigs_num_arg(list_get_borrow(list, i), who); + return eigs_slot_num(list_slot(list, i), who); } double eigs_elem_num(const Value *v, const char *who) { - if (v && v->type == VAL_NUM) return VAL_NUM_RAW(v); - if (g_strict || (v && v->type == VAL_BOOL)) return eigs_num_arg_slow(v, who); - return 0.0; + return eigs_slot_elem_num(eigs_container_slot((Value *)v), who); } int eigs_opt_num(const Value *v, double *out, const char *who) { - if (!v || v->type == VAL_NULL) return 0; - if (v->type == VAL_NUM) { *out = VAL_NUM_RAW(v); return 1; } - eigs_num_arg(v, who); - return 0; + return eigs_slot_opt_num(eigs_container_slot((Value *)v), out, who); } /* Two levels: the argument, an element of the argument list, or an element @@ -1821,12 +1830,12 @@ int eigs_arg_has_bool(const Value *arg) { if (arg->type == VAL_BOOL) return 1; if (arg->type == VAL_LIST) for (int i = 0; i < arg->data.list.count; i++) { - const Value *e = list_get_borrow(arg, i); - if (!e) continue; - if (e->type == VAL_BOOL) return 1; - if (e->type == VAL_LIST) + if (list_elem_type(arg, i) == VAL_BOOL) return 1; + EIGS_VIEW(view); + const Value *e = list_get_view(arg, i, &view); + if (e && e->type == VAL_LIST) for (int j = 0; j < e->data.list.count; j++) - if (list_get_borrow(e, j) && list_get_borrow(e, j)->type == VAL_BOOL) + if (list_elem_type(e, j) == VAL_BOOL) return 1; } return 0; @@ -1970,7 +1979,8 @@ Value* make_list(int capacity) { Value *v = xcalloc(1, sizeof(Value)); v->type = VAL_LIST; v->data.list.capacity = capacity < 8 ? 8 : capacity; - *list_values_storage_ref(v) = xcalloc(v->data.list.capacity, sizeof(Value*)); + *list_values_storage_ref(v) = xcalloc(v->data.list.capacity, + sizeof **list_values_storage_ref(v)); v->data.list.count = 0; v->refcount = 1; return v; @@ -1983,7 +1993,8 @@ Value* make_list_heap(int capacity) { Value *v = xcalloc(1, sizeof(Value)); v->type = VAL_LIST; v->data.list.capacity = capacity < 8 ? 8 : capacity; - *list_values_storage_ref(v) = xcalloc(v->data.list.capacity, sizeof(Value*)); + *list_values_storage_ref(v) = xcalloc(v->data.list.capacity, + sizeof **list_values_storage_ref(v)); v->data.list.count = 0; v->refcount = 1; return v; @@ -2341,14 +2352,14 @@ static Value *module_ns_project(Value *d, Env *e, const char *key, uint32_t h) { env_shared_lock(e); int di = env_hash_find(&d->data.dict.hash, key, h, d->data.dict.keys); if (ei < 0) { - Value *miss = (di >= 0) ? dict_value_get_borrow(d, di) : NULL; + Value *miss = (di >= 0) ? dict_value_compat_ref(d, di) : NULL; env_shared_unlock(e); return miss; } if (di >= 0) { - Value *cur = dict_value_get_borrow(d, di); + Value *cur = dict_value_compat_ref(d, di); if (slot_is_ptr(s)) { - /* Container/fn bindings were already shared by reference. */ + /* Reference bindings are already shared by identity. */ if (slot_as_ptr(s) == cur) { env_shared_unlock(e); slot_decref(s); @@ -2368,7 +2379,7 @@ static Value *module_ns_project(Value *d, Env *e, const char *key, uint32_t h) { Value *mv = slot_to_value(s); /* owned */ dict_set_hashed_raw(d, key, h, mv); di = env_hash_find(&d->data.dict.hash, key, h, d->data.dict.keys); - Value *out = (di >= 0) ? dict_value_get_borrow(d, di) : NULL; + Value *out = (di >= 0) ? dict_value_compat_ref(d, di) : NULL; env_shared_unlock(e); slot_decref(s); /* drop the pin */ val_decref(mv); @@ -2478,7 +2489,7 @@ void dict_set_hashed_raw(Value *dict, const char *key, uint32_t h, Value *val) { int idx = env_hash_find(&dict->data.dict.hash, key, h, dict->data.dict.keys); if (idx >= 0) { val_incref(val); - val_decref(dict_value_get_borrow(dict, idx)); + slot_decref(dict_value_slot(dict, idx)); dict_value_set_owned(dict, idx, val); return; } @@ -2606,7 +2617,8 @@ static Value *chan_clone_rec(Value *v, int depth) { int n = v->data.list.count; Value *out = make_list_heap(n > 0 ? n : 1); for (int i = 0; i < n; i++) { - Value *ce = chan_clone_rec(list_get_borrow(v, i), depth + 1); + EIGS_VIEW(child_view); + Value *ce = chan_clone_rec(list_get_view(v, i, &child_view), depth + 1); list_append(out, ce); /* increfs */ val_decref(ce); /* drop birth ref */ } @@ -2620,7 +2632,8 @@ static Value *chan_clone_rec(Value *v, int depth) { int n = v->data.dict.count; Value *out = make_dict(n > 0 ? n : 8); for (int i = 0; i < n; i++) { - Value *cv = chan_clone_rec(dict_value_get_borrow(v, i), depth + 1); + EIGS_VIEW(child_view); + Value *cv = chan_clone_rec(dict_value_get_view(v, i, &child_view), depth + 1); dict_set_owned(out, v->data.dict.keys[i], cv); /* consumes cv */ } /* Rehome keys from the (thread-local, soon-freed) intern table to @@ -2684,7 +2697,7 @@ Value *val_clone_for_send(Value *v) { return chan_clone_rec(v, 0); } -Value* dict_get_hashed(Value *dict, const char *key, uint32_t h) { +Value* dict_get_hashed_ref_legacy(Value *dict, const char *key, uint32_t h) { if (!dict || dict->type != VAL_DICT) return NULL; if (h == 0) h = env_hash_name(key); if (__builtin_expect(dict->module_ns != 0, 0)) { @@ -2699,18 +2712,18 @@ Value* dict_get_hashed(Value *dict, const char *key, uint32_t h) { env_shared_lock(me); int pidx = env_hash_find(&dict->data.dict.hash, key, h, dict->data.dict.keys); - Value *pv = (pidx >= 0) ? dict_value_get_borrow(dict, pidx) : NULL; + Value *pv = (pidx >= 0) ? dict_value_compat_ref(dict, pidx) : NULL; env_shared_unlock(me); return pv; } } if (dict->data.dict.count == 0) return NULL; int idx = env_hash_find(&dict->data.dict.hash, key, h, dict->data.dict.keys); - return (idx >= 0) ? dict_value_get_borrow(dict, idx) : NULL; + return (idx >= 0) ? dict_value_compat_ref(dict, idx) : NULL; } -Value* dict_get(Value *dict, const char *key) { - return dict_get_hashed(dict, key, env_hash_name(key)); +Value* dict_get_ref_legacy(Value *dict, const char *key) { + return dict_get_hashed_ref_legacy(dict, key, env_hash_name(key)); } int env_hash_find_dict(Value *dict, const char *key, uint32_t h) { @@ -2720,7 +2733,7 @@ int env_hash_find_dict(Value *dict, const char *key, uint32_t h) { } int dict_has(Value *dict, const char *key) { - return dict_get(dict, key) != NULL; + return dict_get_ref_legacy(dict, key) != NULL; } void dict_remove(Value *dict, const char *key) { @@ -2731,11 +2744,11 @@ void dict_remove(Value *dict, const char *key) { if (idx < 0) return; /* keys are interned — do not free */ env_intern_scope_remove(dict->data.dict.keys[idx]); - val_decref(dict_value_get_borrow(dict, idx)); + slot_decref(dict_value_slot(dict, idx)); /* Shift remaining */ for (int j = idx; j < dict->data.dict.count - 1; j++) { dict->data.dict.keys[j] = dict->data.dict.keys[j+1]; - dict_value_set_owned(dict, j, dict_value_get_borrow(dict, j+1)); + dict_value_set_owned(dict, j, dict_value_compat_ref(dict, j + 1)); } dict->data.dict.count--; env_hash_rebuild(&dict->data.dict.hash, dict->data.dict.keys, dict->data.dict.count); @@ -2759,7 +2772,8 @@ void list_append(Value *list, Value *item) { if (!sandbox_charge((size_t)(new_cap - list->data.list.capacity) * (sizeof(Value *) + sizeof(Value)))) return; - *list_values_storage_ref(list) = xrealloc_array(list_values_storage(list), new_cap, sizeof(Value*)); + *list_values_storage_ref(list) = xrealloc_array(*list_values_storage_ref(list), new_cap, + sizeof **list_values_storage_ref(list)); list->data.list.capacity = new_cap; } list_set_owned(list, list->data.list.count++, item); @@ -2834,10 +2848,12 @@ static int values_equal_impl(Value *a, Value *b, int depth, const char *op) { case VAL_BOOL: return a->data.boolean == b->data.boolean; case VAL_LIST: { if (a->data.list.count != b->data.list.count) return 0; - for (int i = 0; i < a->data.list.count; i++) - if (!values_equal_impl(list_get_borrow(a, i), - list_get_borrow(b, i), depth + 1, op)) + for (int i = 0; i < a->data.list.count; i++) { + EIGS_VIEW(av); EIGS_VIEW(bv); + if (!values_equal_impl(list_get_view(a, i, &av), + list_get_view(b, i, &bv), depth + 1, op)) return 0; + } return 1; } case VAL_DICT: { @@ -2845,9 +2861,10 @@ static int values_equal_impl(Value *a, Value *b, int depth, const char *op) { eigs_module_ns_sync(b); if (a->data.dict.count != b->data.dict.count) return 0; for (int i = 0; i < a->data.dict.count; i++) { - Value *bv = dict_get(b, a->data.dict.keys[i]); + Value *bv = dict_get_ref_legacy(b, a->data.dict.keys[i]); if (!bv) return 0; - if (!values_equal_impl(dict_value_get_borrow(a, i), bv, depth + 1, op)) + EIGS_VIEW(av); + if (!values_equal_impl(dict_value_get_view(a, i, &av), bv, depth + 1, op)) return 0; } return 1; @@ -2930,8 +2947,10 @@ char* value_to_string(Value *v) { g_vts_depth++; for (int i = 0; i < v->data.list.count; i++) { if (i > 0) strbuf_append_n(&out, ", ", 2); - char *s = value_to_string(list_get_borrow(v, i)); - if (list_get_borrow(v, i) && list_get_borrow(v, i)->type == VAL_STR) + EIGS_VIEW(item_view); + Value *item = list_get_view(v, i, &item_view); + char *s = value_to_string(item); + if (list_elem_type(v, i) == VAL_STR) eigs_json_escape_string(&out, s); else strbuf_append(&out, s); @@ -2952,8 +2971,9 @@ char* value_to_string(Value *v) { if (i > 0) strbuf_append_n(&out, ", ", 2); eigs_json_escape_string(&out, v->data.dict.keys[i]); strbuf_append_n(&out, ": ", 2); - char *vs = value_to_string(dict_value_get_borrow(v, i)); - if (dict_value_get_borrow(v, i) && dict_value_get_borrow(v, i)->type == VAL_STR) + EIGS_VIEW(item_view); + char *vs = value_to_string(dict_value_get_view(v, i, &item_view)); + if (dict_value_type(v, i) == VAL_STR) eigs_json_escape_string(&out, vs); else strbuf_append(&out, vs); @@ -4251,17 +4271,18 @@ static int gc_env_is_node(Env *e) { chunk_decref(_o); }, __VA_ARGS__) \ /* VAL_LIST / VAL_DICT: every element slot. */ \ X((_k == GC_KIND_VAL && _v->type == VAL_LIST), _v->data.list.count, \ - list_get_borrow(_v, _i), GC_KIND_VAL, \ - gc_value_is_node(list_get_borrow(_v, _i)), \ - { Value *_o = list_get_borrow(_v, _i); \ - list_set_owned(_v, _i, NULL); \ - val_decref(_o); }, __VA_ARGS__) \ + slot_as_ptr(list_slot(_v, _i)), GC_KIND_VAL, \ + slot_is_ptr(list_slot(_v, _i)) && \ + gc_value_is_node(slot_as_ptr(list_slot(_v, _i))), \ + { EigsSlot _o = list_take_slot(_v, _v->data.list.count - 1); \ + slot_decref(_o); }, __VA_ARGS__) \ X((_k == GC_KIND_VAL && _v->type == VAL_DICT), _v->data.dict.count, \ - dict_value_get_borrow(_v, _i), GC_KIND_VAL, \ - gc_value_is_node(dict_value_get_borrow(_v, _i)), \ - { Value *_o = dict_value_get_borrow(_v, _i); \ - dict_value_set_owned(_v, _i, NULL); \ - val_decref(_o); }, __VA_ARGS__) \ + slot_as_ptr(dict_value_slot(_v, _i)), GC_KIND_VAL, \ + slot_is_ptr(dict_value_slot(_v, _i)) && \ + gc_value_is_node(slot_as_ptr(dict_value_slot(_v, _i))), \ + { EigsSlot _o = dict_value_take_slot(_v, _i); \ + dict_value_set_owned(_v, _i, NULL); \ + slot_decref(_o); }, __VA_ARGS__) \ /* #1057 module namespace: the owning backref to the module Env taken \ * by eigs_module_ns_attach. Without this row a garbage \ * namespace <-> module-env cycle would look externally referenced and \ @@ -4288,7 +4309,8 @@ static int gc_env_is_node(Env *e) { #define GC_EDGE_CLEAR(GUARD, COUNT, CHILD, CHILD_KIND, IS_NODE, CLEAR, \ _x1, _x2, _x3) \ if (GUARD) { \ - for (int _i = 0; _i < (COUNT); _i++) \ + int _edge_count = (COUNT); \ + for (int _i = 0; _i < _edge_count; _i++) \ CLEAR \ } diff --git a/src/eigenscript.h b/src/eigenscript.h index 4464784d..00c4b281 100644 --- a/src/eigenscript.h +++ b/src/eigenscript.h @@ -1412,21 +1412,6 @@ int eigs_opt_num(const Value *v, double *out, const char *who); #define EIGS_BOOL_GATE_SCAN 8 int eigs_bool_gate_slow(BuiltinFn fn, const Value *arg); void eigs_bool_gate_exempt(BuiltinFn fn); /* a host function: never gated */ -static inline int eigs_bool_gate(BuiltinFn fn, const Value *arg) { - if (!arg) return 0; - if (arg->type == VAL_BOOL) return eigs_bool_gate_slow(fn, arg); - if (arg->type != VAL_LIST) return 0; - int n = arg->data.list.count < EIGS_BOOL_GATE_SCAN ? arg->data.list.count - : EIGS_BOOL_GATE_SCAN; - for (int i = 0; i < n; i++) { - const Value *e = arg->data.list.items[i]; - if (e && e->type == VAL_BOOL) return eigs_bool_gate_slow(fn, arg); - } - return 0; -} -static inline Value *eigs_call_builtin(BuiltinFn fn, Value *arg) { - return eigs_bool_gate(fn, arg) ? NULL : fn(arg); -} /* #1637: for a builtin that reads a number without a typed guard (its other * wrong types answer null): a bool there raises in every strict mode. */ #define BOOL_REFUSE(v, who) \ @@ -1814,7 +1799,8 @@ void dict_set_owned(Value *dict, const char *key, Value *val); * keys into a process-global intern table so they survive the sender thread's * detach. Returns a heap value (refcount 1). */ Value *val_clone_for_send(Value *v); -Value* dict_get(Value *dict, const char *key); +Value* dict_get_ref_legacy(Value *dict, const char *key); +Value* dict_get_hashed_ref_legacy(Value *dict, const char *key, uint32_t h); void list_append(Value *list, Value *item); void list_append_owned(Value *list, Value *item); @@ -1993,6 +1979,21 @@ static inline void val_decref(Value *v) { #include "value_slot.h" #include "container.h" +static inline int eigs_bool_gate(BuiltinFn fn, const Value *arg) { + if (!arg) return 0; + if (arg->type == VAL_BOOL) return eigs_bool_gate_slow(fn, arg); + if (arg->type != VAL_LIST) return 0; + int n = arg->data.list.count < EIGS_BOOL_GATE_SCAN ? arg->data.list.count + : EIGS_BOOL_GATE_SCAN; + for (int i = 0; i < n; i++) + if (list_elem_type(arg, i) == VAL_BOOL) + return eigs_bool_gate_slow(fn, arg); + return 0; +} +static inline Value *eigs_call_builtin(BuiltinFn fn, Value *arg) { + return eigs_bool_gate(fn, arg) ? NULL : fn(arg); +} + /* ---- Environment ---- */ /* #607: post-resolve array access for an env the MAIN thread may grow @@ -2127,7 +2128,6 @@ void env_store_slot(Env *env, int idx, EigsSlot s); Env *env_resolve_chain(Env *start, const char *name, uint32_t h, int *out_slot, int *out_depth); void dict_set_hashed(Value *dict, const char *key, uint32_t h, Value *val); -Value* dict_get_hashed(Value *dict, const char *key, uint32_t h); /* #1057 module namespaces. `import M` binds a dict that is a LIVE VIEW of the * module's top-level Env: `M.x` reads the module's CURRENT binding and * `M.x is v` writes it. attach flags the dict and takes an OWNING ref on the diff --git a/src/trace.c b/src/trace.c index a9c257b5..69344f64 100644 --- a/src/trace.c +++ b/src/trace.c @@ -2960,7 +2960,8 @@ static void write_value_ptr_full(Value *v, int *budget) { int n = v->data.list.count; for (int i = 0; i < n && *budget > 0; i++) { if (i) wf_puts(", ", budget); - write_value_ptr_full(list_get_borrow(v, i), budget); + EIGS_VIEW(item_view); + write_value_ptr_full(list_get_view(v, i, &item_view), budget); } wf_putc(']', budget); break; @@ -2972,7 +2973,8 @@ static void write_value_ptr_full(Value *v, int *budget) { if (i) wf_puts(", ", budget); write_string_full(v->data.dict.keys[i], budget); wf_puts(": ", budget); - write_value_ptr_full(dict_value_get_borrow(v, i), budget); + EIGS_VIEW(item_view); + write_value_ptr_full(dict_value_get_view(v, i, &item_view), budget); } wf_putc('}', budget); break; diff --git a/tools/container_access_check.sh b/tools/container_access_check.sh index 74853f39..4d2fc7e3 100755 --- a/tools/container_access_check.sh +++ b/tools/container_access_check.sh @@ -22,10 +22,6 @@ while IFS= read -r hit; do src/jit.c:*'offsetof(Value, data.dict.vals)'*) # Emitted machine code needs the representation offset (step-2 site). allowed=$((allowed + 1)); continue ;; - src/eigenscript.h:*'arg->data.list.items[i]'*) - # eigs_bool_gate scans arg lists before container.h is included - # (step-2 site: it must test the slot tag, not a Value*). - allowed=$((allowed + 1)); continue ;; esac printf 'raw Value container access: %s:%s:%s\n' "$file" "$line" "$text" >&2 violations=$((violations + 1)) diff --git a/tools/container_migrated_files.txt b/tools/container_migrated_files.txt index a6420e2a..d8e7967b 100644 --- a/tools/container_migrated_files.txt +++ b/tools/container_migrated_files.txt @@ -1 +1,4 @@ -# B0 has no migrated production call sites. Add one src-relative path per line. +# Whole-file migrations completed after the B0 API introduction. +src/eigenscript.h +src/eigenscript.c +src/trace.c diff --git a/tools/num_read_allowlist.txt b/tools/num_read_allowlist.txt index 6e2a786b..dca6d9b8 100644 --- a/tools/num_read_allowlist.txt +++ b/tools/num_read_allowlist.txt @@ -28,15 +28,14 @@ chunk.c|const_equal|2|constant-pool dedup compares two constants after testing b chunk.c|const_hash_value|1|constant-pool hash of a constant inside its type == VAL_NUM branch eigenscript.c|chan_clone_rec|1|channel deep-copy, switch case VAL_NUM eigenscript.c|compute_entropy_impl|1|observer entropy, switch case VAL_NUM -eigenscript.c|eigs_elem_num|1|checking accessor itself: reads only after type == VAL_NUM +eigenscript.c|ent_child_slot|1|observer entropy reads an immediate only after slot_is_num eigenscript.c|eigs_num_arg_slow|1|checking accessor itself: reads only after type == VAL_NUM, raises otherwise -eigenscript.c|eigs_opt_num|1|checking accessor itself: reads only after type == VAL_NUM, raises on non-null non-numbers eigenscript.c|is_truthy|1|truthiness, switch case VAL_NUM (a bool has its own case) eigenscript.c|make_num|1|constructor: writes the number of a Value it just typed VAL_NUM eigenscript.c|make_num_permanent|1|constructor: writes the number of a Value it just typed VAL_NUM eigenscript.c|module_ns_project|2|mirror refresh: reads a slot after slot_is_num and writes a VAL_NUM mirror it owns exclusively eigenscript.c|observer_entropy_now|1|observer entropy of a slot after slot_is_num -eigenscript.c|observer_slot_from_trajectory|9|trajectory snapshot fields each read right after its own type != VAL_NUM refusal +eigenscript.c|observer_slot_from_trajectory|6|trajectory snapshot fields each read right after its own type != VAL_NUM refusal eigenscript.c|observer_slot_sample_gated|1|observer sample after newval->type == VAL_NUM eigenscript.c|observer_slot_update|1|observer update after newval->type == VAL_NUM eigenscript.c|slot_from_value|1|NaN-boxing boundary, inside its VAL_NUM branch diff --git a/tools/strict_shape_contracts.json b/tools/strict_shape_contracts.json index c144f042..ff3737cd 100644 --- a/tools/strict_shape_contracts.json +++ b/tools/strict_shape_contracts.json @@ -510,7 +510,7 @@ "0f2463d9f7d4223393269796d4688a5d8fa708244de3f974f0e2f62c8cb0b540" ], "eigs_list_num": [ - "7411c7e0c55633135b58755125bdfb3363fcf5ef2ab7efb450526d9a2f4c684d" + "25755c9b1f519fb0447e2d648dc0272419914db805f9249954cd2e146b48a3bc" ] } }, @@ -779,7 +779,7 @@ "268d0eea61701a8101ef750b078302f77890f8baf08b211aebb51c75ce87293d" ], "eigs_list_num": [ - "7411c7e0c55633135b58755125bdfb3363fcf5ef2ab7efb450526d9a2f4c684d" + "25755c9b1f519fb0447e2d648dc0272419914db805f9249954cd2e146b48a3bc" ] } }, @@ -1046,7 +1046,7 @@ "038cedc73097976fcb26f43537ffd5fa8d598b2da404e2c05439106ac636bc3e" ], "eigs_opt_num": [ - "5bd33573efec2133743c045bc9827a72a8c9ea73f15a185468495764b5c37ecd" + "a59259f1f1c8f200ce3f1c7e6f668b797fbfa1219686fb8f9078719cdc9d129e" ] } }, @@ -1687,7 +1687,7 @@ "bcb3999873c5e085e20e6370e6ae81ddaf01a610069729a2a12dd5d402df301c" ], "value_to_string": [ - "0162dd03ddc58d709c0752df0b991eebc305c3b058f78838f8f5ecdb0a1143dc" + "c5f081c068f1ae84bec75b29c9954f194376fbaa26f557bd596eb2bf889db46f" ] } }, @@ -2130,7 +2130,7 @@ "54d7497ad4bc951a7f8b8e0e690f901fcab06ed74c92050e4e68be8c36921372" ], "value_to_string": [ - "0162dd03ddc58d709c0752df0b991eebc305c3b058f78838f8f5ecdb0a1143dc" + "c5f081c068f1ae84bec75b29c9954f194376fbaa26f557bd596eb2bf889db46f" ] } }, @@ -2752,7 +2752,7 @@ "dce6eda1020469efb73a67c34f60fa7cc66c03c617d1fb4bc96aaea536346ae3" ], "value_to_string": [ - "0162dd03ddc58d709c0752df0b991eebc305c3b058f78838f8f5ecdb0a1143dc" + "c5f081c068f1ae84bec75b29c9954f194376fbaa26f557bd596eb2bf889db46f" ] } }, @@ -2844,7 +2844,7 @@ "5556903dd96019edcff890e180e1e8f04ffc7101fe80b5fcf93520968ea3c180" ], "value_to_string": [ - "0162dd03ddc58d709c0752df0b991eebc305c3b058f78838f8f5ecdb0a1143dc" + "c5f081c068f1ae84bec75b29c9954f194376fbaa26f557bd596eb2bf889db46f" ] } },