mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-29 07:04:09 +08:00
fix fetch stream parity edge cases
This commit is contained in:
@@ -15,6 +15,31 @@ function streamTypedRefCommitAdapter(
|
||||
snapshot: string,
|
||||
defs: string[],
|
||||
): string {
|
||||
if (t.kind === "array") {
|
||||
const commit = `${snapshot}_commit`;
|
||||
E.walkerProtos.push(
|
||||
`static void ${commit}(void *sc_p, const ScrDyn *sc_d); /* commit live stream element ${typeKey(t)} */`,
|
||||
);
|
||||
defs.push(
|
||||
`static void ${commit}(void *sc_p, const ScrDyn *sc_d) {`,
|
||||
` ScrArr *sc_target = (ScrArr *)sc_p;`,
|
||||
` ScrArr *sc_next = ${E.dynCheckHelper(t)}(sc_d, NULL);`,
|
||||
` if (!sc_next) return;`,
|
||||
` size_t sc_target_len = sc_target->len;`,
|
||||
` size_t sc_target_cap = sc_target->cap;`,
|
||||
` uint64_t *sc_target_data = sc_target->data;`,
|
||||
` sc_target->len = sc_next->len;`,
|
||||
` sc_target->cap = sc_next->cap;`,
|
||||
` sc_target->data = sc_next->data;`,
|
||||
` sc_next->len = sc_target_len;`,
|
||||
` sc_next->cap = sc_target_cap;`,
|
||||
` sc_next->data = sc_target_data;`,
|
||||
` scr_arr_release(sc_next);`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
return `&${commit}`;
|
||||
}
|
||||
if (t.kind !== "record") return "NULL";
|
||||
const shape = E.recordsById.get(t.shapeId);
|
||||
if (!shape) {
|
||||
@@ -57,6 +82,132 @@ function streamTypedRefCommitAdapter(
|
||||
return `&${commit}`;
|
||||
}
|
||||
|
||||
interface StreamTypedRefAdapter {
|
||||
snapshot: string;
|
||||
commit: string;
|
||||
}
|
||||
|
||||
interface StreamTypedRefContext {
|
||||
prefix: string;
|
||||
defs: string[];
|
||||
adapters: Map<string, StreamTypedRefAdapter>;
|
||||
}
|
||||
|
||||
function streamTypedRefEligible(t: IrType): boolean {
|
||||
return t.kind === "record" || t.kind === "array";
|
||||
}
|
||||
|
||||
/** Build the live dyn view of one typed stream value. Mutable reference
|
||||
* children are capsules of their own, so a write through `v.child.x` or
|
||||
* `v.items[0]` commits directly to that child's static source instead of
|
||||
* disappearing into the parent's detached snapshot. */
|
||||
function streamTypedRefAdapter(
|
||||
E: CEmitter,
|
||||
t: IrType,
|
||||
ctx: StreamTypedRefContext,
|
||||
preferredSnapshot?: string,
|
||||
): StreamTypedRefAdapter {
|
||||
const key = typeKey(t);
|
||||
const existing = ctx.adapters.get(key);
|
||||
if (existing) return existing;
|
||||
const snapshot = preferredSnapshot ??
|
||||
`${ctx.prefix}_nested_${ctx.adapters.size}`;
|
||||
const adapter: StreamTypedRefAdapter = { snapshot, commit: "NULL" };
|
||||
/* Register before walking children: recursive record/array types refer
|
||||
* back to this prototype without recursively generating helpers. */
|
||||
ctx.adapters.set(key, adapter);
|
||||
E.walkerProtos.push(
|
||||
`static ScrDyn *${snapshot}(void *sc_p); /* materialize live stream value ${key} */`,
|
||||
);
|
||||
adapter.commit = streamTypedRefCommitAdapter(E, t, snapshot, ctx.defs);
|
||||
|
||||
const box = (child: IrType, expr: string): string => {
|
||||
if (!streamTypedRefEligible(child)) {
|
||||
return `${E.toDynHelper(child)}(${expr})`;
|
||||
}
|
||||
const nested = streamTypedRefAdapter(E, child, ctx);
|
||||
const rc = vAdapters(child);
|
||||
const childKey = typeKey(child);
|
||||
const keyLit = cStringLiteral(Buffer.from(childKey, "utf8"));
|
||||
return `scr_dyn_new_typed_ref(${expr}, &${rc.retain}, &${rc.release}, ${keyLit}, ${Buffer.byteLength(childKey, "utf8")}, &${nested.snapshot}, ${nested.commit})`;
|
||||
};
|
||||
|
||||
const lines = [
|
||||
`static ScrDyn *${snapshot}(void *sc_p) { /* materialize live stream value ${key} */`,
|
||||
` ${cDecl(t, "v")} = (${cType(t).trim()})sc_p;`,
|
||||
];
|
||||
if (t.kind === "record") {
|
||||
const shape = E.recordsById.get(t.shapeId);
|
||||
if (!shape) {
|
||||
throw new Error(
|
||||
`emitter bug: stream typed-ref materialize of unknown shape ${t.shapeId}`,
|
||||
);
|
||||
}
|
||||
const ordinary = shape.indexValue || shape.fields.some(
|
||||
(field) => field.name === "handleEvent" && field.type.kind === "func",
|
||||
);
|
||||
if (ordinary) {
|
||||
lines.push(` return ${E.toDynHelper(t)}(v);`, `}`, ``);
|
||||
ctx.defs.push(...lines);
|
||||
return adapter;
|
||||
}
|
||||
if (shape.tuple) {
|
||||
lines.push(` ScrDyn *d = scr_dyn_new_arr();`);
|
||||
const fields = [...shape.fields].sort(
|
||||
(a, b) => Number(a.name) - Number(b.name),
|
||||
);
|
||||
for (const field of fields) {
|
||||
lines.push(
|
||||
` scr_dyn_arr_push(d, ${box(field.type, `v->${mangleField(field.name)}`)});`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
lines.push(` ScrDyn *d = scr_dyn_new_obj();`);
|
||||
const byName = new Map(shape.fields.map((field) => [field.name, field]));
|
||||
const order = shape.declaredOrder ?? shape.fields.map((field) => field.name);
|
||||
const inOrder = new Set(order);
|
||||
const fields = [
|
||||
...order.map((name) => byName.get(name)).filter((field) => field !== undefined),
|
||||
...shape.fields.filter((field) => !inOrder.has(field.name)),
|
||||
];
|
||||
for (const field of fields) {
|
||||
const keyLit = cStringLiteral(Buffer.from(field.name, "utf8"));
|
||||
lines.push(
|
||||
` scr_dyn_obj_set(d, ${keyLit}, ${Buffer.byteLength(field.name, "utf8")}, ${box(field.type, `v->${mangleField(field.name)}`)});`,
|
||||
);
|
||||
}
|
||||
}
|
||||
lines.push(` return d;`);
|
||||
} else if (t.kind === "array") {
|
||||
const elem = t.elem;
|
||||
lines.push(
|
||||
` ScrDyn *d = scr_dyn_new_arr();`,
|
||||
` for (size_t sc_i = 0; sc_i < v->len; sc_i++) {`,
|
||||
);
|
||||
if (elem.kind === "f64") {
|
||||
lines.push(
|
||||
` scr_dyn_arr_push(d, ${box(elem, "scr_arr_get_f64(v, (double)sc_i)")});`,
|
||||
);
|
||||
} else if (elem.kind === "bool") {
|
||||
lines.push(
|
||||
` scr_dyn_arr_push(d, ${box(elem, "scr_arr_get_bool(v, (double)sc_i)")});`,
|
||||
);
|
||||
} else {
|
||||
lines.push(
|
||||
` ${cDecl(elem, "sc_value")} = (${cType(elem).trim()})scr_arr_get_ref(v, (double)sc_i);`,
|
||||
` scr_dyn_arr_push(d, ${box(elem, "sc_value")});`,
|
||||
` ${releaseCallC(elem, "sc_value")};`,
|
||||
);
|
||||
}
|
||||
lines.push(` }`, ` return d;`);
|
||||
} else {
|
||||
lines.push(` return ${E.toDynHelper(t)}(v);`);
|
||||
}
|
||||
lines.push(`}`, ``);
|
||||
ctx.defs.push(...lines);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
function streamFromArrayAdapter(
|
||||
E: CEmitter,
|
||||
t: IrType & { kind: "array" },
|
||||
@@ -88,15 +239,25 @@ function streamFromArrayAdapter(
|
||||
const d: string[] = [];
|
||||
let commit = "NULL";
|
||||
if (typedRef) {
|
||||
const snapshotSig = `static ScrDyn *${snapshot}(void *sc_p)`;
|
||||
E.walkerProtos.push(`${snapshotSig}; /* materialize stream element ${key} */`);
|
||||
d.push(
|
||||
`${snapshotSig} {`,
|
||||
` return ${E.toDynHelper(elem)}((${cType(elem).trim()})sc_p);`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
commit = streamTypedRefCommitAdapter(E, elem, snapshot, d);
|
||||
if (streamTypedRefEligible(elem)) {
|
||||
const adapter = streamTypedRefAdapter(
|
||||
E,
|
||||
elem,
|
||||
{ prefix: snapshot, defs: d, adapters: new Map() },
|
||||
snapshot,
|
||||
);
|
||||
commit = adapter.commit;
|
||||
} else {
|
||||
const snapshotSig = `static ScrDyn *${snapshot}(void *sc_p)`;
|
||||
E.walkerProtos.push(`${snapshotSig}; /* materialize stream element ${key} */`);
|
||||
d.push(
|
||||
`${snapshotSig} {`,
|
||||
` return ${E.toDynHelper(elem)}((${cType(elem).trim()})sc_p);`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
commit = streamTypedRefCommitAdapter(E, elem, snapshot, d);
|
||||
}
|
||||
}
|
||||
const unionCommits = new Map<number, string>();
|
||||
for (const { arm, tag } of unionRefArms) {
|
||||
|
||||
@@ -1130,10 +1130,48 @@ export function jsonWriteHelper(E: CEmitter, t: IrType): string {
|
||||
d.push(
|
||||
` if (d && d->kind == SCR_DYN_TYPED_REF) {`,
|
||||
` ${cDecl(t, "sc_cached")} = (${cType(t).trim()})scr_dyn_typed_ref_cached_cast(d, ${keyLit}, ${keyLen});`,
|
||||
` if (sc_cached) return sc_cached;`,
|
||||
);
|
||||
if (t.kind !== "record") {
|
||||
d.push(` if (sc_cached) return sc_cached;`);
|
||||
}
|
||||
d.push(
|
||||
` ScrDyn *sc_materialized = scr_dyn_typed_ref_materialize(d);`,
|
||||
` ${cDecl(t, "sc_out")} = ${name}(sc_materialized, path);`,
|
||||
` scr_dyn_release(sc_materialized);`,
|
||||
);
|
||||
if (t.kind === "record") {
|
||||
const shape = E.recordsById.get(t.shapeId);
|
||||
if (!shape) {
|
||||
throw new Error(
|
||||
`emitter bug: cached typed-ref cast of unknown shape ${t.shapeId}`,
|
||||
);
|
||||
}
|
||||
d.push(` if (sc_out && sc_cached) {`);
|
||||
for (const field of shape.fields) {
|
||||
const member = mangleField(field.name);
|
||||
d.push(
|
||||
` {`,
|
||||
` ${cDecl(field.type, "sc_old")} = sc_cached->${member};`,
|
||||
` sc_cached->${member} = sc_out->${member};`,
|
||||
` sc_out->${member} = sc_old;`,
|
||||
` }`,
|
||||
);
|
||||
}
|
||||
if (shape.indexValue) {
|
||||
d.push(
|
||||
` ScrMap *sc_old = sc_cached->${OVERFLOW_MEMBER};`,
|
||||
` sc_cached->${OVERFLOW_MEMBER} = sc_out->${OVERFLOW_MEMBER};`,
|
||||
` sc_out->${OVERFLOW_MEMBER} = sc_old;`,
|
||||
);
|
||||
}
|
||||
d.push(
|
||||
` ${releaseCallC(t, "sc_out")};`,
|
||||
` return sc_cached;`,
|
||||
` }`,
|
||||
` if (sc_cached) ${releaseCallC(t, "sc_cached")};`,
|
||||
);
|
||||
}
|
||||
d.push(
|
||||
` if (sc_out) {`,
|
||||
` scr_dyn_typed_ref_cache_cast((ScrDyn *)d, ${keyLit}, ${keyLen}, sc_out, &${rc.retain}, &${rc.release});`,
|
||||
` }`,
|
||||
@@ -1344,10 +1382,21 @@ export function jsonWriteHelper(E: CEmitter, t: IrType): string {
|
||||
const keyLen = Buffer.byteLength(key, "utf8");
|
||||
const rc = vAdapters(t);
|
||||
d.push(` ${cDecl(t, "sc_cached")} = (${cType(t).trim()})scr_dyn_typed_ref_cached_cast(d, ${keyLit}, ${keyLen});`);
|
||||
d.push(` if (sc_cached) return sc_cached;`);
|
||||
d.push(` ScrDyn *sc_materialized = scr_dyn_typed_ref_materialize(d);`);
|
||||
d.push(` ${cDecl(t, "sc_out")} = ${name}(sc_materialized, path);`);
|
||||
d.push(` scr_dyn_release(sc_materialized);`);
|
||||
d.push(` if (sc_out && sc_cached) {`);
|
||||
d.push(` size_t sc_cached_rc = sc_cached->rc;`);
|
||||
d.push(` size_t sc_out_rc = sc_out->rc;`);
|
||||
d.push(` ScrUnion sc_old = *sc_cached;`);
|
||||
d.push(` *sc_cached = *sc_out;`);
|
||||
d.push(` sc_cached->rc = sc_cached_rc;`);
|
||||
d.push(` *sc_out = sc_old;`);
|
||||
d.push(` sc_out->rc = sc_out_rc;`);
|
||||
d.push(` scr_union_release(sc_out);`);
|
||||
d.push(` return sc_cached;`);
|
||||
d.push(` }`);
|
||||
d.push(` if (sc_cached) scr_union_release(sc_cached);`);
|
||||
d.push(` if (sc_out) {`);
|
||||
d.push(` scr_dyn_typed_ref_cache_cast((ScrDyn *)d, ${keyLit}, ${keyLen}, sc_out, &${rc.retain}, &${rc.release});`);
|
||||
d.push(` }`);
|
||||
|
||||
@@ -582,12 +582,14 @@ export class LlDyn {
|
||||
B.line(`${cached} = call ptr @scr_dyn_typed_ref_cached_cast(ptr %d, ptr ${host.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")})`);
|
||||
const hasCached = B.tmp();
|
||||
B.line(`${hasCached} = icmp ne ptr ${cached}, null`);
|
||||
const lCached = B.newLabel("dc.tr.hit");
|
||||
const lMaterialize = B.newLabel("dc.tr.mat");
|
||||
B.condBr(hasCached, lCached, lMaterialize);
|
||||
B.startBlock(lCached);
|
||||
B.terminate(`ret ptr ${cached}`);
|
||||
B.startBlock(lMaterialize);
|
||||
if (t.kind !== "record") {
|
||||
const lCached = B.newLabel("dc.tr.hit");
|
||||
const lMaterialize = B.newLabel("dc.tr.mat");
|
||||
B.condBr(hasCached, lCached, lMaterialize);
|
||||
B.startBlock(lCached);
|
||||
B.terminate(`ret ptr ${cached}`);
|
||||
B.startBlock(lMaterialize);
|
||||
}
|
||||
const materialized = B.tmp();
|
||||
const checked = B.tmp();
|
||||
B.line(`${materialized} = call ptr @scr_dyn_typed_ref_materialize(ptr %d)`);
|
||||
@@ -595,14 +597,71 @@ export class LlDyn {
|
||||
B.line(`call void @scr_dyn_release_v(ptr ${materialized})`);
|
||||
const ok = B.tmp();
|
||||
B.line(`${ok} = icmp ne ptr ${checked}, null`);
|
||||
const lCache = B.newLabel("dc.tr.put");
|
||||
const lReturn = B.newLabel("dc.tr.ret");
|
||||
B.condBr(ok, lCache, lReturn);
|
||||
B.startBlock(lCache);
|
||||
B.line(`call void @scr_dyn_typed_ref_cache_cast(ptr %d, ptr ${host.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")}, ptr ${checked}, ptr ${rc.retain}, ptr ${rc.release})`);
|
||||
B.br(lReturn);
|
||||
B.startBlock(lReturn);
|
||||
B.terminate(`ret ptr ${checked}`);
|
||||
if (t.kind === "record") {
|
||||
const shape = host.recordsById.get(t.shapeId);
|
||||
if (!shape) {
|
||||
throw new Error(
|
||||
`llvm emitter bug: cached typed-ref cast of unknown shape ${t.shapeId}`,
|
||||
);
|
||||
}
|
||||
const lOk = B.newLabel("dc.tr.ok");
|
||||
const lBad = B.newLabel("dc.tr.bad");
|
||||
B.condBr(ok, lOk, lBad);
|
||||
B.startBlock(lBad);
|
||||
const lBadDrop = B.newLabel("dc.tr.bad.drop");
|
||||
const lBadRet = B.newLabel("dc.tr.bad.ret");
|
||||
B.condBr(hasCached, lBadDrop, lBadRet);
|
||||
B.startBlock(lBadDrop);
|
||||
B.line(`call void ${releaseSym(host, t)}(ptr ${cached})`);
|
||||
B.br(lBadRet);
|
||||
B.startBlock(lBadRet);
|
||||
B.terminate(`ret ptr null`);
|
||||
B.startBlock(lOk);
|
||||
const lRefresh = B.newLabel("dc.tr.refresh");
|
||||
const lCache = B.newLabel("dc.tr.put");
|
||||
B.condBr(hasCached, lRefresh, lCache);
|
||||
B.startBlock(lRefresh);
|
||||
const members = [
|
||||
...shape.fields.map((field, index) => ({
|
||||
index: index + 1,
|
||||
type: llFieldType(field.type),
|
||||
name: field.name,
|
||||
})),
|
||||
...(shape.indexValue
|
||||
? [{
|
||||
index: shape.fields.length + 1,
|
||||
type: "ptr" as const,
|
||||
name: "[key: string] overflow",
|
||||
}]
|
||||
: []),
|
||||
];
|
||||
members.forEach((member) => {
|
||||
const cachedPtr = B.tmp();
|
||||
const checkedPtr = B.tmp();
|
||||
const oldValue = B.tmp();
|
||||
const newValue = B.tmp();
|
||||
B.line(`${cachedPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr ${cached}, i64 0, i32 ${member.index}`);
|
||||
B.line(`${checkedPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr ${checked}, i64 0, i32 ${member.index}`);
|
||||
B.line(`${oldValue} = load ${member.type}, ptr ${cachedPtr} ; ${member.name}`);
|
||||
B.line(`${newValue} = load ${member.type}, ptr ${checkedPtr}`);
|
||||
B.line(`store ${member.type} ${newValue}, ptr ${cachedPtr}`);
|
||||
B.line(`store ${member.type} ${oldValue}, ptr ${checkedPtr}`);
|
||||
});
|
||||
B.line(`call void ${releaseSym(host, t)}(ptr ${checked})`);
|
||||
B.terminate(`ret ptr ${cached}`);
|
||||
B.startBlock(lCache);
|
||||
B.line(`call void @scr_dyn_typed_ref_cache_cast(ptr %d, ptr ${host.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")}, ptr ${checked}, ptr ${rc.retain}, ptr ${rc.release})`);
|
||||
B.terminate(`ret ptr ${checked}`);
|
||||
} else {
|
||||
const lCache = B.newLabel("dc.tr.put");
|
||||
const lReturn = B.newLabel("dc.tr.ret");
|
||||
B.condBr(ok, lCache, lReturn);
|
||||
B.startBlock(lCache);
|
||||
B.line(`call void @scr_dyn_typed_ref_cache_cast(ptr %d, ptr ${host.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")}, ptr ${checked}, ptr ${rc.retain}, ptr ${rc.release})`);
|
||||
B.br(lReturn);
|
||||
B.startBlock(lReturn);
|
||||
B.terminate(`ret ptr ${checked}`);
|
||||
}
|
||||
B.startBlock(lPlain);
|
||||
}
|
||||
}
|
||||
@@ -977,6 +1036,7 @@ export class LlDyn {
|
||||
host.declare(`declare ptr @scr_dyn_typed_ref_cached_cast(ptr, ptr, i64)`);
|
||||
host.declare(`declare void @scr_dyn_typed_ref_cache_cast(ptr, ptr, i64, ptr, ptr, ptr)`);
|
||||
host.declare(`declare void @scr_dyn_release_v(ptr)`);
|
||||
host.declare(`declare void @scr_union_release(ptr)`);
|
||||
const rc = vAdapters(host, t);
|
||||
const kind = this.kindOf(B, "%d");
|
||||
const capsule = B.tmp();
|
||||
@@ -989,12 +1049,6 @@ export class LlDyn {
|
||||
B.line(`${cached} = call ptr @scr_dyn_typed_ref_cached_cast(ptr %d, ptr ${host.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")})`);
|
||||
const hasCached = B.tmp();
|
||||
B.line(`${hasCached} = icmp ne ptr ${cached}, null`);
|
||||
const lCached = B.newLabel("dcu.hit");
|
||||
const lMaterialize = B.newLabel("dcu.mat");
|
||||
B.condBr(hasCached, lCached, lMaterialize);
|
||||
B.startBlock(lCached);
|
||||
B.terminate(`ret ptr ${cached}`);
|
||||
B.startBlock(lMaterialize);
|
||||
const materialized = B.tmp();
|
||||
const checked = B.tmp();
|
||||
B.line(`${materialized} = call ptr @scr_dyn_typed_ref_materialize(ptr %d)`);
|
||||
@@ -1002,13 +1056,45 @@ export class LlDyn {
|
||||
B.line(`call void @scr_dyn_release_v(ptr ${materialized})`);
|
||||
const ok = B.tmp();
|
||||
B.line(`${ok} = icmp ne ptr ${checked}, null`);
|
||||
const lOk = B.newLabel("dcu.ok");
|
||||
const lBad = B.newLabel("dcu.bad");
|
||||
B.condBr(ok, lOk, lBad);
|
||||
B.startBlock(lBad);
|
||||
const lBadDrop = B.newLabel("dcu.bad.drop");
|
||||
const lBadRet = B.newLabel("dcu.bad.ret");
|
||||
B.condBr(hasCached, lBadDrop, lBadRet);
|
||||
B.startBlock(lBadDrop);
|
||||
B.line(`call void @scr_union_release(ptr ${cached})`);
|
||||
B.br(lBadRet);
|
||||
B.startBlock(lBadRet);
|
||||
B.terminate(`ret ptr null`);
|
||||
B.startBlock(lOk);
|
||||
const lRefresh = B.newLabel("dcu.refresh");
|
||||
const lCache = B.newLabel("dcu.put");
|
||||
const lReturn = B.newLabel("dcu.ret");
|
||||
B.condBr(ok, lCache, lReturn);
|
||||
B.condBr(hasCached, lRefresh, lCache);
|
||||
B.startBlock(lRefresh);
|
||||
([
|
||||
[1, "i32", "tag"],
|
||||
[2, "ptr", "retain"],
|
||||
[3, "ptr", "release"],
|
||||
[4, "ptr", "trace"],
|
||||
[5, "i64", "slot"],
|
||||
] as const).forEach(([index, fieldType, fieldName]) => {
|
||||
const cachedPtr = B.tmp();
|
||||
const checkedPtr = B.tmp();
|
||||
const oldValue = B.tmp();
|
||||
const newValue = B.tmp();
|
||||
B.line(`${cachedPtr} = getelementptr inbounds %ScrUnion, ptr ${cached}, i64 0, i32 ${index}`);
|
||||
B.line(`${checkedPtr} = getelementptr inbounds %ScrUnion, ptr ${checked}, i64 0, i32 ${index}`);
|
||||
B.line(`${oldValue} = load ${fieldType}, ptr ${cachedPtr} ; ${fieldName}`);
|
||||
B.line(`${newValue} = load ${fieldType}, ptr ${checkedPtr}`);
|
||||
B.line(`store ${fieldType} ${newValue}, ptr ${cachedPtr}`);
|
||||
B.line(`store ${fieldType} ${oldValue}, ptr ${checkedPtr}`);
|
||||
});
|
||||
B.line(`call void @scr_union_release(ptr ${checked})`);
|
||||
B.terminate(`ret ptr ${cached}`);
|
||||
B.startBlock(lCache);
|
||||
B.line(`call void @scr_dyn_typed_ref_cache_cast(ptr %d, ptr ${host.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")}, ptr ${checked}, ptr ${rc.retain}, ptr ${rc.release})`);
|
||||
B.br(lReturn);
|
||||
B.startBlock(lReturn);
|
||||
B.terminate(`ret ptr ${checked}`);
|
||||
B.startBlock(lFail);
|
||||
B.line(`call void @scr_dyn_check_fail(ptr %path, ptr ${want}, ptr %d)`);
|
||||
|
||||
@@ -88,6 +88,16 @@ import {
|
||||
import { DK, LlDyn } from "./dyn.js";
|
||||
import { LlvmUnsupportedError } from "./unsupported.js";
|
||||
import { LlWalkers } from "./walkers.js";
|
||||
|
||||
interface LlStreamTypedRefAdapter {
|
||||
snapshot: string;
|
||||
commit: string;
|
||||
}
|
||||
|
||||
interface LlStreamTypedRefContext {
|
||||
prefix: string;
|
||||
adapters: Map<string, LlStreamTypedRefAdapter>;
|
||||
}
|
||||
import {
|
||||
arrNewCall,
|
||||
boxAccess,
|
||||
@@ -9980,6 +9990,44 @@ class LlEmitter {
|
||||
t: IrType,
|
||||
snapshot: string,
|
||||
): string {
|
||||
if (t.kind === "array") {
|
||||
const commit = `${snapshot}_commit`;
|
||||
const check = this.dyn.dynCheckHelper(t);
|
||||
this.declare(`declare void @scr_arr_release(ptr)`);
|
||||
this.resolveThunkDefs.push(
|
||||
`define internal void @${commit}(ptr %target, ptr %d) ${FN_ATTRS} { ; commit live stream element ${typeKey(t)}`,
|
||||
`entry:`,
|
||||
` %next = call ptr @${check}(ptr %d, ptr null)`,
|
||||
` %missing = icmp eq ptr %next, null`,
|
||||
` br i1 %missing, label %done, label %swap`,
|
||||
`swap:`,
|
||||
` %target_len_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 1`,
|
||||
` %next_len_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 1`,
|
||||
` %target_len = load i64, ptr %target_len_ptr`,
|
||||
` %next_len = load i64, ptr %next_len_ptr`,
|
||||
` store i64 %next_len, ptr %target_len_ptr`,
|
||||
` store i64 %target_len, ptr %next_len_ptr`,
|
||||
` %target_cap_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 2`,
|
||||
` %next_cap_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 2`,
|
||||
` %target_cap = load i64, ptr %target_cap_ptr`,
|
||||
` %next_cap = load i64, ptr %next_cap_ptr`,
|
||||
` store i64 %next_cap, ptr %target_cap_ptr`,
|
||||
` store i64 %target_cap, ptr %next_cap_ptr`,
|
||||
` %target_data_ptr = getelementptr inbounds %ScrArr, ptr %target, i64 0, i32 7`,
|
||||
` %next_data_ptr = getelementptr inbounds %ScrArr, ptr %next, i64 0, i32 7`,
|
||||
` %target_data = load ptr, ptr %target_data_ptr`,
|
||||
` %next_data = load ptr, ptr %next_data_ptr`,
|
||||
` store ptr %next_data, ptr %target_data_ptr`,
|
||||
` store ptr %target_data, ptr %next_data_ptr`,
|
||||
` call void @scr_arr_release(ptr %next)`,
|
||||
` br label %done`,
|
||||
`done:`,
|
||||
` ret void`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
return `@${commit}`;
|
||||
}
|
||||
if (t.kind !== "record") return "null";
|
||||
const shape = this.recordsById.get(t.shapeId);
|
||||
if (!shape) {
|
||||
@@ -10033,6 +10081,188 @@ class LlEmitter {
|
||||
return `@${commit}`;
|
||||
}
|
||||
|
||||
private streamTypedRefEligible(t: IrType): boolean {
|
||||
return t.kind === "record" || t.kind === "array";
|
||||
}
|
||||
|
||||
private streamTypedRefBoxValue(
|
||||
B: BlockBuilder,
|
||||
t: IrType,
|
||||
value: string,
|
||||
ctx: LlStreamTypedRefContext,
|
||||
): string {
|
||||
const boxed = B.tmp();
|
||||
if (!this.streamTypedRefEligible(t)) {
|
||||
const valueTy = t.kind === "f64"
|
||||
? "double"
|
||||
: t.kind === "bool"
|
||||
? "i1"
|
||||
: "ptr";
|
||||
B.line(
|
||||
`${boxed} = call ptr @${this.dyn.toDynHelper(t)}(${valueTy} ${value})`,
|
||||
);
|
||||
return boxed;
|
||||
}
|
||||
const nested = this.streamTypedRefMaterializeAdapter(t, ctx);
|
||||
const rc = vAdapters(this, t);
|
||||
const key = typeKey(t);
|
||||
this.declare(
|
||||
`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, i64, ptr, ptr)`,
|
||||
);
|
||||
B.line(
|
||||
`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${value}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(key)}, i64 ${Buffer.byteLength(key, "utf8")}, ptr @${nested.snapshot}, ptr ${nested.commit})`,
|
||||
);
|
||||
return boxed;
|
||||
}
|
||||
|
||||
/** LLVM twin of the C stream materializer: mutable reference children
|
||||
* are their own typed capsules, so writes through a nested alias commit
|
||||
* to the nested static source immediately. */
|
||||
private streamTypedRefMaterializeAdapter(
|
||||
t: IrType,
|
||||
ctx: LlStreamTypedRefContext,
|
||||
preferredSnapshot?: string,
|
||||
): LlStreamTypedRefAdapter {
|
||||
const key = typeKey(t);
|
||||
const existing = ctx.adapters.get(key);
|
||||
if (existing) return existing;
|
||||
const snapshot = preferredSnapshot ??
|
||||
`${ctx.prefix}_nested_${ctx.adapters.size}`;
|
||||
const adapter: LlStreamTypedRefAdapter = { snapshot, commit: "null" };
|
||||
ctx.adapters.set(key, adapter);
|
||||
adapter.commit = this.streamTypedRefCommitAdapter(t, snapshot);
|
||||
const B = new BlockBuilder();
|
||||
|
||||
if (t.kind === "record") {
|
||||
const shape = this.recordsById.get(t.shapeId);
|
||||
if (!shape) {
|
||||
throw new Error(
|
||||
`llvm emitter bug: stream typed-ref materialize of unknown shape ${t.shapeId}`,
|
||||
);
|
||||
}
|
||||
/* Keep the ordinary converter for index-signature/listener records;
|
||||
* their source-identity and overflow walks have extra contracts.
|
||||
* Declared-field records and tuples cover the live stream values. */
|
||||
const ordinary = shape.indexValue || shape.fields.some(
|
||||
(field) => field.name === "handleEvent" && field.type.kind === "func",
|
||||
);
|
||||
if (ordinary) {
|
||||
const out = B.tmp();
|
||||
B.line(`${out} = call ptr @${this.dyn.toDynHelper(t)}(ptr %p)`);
|
||||
B.terminate(`ret ptr ${out}`);
|
||||
} else if (shape.tuple) {
|
||||
this.declare(`declare ptr @scr_dyn_new_arr()`);
|
||||
this.declare(`declare void @scr_dyn_arr_push(ptr, ptr)`);
|
||||
const out = B.tmp();
|
||||
B.line(`${out} = call ptr @scr_dyn_new_arr()`);
|
||||
const fields = [...shape.fields].sort(
|
||||
(a, b) => Number(a.name) - Number(b.name),
|
||||
);
|
||||
for (const field of fields) {
|
||||
const index = shape.fields.indexOf(field) + 1;
|
||||
const fieldPtr = B.tmp();
|
||||
const fieldValue = B.tmp();
|
||||
B.line(`${fieldPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr %p, i64 0, i32 ${index}`);
|
||||
B.line(`${fieldValue} = load ${llFieldType(field.type)}, ptr ${fieldPtr}`);
|
||||
const boxed = this.streamTypedRefBoxValue(
|
||||
B,
|
||||
field.type,
|
||||
fieldValue,
|
||||
ctx,
|
||||
);
|
||||
B.line(`call void @scr_dyn_arr_push(ptr ${out}, ptr ${boxed})`);
|
||||
}
|
||||
B.terminate(`ret ptr ${out}`);
|
||||
} else {
|
||||
this.declare(`declare ptr @scr_dyn_new_obj()`);
|
||||
this.declare(`declare void @scr_dyn_obj_set(ptr, ptr, i64, ptr)`);
|
||||
const out = B.tmp();
|
||||
B.line(`${out} = call ptr @scr_dyn_new_obj()`);
|
||||
const byName = new Map(shape.fields.map((field) => [field.name, field]));
|
||||
const order = shape.declaredOrder ?? shape.fields.map((field) => field.name);
|
||||
const inOrder = new Set(order);
|
||||
const fields = [
|
||||
...order.map((name) => byName.get(name)).filter((field) => field !== undefined),
|
||||
...shape.fields.filter((field) => !inOrder.has(field.name)),
|
||||
];
|
||||
for (const field of fields) {
|
||||
const index = shape.fields.indexOf(field) + 1;
|
||||
const fieldPtr = B.tmp();
|
||||
const fieldValue = B.tmp();
|
||||
B.line(`${fieldPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr %p, i64 0, i32 ${index}`);
|
||||
B.line(`${fieldValue} = load ${llFieldType(field.type)}, ptr ${fieldPtr}`);
|
||||
const boxed = this.streamTypedRefBoxValue(
|
||||
B,
|
||||
field.type,
|
||||
fieldValue,
|
||||
ctx,
|
||||
);
|
||||
B.line(`call void @scr_dyn_obj_set(ptr ${out}, ptr ${this.cstr(field.name)}, i64 ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`);
|
||||
}
|
||||
B.terminate(`ret ptr ${out}`);
|
||||
}
|
||||
} else if (t.kind === "array") {
|
||||
const elem = t.elem;
|
||||
this.declare(`declare ptr @scr_dyn_new_arr()`);
|
||||
this.declare(`declare void @scr_dyn_arr_push(ptr, ptr)`);
|
||||
this.declare(`declare double @scr_arr_len(ptr)`);
|
||||
const out = B.tmp();
|
||||
B.line(`${out} = call ptr @scr_dyn_new_arr()`);
|
||||
const len = B.tmp();
|
||||
B.line(`${len} = call double @scr_arr_len(ptr %p)`);
|
||||
const iSlot = B.slot();
|
||||
B.entryAllocas.push(`${iSlot} = alloca double`);
|
||||
B.line(`store double ${f64Lit(0)}, ptr ${iSlot}`);
|
||||
const condition = B.newLabel("strm.c");
|
||||
const body = B.newLabel("strm.b");
|
||||
const done = B.newLabel("strm.e");
|
||||
B.br(condition);
|
||||
B.startBlock(condition);
|
||||
const index = B.tmp();
|
||||
const more = B.tmp();
|
||||
B.line(`${index} = load double, ptr ${iSlot}`);
|
||||
B.line(`${more} = fcmp olt double ${index}, ${len}`);
|
||||
B.condBr(more, body, done);
|
||||
B.startBlock(body);
|
||||
let value: string;
|
||||
if (elem.kind === "f64" || elem.kind === "bool") {
|
||||
const valueTy = elem.kind === "f64" ? "double" : "i1";
|
||||
this.declare(
|
||||
`declare ${elem.kind === "bool" ? "zeroext i1" : valueTy} @scr_arr_get_${elem.kind}(ptr, double)`,
|
||||
);
|
||||
value = B.tmp();
|
||||
B.line(`${value} = call ${valueTy} @scr_arr_get_${elem.kind}(ptr %p, double ${index})`);
|
||||
} else {
|
||||
this.declare(`declare ptr @scr_arr_get_ref(ptr, double)`);
|
||||
value = B.tmp();
|
||||
B.line(`${value} = call ptr @scr_arr_get_ref(ptr %p, double ${index}) ; +1`);
|
||||
}
|
||||
const boxed = this.streamTypedRefBoxValue(B, elem, value, ctx);
|
||||
B.line(`call void @scr_dyn_arr_push(ptr ${out}, ptr ${boxed})`);
|
||||
if (isRefCounted(elem)) {
|
||||
B.line(`call void ${releaseSym(this, elem)}(ptr ${value})`);
|
||||
}
|
||||
const next = B.tmp();
|
||||
B.line(`${next} = fadd double ${index}, ${f64Lit(1)}`);
|
||||
B.line(`store double ${next}, ptr ${iSlot}`);
|
||||
B.br(condition);
|
||||
B.startBlock(done);
|
||||
B.terminate(`ret ptr ${out}`);
|
||||
} else {
|
||||
const out = B.tmp();
|
||||
B.line(`${out} = call ptr @${this.dyn.toDynHelper(t)}(ptr %p)`);
|
||||
B.terminate(`ret ptr ${out}`);
|
||||
}
|
||||
|
||||
this.resolveThunkDefs.push(
|
||||
`define internal ptr @${snapshot}(ptr %p) ${FN_ATTRS} { ; materialize live stream value ${key}`,
|
||||
B.render(),
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
private streamFromArrayAdapter(
|
||||
t: IrType & { kind: "array" },
|
||||
): string {
|
||||
@@ -10151,21 +10381,30 @@ class LlEmitter {
|
||||
boxed = B.tmp();
|
||||
const rc = vAdapters(this, elem);
|
||||
const keyPtr = this.cstr(key);
|
||||
const commit = this.streamTypedRefCommitAdapter(elem, snapshot);
|
||||
let commit: string;
|
||||
if (this.streamTypedRefEligible(elem)) {
|
||||
commit = this.streamTypedRefMaterializeAdapter(
|
||||
elem,
|
||||
{ prefix: snapshot, adapters: new Map() },
|
||||
snapshot,
|
||||
).commit;
|
||||
} else {
|
||||
commit = this.streamTypedRefCommitAdapter(elem, snapshot);
|
||||
this.resolveThunkDefs.push(
|
||||
`define internal ptr @${snapshot}(ptr %p) ${FN_ATTRS} { ; materialize stream element ${key}`,
|
||||
`entry:`,
|
||||
` %d = call ptr @${this.dyn.toDynHelper(elem)}(ptr %p)`,
|
||||
` ret ptr %d`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
}
|
||||
this.declare(
|
||||
`declare ptr @scr_dyn_new_typed_ref(ptr, ptr, ptr, ptr, i64, ptr, ptr)`,
|
||||
);
|
||||
B.line(
|
||||
`${boxed} = call ptr @scr_dyn_new_typed_ref(ptr ${value}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${keyPtr}, i64 ${Buffer.byteLength(key, "utf8")}, ptr @${snapshot}, ptr ${commit})`,
|
||||
);
|
||||
this.resolveThunkDefs.push(
|
||||
`define internal ptr @${snapshot}(ptr %p) ${FN_ATTRS} { ; materialize stream element ${key}`,
|
||||
`entry:`,
|
||||
` %d = call ptr @${this.dyn.toDynHelper(elem)}(ptr %p)`,
|
||||
` ret ptr %d`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
} else {
|
||||
boxed = B.tmp();
|
||||
B.line(
|
||||
|
||||
@@ -999,10 +999,6 @@ static ScrDyn *sf_signal_invoke(void *ptr, ScrDyn *self, const char *method,
|
||||
const char *what) {
|
||||
SfSignal *s = ptr;
|
||||
if (strcmp(method, "throwIfAborted") == 0) {
|
||||
if (argc != 0) {
|
||||
sf_type_error("AbortSignal.throwIfAborted takes no arguments");
|
||||
return NULL;
|
||||
}
|
||||
if (s->aborted) {
|
||||
sf_throw_dyn_reason(s->reason);
|
||||
return NULL;
|
||||
@@ -2402,10 +2398,6 @@ static ScrDyn *sf_reader_invoke(void *ptr, ScrDyn *self, const char *method,
|
||||
const char *what) {
|
||||
SfReader *r = ptr;
|
||||
if (strcmp(method, "read") == 0) {
|
||||
if (argc != 0) {
|
||||
sf_type_error("ReadableStreamDefaultReader.read takes no arguments");
|
||||
return NULL;
|
||||
}
|
||||
ScrPromise *p = sf_reader_read(r);
|
||||
ScrDyn *out = scr_dyn_new_promise(p);
|
||||
scr_promise_release(p);
|
||||
@@ -4208,9 +4200,8 @@ static void sf_on_upgrade(
|
||||
|
||||
static bool sf_body_is_stream(const ScrDyn *body) {
|
||||
return body &&
|
||||
((body->kind == SCR_DYN_HANDLE &&
|
||||
body->v.handle.tag == SCR_DYNH_WEB_STREAM) ||
|
||||
body->kind == SCR_DYN_ARR);
|
||||
body->kind == SCR_DYN_HANDLE &&
|
||||
body->v.handle.tag == SCR_DYNH_WEB_STREAM;
|
||||
}
|
||||
|
||||
static bool sf_redirect(SfTransfer *t, int status,
|
||||
@@ -4501,9 +4492,8 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
|
||||
body && body->kind != SCR_DYN_UNDEF && body->kind != SCR_DYN_NULL;
|
||||
bool body_stream =
|
||||
body_present &&
|
||||
((body->kind == SCR_DYN_HANDLE &&
|
||||
body->v.handle.tag == SCR_DYNH_WEB_STREAM) ||
|
||||
body->kind == SCR_DYN_ARR);
|
||||
body->kind == SCR_DYN_HANDLE &&
|
||||
body->v.handle.tag == SCR_DYNH_WEB_STREAM;
|
||||
const ScrDyn *duplex =
|
||||
init && init->kind == SCR_DYN_OBJ
|
||||
? scr_dyn_obj_get(init, "duplex", 6)
|
||||
@@ -4521,7 +4511,8 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
|
||||
promise, "RequestInit.duplex must be 'half'");
|
||||
}
|
||||
if (body_present && body->kind != SCR_DYN_STR &&
|
||||
body->kind != SCR_DYN_BYTES && !body_stream) {
|
||||
body->kind != SCR_DYN_BYTES && body->kind != SCR_DYN_ARR &&
|
||||
!body_stream) {
|
||||
scr_str_release(method);
|
||||
scr_url_release(u);
|
||||
return sf_reject_now(promise, "fetch failed");
|
||||
@@ -4542,12 +4533,24 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
|
||||
promise, "Request with GET/HEAD method cannot have body.");
|
||||
}
|
||||
|
||||
/* BodyInit's USVString fallback applies to ordinary arrays too. An
|
||||
* `any`-typed array is therefore "a,b", not a stream of "a" and "b";
|
||||
* only an actual ReadableStream uses the streaming-body branch. */
|
||||
ScrDyn *coerced_body = NULL;
|
||||
if (body_present && body->kind == SCR_DYN_ARR) {
|
||||
ScrStr *text = scr_dyn_string_coerce(body);
|
||||
coerced_body = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
body = coerced_body;
|
||||
}
|
||||
|
||||
ScrArr *headers = scr_arr_new(SCR_ELEM_STR, 16);
|
||||
const ScrDyn *init_headers =
|
||||
init && init->kind == SCR_DYN_OBJ
|
||||
? scr_dyn_obj_get(init, "headers", 7)
|
||||
: NULL;
|
||||
if (!sf_add_headers(headers, init_headers)) {
|
||||
scr_dyn_release(coerced_body);
|
||||
scr_arr_release(headers);
|
||||
scr_str_release(method);
|
||||
scr_url_release(u);
|
||||
@@ -4564,6 +4567,7 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
|
||||
bool has_content_length = false;
|
||||
size_t content_length = 0;
|
||||
if (!sf_content_length(headers, &has_content_length, &content_length)) {
|
||||
scr_dyn_release(coerced_body);
|
||||
scr_arr_release(headers);
|
||||
scr_str_release(method);
|
||||
scr_url_release(u);
|
||||
@@ -4577,6 +4581,7 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
|
||||
? body->v.str->len
|
||||
: body->v.bytes->len;
|
||||
if (body_length != content_length) {
|
||||
scr_dyn_release(coerced_body);
|
||||
scr_arr_release(headers);
|
||||
scr_str_release(method);
|
||||
scr_url_release(u);
|
||||
@@ -4589,6 +4594,7 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
|
||||
* over starting I/O, but never masks a RequestInit validation error.
|
||||
*/
|
||||
if (signal && signal->aborted) {
|
||||
scr_dyn_release(coerced_body);
|
||||
scr_arr_release(headers);
|
||||
scr_str_release(method);
|
||||
scr_url_release(u);
|
||||
@@ -4604,6 +4610,7 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
|
||||
t->method = method;
|
||||
t->headers = headers;
|
||||
t->body = body_present ? scr_dyn_retain((ScrDyn *)body) : NULL;
|
||||
scr_dyn_release(coerced_body);
|
||||
t->redirect_mode = redirect_mode;
|
||||
t->request_has_content_length = has_content_length;
|
||||
t->request_content_length = content_length;
|
||||
|
||||
@@ -700,6 +700,46 @@ void *scr_dyn_typed_ref_unbox(const ScrDyn *d) {
|
||||
return d->v.typed_ref.retain(d->v.typed_ref.ptr);
|
||||
}
|
||||
|
||||
static void scr_dyn_typed_ref_preserve_child(
|
||||
ScrDyn **fresh_slot, ScrDyn *cached_child) {
|
||||
ScrDyn *fresh_child = *fresh_slot;
|
||||
if (!cached_child || !fresh_child ||
|
||||
cached_child->kind != SCR_DYN_TYPED_REF ||
|
||||
fresh_child->kind != SCR_DYN_TYPED_REF ||
|
||||
!scr_dyn_strict_eq(cached_child, fresh_child)) {
|
||||
return;
|
||||
}
|
||||
*fresh_slot = scr_dyn_retain(cached_child);
|
||||
scr_dyn_release(fresh_child);
|
||||
}
|
||||
|
||||
/* A live record/array materializer creates typed capsules for mutable
|
||||
* children. Preserve an existing child capsule when the fresh view points
|
||||
* at the same static source, so retaining `value.child` across parent
|
||||
* refreshes keeps JavaScript reference identity as well as liveness. */
|
||||
static void scr_dyn_typed_ref_preserve_children(
|
||||
ScrDyn *cached, ScrDyn *fresh) {
|
||||
if (cached->kind != fresh->kind) return;
|
||||
if (cached->kind == SCR_DYN_ARR) {
|
||||
size_t n = cached->v.arr.len < fresh->v.arr.len
|
||||
? cached->v.arr.len
|
||||
: fresh->v.arr.len;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
scr_dyn_typed_ref_preserve_child(
|
||||
&fresh->v.arr.items[i], cached->v.arr.items[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (cached->kind == SCR_DYN_OBJ) {
|
||||
for (size_t i = 0; i < fresh->v.obj.len; i++) {
|
||||
ScrDynEntry *entry = &fresh->v.obj.entries[i];
|
||||
ScrDyn *cached_child = scr_dyn_obj_get(
|
||||
cached, entry->key, entry->key_len);
|
||||
scr_dyn_typed_ref_preserve_child(&entry->value, cached_child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrDyn *scr_dyn_typed_ref_materialize(const ScrDyn *d) {
|
||||
ScrDyn *capsule = (ScrDyn *)d;
|
||||
if (!capsule->v.typed_ref.materialized) {
|
||||
@@ -712,6 +752,8 @@ ScrDyn *scr_dyn_typed_ref_materialize(const ScrDyn *d) {
|
||||
* detached contents without changing the cached node's address. */
|
||||
ScrDyn *fresh =
|
||||
capsule->v.typed_ref.materialize(capsule->v.typed_ref.ptr);
|
||||
scr_dyn_typed_ref_preserve_children(
|
||||
capsule->v.typed_ref.materialized, fresh);
|
||||
size_t cached_rc = capsule->v.typed_ref.materialized->rc;
|
||||
size_t fresh_rc = fresh->rc;
|
||||
ScrDyn old = *capsule->v.typed_ref.materialized;
|
||||
|
||||
+60
@@ -57,6 +57,22 @@ const posted = await fetch(`${process.argv[2]}/post-echo`, {
|
||||
});
|
||||
console.log(await posted.json());
|
||||
console.log("consumed request locked:", requestBody.locked);
|
||||
|
||||
const arrayRequestBody: any = ["array", "body"];
|
||||
const arrayPosted = await fetch(`${process.argv[2]}/post-echo`, {
|
||||
method: "POST",
|
||||
body: arrayRequestBody,
|
||||
duplex: "half",
|
||||
});
|
||||
const arrayPostResult = (await arrayPosted.json()) as {
|
||||
body: string;
|
||||
contentType: string | null;
|
||||
};
|
||||
console.log(
|
||||
"array request body:",
|
||||
arrayPostResult.body,
|
||||
arrayPostResult.contentType,
|
||||
);
|
||||
try {
|
||||
requestBody.getReader();
|
||||
console.log("consumed request reader unexpectedly acquired");
|
||||
@@ -300,6 +316,17 @@ const bracketReader = ReadableStream.from(["bracket"]).getReader();
|
||||
const bracketPart = await bracketReader["read"]();
|
||||
console.log("bracket reader read:", bracketPart.done, bracketPart.value);
|
||||
|
||||
const surplusReader: any = ReadableStream.from([11]).getReader();
|
||||
console.log(
|
||||
"reader surplus argument:",
|
||||
JSON.stringify(await surplusReader.read("ignored")),
|
||||
);
|
||||
const surplusSignal: any = AbortSignal.any([]);
|
||||
console.log(
|
||||
"throwIfAborted surplus argument:",
|
||||
surplusSignal.throwIfAborted("ignored"),
|
||||
);
|
||||
|
||||
interface StreamUnionBox {
|
||||
value: number;
|
||||
}
|
||||
@@ -365,12 +392,14 @@ const repeatedStructuralReader =
|
||||
repeatedStructuralValue,
|
||||
]) as ReadableStream<StreamBaseValue>).getReader();
|
||||
const repeatedStructuralFirst = await repeatedStructuralReader.read();
|
||||
repeatedStructuralValue.value = 19;
|
||||
const repeatedStructuralSecond = await repeatedStructuralReader.read();
|
||||
console.log(
|
||||
"stream widened repeated identity:",
|
||||
repeatedStructuralFirst.done || repeatedStructuralSecond.done
|
||||
? false
|
||||
: repeatedStructuralFirst.value === repeatedStructuralSecond.value,
|
||||
repeatedStructuralSecond.done ? -1 : repeatedStructuralSecond.value.value,
|
||||
);
|
||||
|
||||
const repeatedDynamicValue = { value: 14 };
|
||||
@@ -401,6 +430,37 @@ console.log("dynamic stream live refresh:", JSON.stringify(liveDynamicSecond.val
|
||||
liveDynamicSecond.value.value = 18;
|
||||
console.log("dynamic stream live commit:", liveDynamicValue.value);
|
||||
|
||||
const dynamicArrayValue = [21];
|
||||
const dynamicArrayReader: any = ReadableStream.from([
|
||||
dynamicArrayValue,
|
||||
dynamicArrayValue,
|
||||
]).getReader();
|
||||
const dynamicArrayFirst: any = await dynamicArrayReader.read();
|
||||
dynamicArrayFirst.value[0] = 22;
|
||||
const dynamicArraySecond: any = await dynamicArrayReader.read();
|
||||
console.log(
|
||||
"dynamic stream array commit:",
|
||||
dynamicArrayValue[0],
|
||||
dynamicArraySecond.value[0],
|
||||
dynamicArrayFirst.value === dynamicArraySecond.value,
|
||||
);
|
||||
|
||||
const dynamicNestedValue = { nested: { value: 23 } };
|
||||
const dynamicNestedReader: any = ReadableStream.from([
|
||||
dynamicNestedValue,
|
||||
dynamicNestedValue,
|
||||
]).getReader();
|
||||
const dynamicNestedFirst: any = await dynamicNestedReader.read();
|
||||
const retainedDynamicNested: any = dynamicNestedFirst.value.nested;
|
||||
retainedDynamicNested.value = 24;
|
||||
const dynamicNestedSecond: any = await dynamicNestedReader.read();
|
||||
console.log(
|
||||
"dynamic stream nested commit:",
|
||||
dynamicNestedValue.nested.value,
|
||||
dynamicNestedSecond.value.nested.value,
|
||||
retainedDynamicNested === dynamicNestedSecond.value.nested,
|
||||
);
|
||||
|
||||
const widenedStringStream: ReadableStream<unknown> = ReadableStream.from([
|
||||
"same",
|
||||
["sa", "me"].join(""),
|
||||
|
||||
Reference in New Issue
Block a user