mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-30 07:14:39 +08:00
Specialize int32 class fields in worker executables
Worker builds skipped the whole-program int32 field analysis, as library builds do, so a Worker-using tsc-ts kept every flags field a double and isSimpleTypeRelatedTo tripled in size. Unlike library instances, worker instances never cross a native boundary: they reach other threads only through postMessage and workerData, whose dynamic views read and commit fields like every other dynamic reader the analysis already handles. worker.new and the ArrayBuffer/SharedArrayBuffer constructors take dynamic arguments but never store into a live class capsule (none of their runtime paths reaches scr_dyn_typed_ref_commit), so they join the read-only helpers instead of disqualifying every field a dynamic store could name.
This commit is contained in:
@@ -272,11 +272,11 @@ test("captured bindings share one proof across the declaring function and its cl
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test("library and worker builds keep ordinary storage", () => {
|
||||
test("worker builds specialize int32 fields like ordinary executables", () => {
|
||||
const program = mod(
|
||||
[cls("T", ["x"])],
|
||||
[fn("init", [{ id: "t", type: obj("T") }], [set(ref("t", obj("T")), "T", "x", num(1))])],
|
||||
);
|
||||
expect(analyzeInt32Slots(program).isField("T", "x")).toBe(true);
|
||||
expect(analyzeInt32Slots({ ...program, workers: true }).isField("T", "x")).toBe(false);
|
||||
expect(analyzeInt32Slots({ ...program, workers: true }).isField("T", "x")).toBe(true);
|
||||
});
|
||||
|
||||
@@ -34,9 +34,12 @@ import { everyExprChild, everyStmtChild, everyTypeChild } from "./traverse.js";
|
||||
* with runtime-provided ancestors, generic families, fields under
|
||||
* `++`/`--`, and parameters of functions that escape as values, are
|
||||
* constructed through class values, or are virtual-dispatch targets are
|
||||
* excluded as well. Library and worker builds keep ordinary storage: their
|
||||
* instances cross a native boundary. Reads are never restricted: every
|
||||
* reader (static code, dyn views, JSON, inspect) widens the i32 exactly. */
|
||||
* excluded as well. Library builds keep ordinary storage: their instances
|
||||
* cross a native boundary. Worker builds do not: instances reach another
|
||||
* thread only through postMessage/workerData, whose dyn views read (and
|
||||
* commit) fields like every other dynamic reader. Reads are never
|
||||
* restricted: every reader (static code, dyn views, JSON, inspect) widens
|
||||
* the i32 exactly. */
|
||||
export class Int32Slots {
|
||||
/** Field families (topmost declaring class and field) stored as i32. */
|
||||
private readonly fields = new Set<string>();
|
||||
@@ -190,6 +193,7 @@ const BOUNDARY_KINDS = new Set<IrExpr["kind"]>([
|
||||
* target). Any other helper that receives a dynamic value may store any
|
||||
* property, except literal-keyed `dyn.keySet`/`dyn.keyDelete`. */
|
||||
const DYN_READ_ONLY = new Set<string>([
|
||||
"arrayBuffer.new",
|
||||
"arrayBuffer.viewF32",
|
||||
"arrayBuffer.viewF64",
|
||||
"arrayBuffer.viewI16",
|
||||
@@ -225,7 +229,9 @@ const DYN_READ_ONLY = new Set<string>([
|
||||
"json.stringifyReplacer",
|
||||
"json.stringifyValue",
|
||||
"num.toStringRadix",
|
||||
"sharedArrayBuffer.new",
|
||||
"text.decodeStream",
|
||||
"worker.new",
|
||||
]);
|
||||
const ISLAND_KINDS = new Set<IrExpr["kind"]>([
|
||||
"jsOp",
|
||||
@@ -264,7 +270,7 @@ function walkFunction(
|
||||
export function analyzeInt32Slots(mod: IrModule): Int32Slots {
|
||||
const classes = new Map((mod.classes ?? []).map((c) => [c.name, c]));
|
||||
const slots = new Int32Slots(classes);
|
||||
if (mod.lib || mod.workers) return slots;
|
||||
if (mod.lib) return slots;
|
||||
const reason = (key: string, why: string): void => {
|
||||
if (!slots.reasons.has(key)) slots.reasons.set(key, why);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user