From cf210c47752cbc1681ee6bf050a2b9fab35d62b1 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Fri, 16 Oct 2026 06:12:24 -0700 Subject: [PATCH] Skip intact documents in compiled dyn releases and new dyn writers Main now inlines dyn releases in compiled code (shapes.ts). Its fast path buffered every surviving array and object, so compiled releases of a parsed document's containers (the `parsed[i]` temporaries) bypassed the intact-region check in scr_dyn_release. Arrays and objects now keep the inline already-buffered check and otherwise call scr_dyn_release_survivor, which skips containers of intact documents (ScrDynRegion) like scr_dyn_release does. Functions and proxies keep the inline enqueue. Two writers that main added install tables or prototypes on existing objects directly: scr_dyn_symbol_init_own and scr_dyn_dirent_finish. Both now clear the object's region first, keeping the invariant that intact containers have no symbol tables or prototypes. --- .../compiler/src/backend/llvm/shapes.test.ts | 6 +++++- packages/compiler/src/backend/llvm/shapes.ts | 16 ++++++++++++---- packages/runtime/src/scr_json.c | 14 +++++++++++--- packages/runtime/src/scr_symbol.c | 1 + 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/compiler/src/backend/llvm/shapes.test.ts b/packages/compiler/src/backend/llvm/shapes.test.ts index 05797ebbb..8c2c46541 100644 --- a/packages/compiler/src/backend/llvm/shapes.test.ts +++ b/packages/compiler/src/backend/llvm/shapes.test.ts @@ -144,7 +144,11 @@ describe("LLVM runtime RC symbols", () => { // The dyn fast path buffers surviving values by kind, as scr_dyn_release does. const ir = emitInlineRcHelpers(host).join("\n"); expect(ir).toContain("call void @scr_dyn_release_survivor(ptr %o)"); - expect(ir).toMatch(/switch i32 %kind, label %done \[ i32 4, label %root/); + expect(ir).toMatch( + /switch i32 %kind, label %done \[ i32 4, label %container i32 5, label %container i32 8, label %root/, + ); + // Arrays and objects not yet buffered ask the runtime, which skips intact documents. + expect(ir).toMatch(/br i1 %cqueued, label %done, label %survivor/); }); test("sanitized builds call the fast-path helpers out of line", () => { diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index 4e8b5d819..c3d4aff8d 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -283,7 +283,8 @@ interface InlineRcFamily { * * The dyn release buffers a surviving value by kind (scr_dyn_release): * the fast path decides the always and never cases and calls - * scr_dyn_release_survivor for kinds whose candidacy is per value. + * scr_dyn_release_survivor for kinds whose candidacy is per value, + * including arrays and objects of intact documents (ScrDynRegion). * * Releases stay plain calls for: * - arr: array releases are the most numerous release sites and the @@ -552,12 +553,19 @@ function releaseHelper(host: ShapeHost, family: string, row: InlineRcFamily): st : family === "dyn" ? [ // scr_dyn_release buffers a surviving value by kind: always for - // arrays, objects, functions and proxies, per value for capsules - // and handles (scr_dyn_release_survivor), never for scalars. + // functions and proxies, per value for arrays and objects (not + // while their document is intact, ScrDynRegion), capsules and + // handles (scr_dyn_release_survivor), never for scalars. An + // array or object already in a candidate buffer stays inline. ` %kp = getelementptr i8, ptr %o, ${S} ${S === "i64" ? 8 : 4} ; ScrDyn.kind`, ` %kind = load i32, ptr %kp`, // SCR_DYN_ARR 4, OBJ 5, FUNC 8, PROXY 13; HANDLE 9, TYPED_REF 12. - ` switch i32 %kind, label %done [ i32 4, label %root i32 5, label %root i32 8, label %root i32 13, label %root i32 9, label %survivor i32 12, label %survivor ]`, + ` switch i32 %kind, label %done [ i32 4, label %container i32 5, label %container i32 8, label %root i32 13, label %root i32 9, label %survivor i32 12, label %survivor ]`, + `container:`, + ` %cbufp = getelementptr i8, ptr %o, ${S} -${host.cycleBufOffset}`, + ` %cbuf = load i32, ptr %cbufp`, + ` %cqueued = icmp slt i32 %cbuf, 0`, + ` br i1 %cqueued, label %done, label %survivor`, `survivor:`, ` call void @scr_dyn_release_survivor(ptr %o)`, ` br label %done`, diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 6c5eb97ee..188246762 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1086,10 +1086,17 @@ static void scr_dyn_gcfree(void *ptr) { scr_dyn_dispose(ptr, true); } static void scr_dyn_destroy(void *ptr) { scr_dyn_dispose(ptr, false); } -/* The cycle bookkeeping of a capsule or handle that survives a release. - * Compiled release fast paths decrement first and call this for the kinds - * whose candidacy depends on per-value state, matching scr_dyn_release. */ +/* The cycle bookkeeping of a value that survives a release. Compiled + * release fast paths decrement first and call this for the kinds whose + * candidacy depends on per-value state, matching scr_dyn_release: arrays + * and objects not yet buffered (skipped while their document is intact, + * ScrDynRegion), capsules and handles. */ void scr_dyn_release_survivor(ScrDyn *d) { + if (d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ) { + const ScrDynRegion *region = scr_dyn_region_of(d); + if (!region || !region->intact) scr_cyc_released(d); + return; + } if ((d->kind == SCR_DYN_TYPED_REF && (d->v.typed_ref.materialized || d->v.typed_ref.casts || d->v.typed_ref.traced || d->prototype || d->symbol_properties || d->symbol_keys)) || @@ -3904,6 +3911,7 @@ ScrDynEntry *scr_dyn_own_symbol_entry(const ScrDyn *object, const ScrDyn *key) { ScrStr *scr_dyn_symbol_storage_name(const ScrDyn *key) { return scr_dyn_symbol_storage_key(key); } void scr_dyn_symbol_init_own(ScrDyn *object, ScrDyn *key, const ScrStr *storage, ScrDyn *value) { + scr_dyn_region_store(object, NULL); /* intact containers have no symbol tables */ object->symbol_properties = scr_dyn_new_obj_null_proto(); scr_dyn_obj_set(object->symbol_properties, storage->data, storage->len, scr_dyn_retain(value)); object->symbol_keys = scr_dyn_new_arr(); diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index e3baf8bc8..b3d60d354 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -342,6 +342,7 @@ static void scr_dirent_init(void) { void scr_dyn_dirent_finish(ScrDyn *obj) { scr_dirent_init(); + scr_dyn_region_store(obj, NULL); /* a prototype store (ScrDynRegion) */ scr_dyn_release(obj->prototype); obj->prototype = scr_dyn_retain(scr_dirent_prototype); for (size_t i = 0; i < obj->v.obj.len; i++) {