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.
This commit is contained in:
Malte Ubl
2026-10-16 06:18:56 -07:00
parent 8038a7fdeb
commit cf210c4775
4 changed files with 29 additions and 8 deletions
@@ -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", () => {
+12 -4
View File
@@ -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`,
+11 -3
View File
@@ -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();
+1
View File
@@ -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++) {