@loopdive/js2 0.58.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,7 +57,7 @@ STATUS.md rather than duplicating numbers that go stale. Standalone
57
57
  README until the current standalone regression is fixed.
58
58
 
59
59
  <!-- AUTO:conformance-start -->
60
- **test262 conformance**: 32,667 / 43,135 (75.7 %)
60
+ **test262 conformance**: 32,704 / 43,135 (75.8 %)
61
61
  <!-- AUTO:conformance-end -->
62
62
 
63
63
  ## Current Status
package/dist/cli.js CHANGED
@@ -66,13 +66,17 @@ Options:
66
66
  is ON by default; this restores the pre-#1950 behaviour.
67
67
  (No-op when binaryen/wasm-opt is unavailable — that path
68
68
  already degrades to a one-line note, never a failure.)
69
- --link-node-shims (WASI, #2625/#2633) Emit the per-module linkable node:<mod>
70
- shims instead of inlining the host APIs. Std-IO goes through
71
- node:fs: the module imports readSync/writeSync + its memory
72
- from node:fs (no wasi_snapshot_preview1 for stream IO) and
73
- links node-fs.wasm. console.log / process.std*.write lower to
74
- writeSync(1|2, …); stdin is readSync(0, …). Off by default —
75
- the inline fd_read/fd_write path is self-contained.
69
+ --link <ns> (WASI, #2783) Leave the external namespace <ns> as link-time
70
+ imports (repeatable) instead of inline-lowering it. Satisfied
71
+ at instantiation by a preloaded provider module (e.g.
72
+ 'wasmtime --preload <ns>=provider.wasm'). Any namespace works
73
+ (leave-as-import is universal); '--link node:fs' additionally
74
+ selects the import-and-link std-IO path: the module imports
75
+ readSync/writeSync + its memory from node:fs (no
76
+ wasi_snapshot_preview1 for stream IO) and links node-fs.wasm.
77
+ console.log / process.std*.write lower to writeSync(1|2, ...),
78
+ stdin is readSync(0, ...). Off by default — every namespace is
79
+ inline-lowered into a self-contained module.
76
80
  --emulate <env> Emulate a host runtime's globals so they type-check without
77
81
  @types/node. 'node' = ambient process/etc.; 'none' = off.
78
82
  Auto-enabled (type-level only) when the source imports a
@@ -127,7 +131,7 @@ let allowFs = false;
127
131
  let quiet = false;
128
132
  let utf8Storage = false;
129
133
  let strictNoHostImports;
130
- let linkNodeShims = false;
134
+ const linkedNamespaces = /* @__PURE__ */ new Set();
131
135
  let emulateNode = false;
132
136
  let emulateExplicit = false;
133
137
  let platform;
@@ -186,8 +190,13 @@ for (let i = 0; i < args.length; i++) {
186
190
  quiet = true;
187
191
  } else if (arg === "--utf8-storage") {
188
192
  utf8Storage = true;
189
- } else if (arg === "--link-node-shims") {
190
- linkNodeShims = true;
193
+ } else if (arg === "--link" || arg.startsWith("--link=")) {
194
+ const ns = arg.startsWith("--link=") ? arg.slice("--link=".length) : args[++i];
195
+ if (!ns) {
196
+ console.error("--link requires a namespace argument (e.g. --link node:fs)");
197
+ process.exit(1);
198
+ }
199
+ linkedNamespaces.add(ns);
191
200
  } else if (arg === "--emulate" || arg.startsWith("--emulate=")) {
192
201
  const env = arg.startsWith("--emulate=") ? arg.slice("--emulate=".length) : args[++i];
193
202
  if (env === "node") {
@@ -283,7 +292,7 @@ const compileOptions = {
283
292
  ...emitWit ? { wit: witPackageName ? { packageName: witPackageName } : true } : {},
284
293
  ...allowFs ? { allowFs: true } : {},
285
294
  ...utf8Storage ? { utf8Storage: true } : {},
286
- ...linkNodeShims ? { linkNodeShims: true } : {},
295
+ ...linkedNamespaces.size ? { link: [...linkedNamespaces] } : {},
287
296
  ...emulateNode ? { emulateNode: true } : {},
288
297
  ...platform ? { platform } : {},
289
298
  fileName: absInput,
@@ -1,21 +1,35 @@
1
1
  import { ts } from '../ts-api.js';
2
2
  /**
3
- * Return true if `expr` provably produces a 32-bit signed integer at runtime,
4
- * given that `i32Locals` is the set of locals already known to hold i32.
3
+ * Return true if `expr` provably produces a **canonical** signed-32-bit integer
4
+ * at runtime — a value `v [-2^31, 2^31)` whose f64 image is bit-identical
5
+ * to `v` (so `v` is not `-0`, not fractional, not NaN/±Inf, not `|v| ≥ 2^31`).
6
+ * `i32Locals` is the set of locals already known to hold i32.
5
7
  *
6
- * Recognised i32-safe forms (mirrors `isI32SafeExpr` in function-body.ts but
7
- * is intentionally narrower we err on the side of disqualification):
8
- * - integer numeric literal in [-2^31, 2^31)
9
- * - identifier referencing a known-i32 local
10
- * - bitwise `|`, `&`, `^`, `<<`, `>>` (always produce int32 per ECMAScript)
11
- * - comparison ops (return boolean = i32)
12
- * - unary `+` / `-` / `~` of an i32-safe operand
13
- * - `+` / `-` / `*` of two i32-safe operands (overflow wraps; receiver is i32)
14
- * - parenthesised / `as`-cast / non-null-asserted i32-safe expr
8
+ * The "canonical" property is the soundness contract for #2789: when EVERY write
9
+ * to a packed array yields a canonical i32, the i32-stored value read back as f64
10
+ * equals what the f64 backing would have stored, so NO read can observe a
11
+ * distinction i32 erases. That discharges the read-side proof obligation in the
12
+ * hybrid fast-path audit (Row 3) for free there is no distinction to observe.
15
13
  *
16
- * Note: `>>>` is intentionally excluded it produces uint32 which can sit
17
- * above 2^31 and would be reinterpreted as a negative i32 on store. The
18
- * conservative choice is to disqualify (the array would then stay f64).
14
+ * Recognised canonical-i32 forms (mirrors `isI32SafeExpr` in function-body.ts;
15
+ * intentionally narrow we err on the side of disqualification):
16
+ * - integer numeric literal in [-2^31, 2^31) (always +0, never -0)
17
+ * - identifier referencing a known-i32 local (physically i32 ⇒ canonical)
18
+ * - bitwise `|`, `&`, `^`, `<<`, `>>` (ECMAScript defines these to yield a
19
+ * value that equals ToInt32 of itself ⇒ canonical regardless of operands)
20
+ * - comparison ops (return boolean = i32 0/1)
21
+ * - `~x` (canonical int32) and unary `+x` of an i32-safe operand
22
+ * - unary `-<non-zero integer literal>` only (a `-1`-style sentinel) — NOT
23
+ * `-x` / `-(expr)`, which can be `-0` (#2789)
24
+ * - parenthesised / `as`-cast / non-null-asserted canonical-i32 expr
25
+ *
26
+ * Deliberately EXCLUDED (would break canonicality → MISCOMPILE if packed):
27
+ * - `+` / `-` / `*` arithmetic — f64 result stored via `i32.trunc_sat_f64_s`,
28
+ * which SATURATES on overflow rather than yielding the spec-correct f64
29
+ * (#2789, mirrors the #1236 scalar-local fix). Wrap-canonicalising patterns
30
+ * like `(a*b) | 0` still qualify via the top-level bitwise op.
31
+ * - `>>>` — produces uint32 which can sit above 2^31, reinterpreted as a
32
+ * negative i32 on store.
19
33
  */
20
34
  export declare function isI32SafeExprForArray(expr: ts.Expression | undefined, i32Locals: ReadonlySet<string>, depth?: number): boolean;
21
35
  /**
@@ -55,22 +55,29 @@ export interface CodegenOptions {
55
55
  /** WASI target: emit WASI imports (fd_write, proc_exit) instead of JS host imports */
56
56
  wasi?: boolean;
57
57
  /**
58
- * #2524 / #2633 route std-IO through a separately compiled, linkable
59
- * `node:fs` shim instead of inlining the `wasi_snapshot_preview1.fd_read`/
60
- * `fd_write` glue. When set (WASI only), the user module imports
61
- * `readSync`/`writeSync` plus its linear memory from `node:fs` and carries NO
58
+ * #2783the dynamic-linking axis: namespaces to leave as link-time imports
59
+ * (satisfied by a preloaded provider) instead of inline-lowering. `["node:fs"]`
60
+ * routes std-IO through the `node:fs` shim (the user module imports
61
+ * `readSync`/`writeSync` + its linear memory from `node:fs` and carries NO
62
62
  * `wasi_snapshot_preview1` import for the stream IO path; console.log /
63
- * process.std*.write lower to `writeSync(1|2, …)`. `node-fs.wasm` implements
64
- * the interface over WASI. The bespoke `js2wasm:node-process` shim was retired
65
- * (#2633). Default off — the inline fd_read/fd_write path stays as fallback.
63
+ * process.std*.write lower to `writeSync(1|2, …)`; `node-fs.wasm` implements
64
+ * the interface over WASI). WASI-gated in `create-context.ts` (ignored for
65
+ * non-WASI targets). Default empty — the inline fd_read/fd_write path stays.
66
66
  */
67
- linkNodeShims?: boolean;
67
+ link?: string[];
68
68
  /** Standalone target (#1470): pure WasmGC, no JS host imports and no WASI
69
69
  * runtime. Implies `nativeStrings: true` and refuses to emit any
70
70
  * `wasm:js-string` namespace or `env::__concat_*` / `__extern_toString` /
71
71
  * `__unbox_string` JS-host string imports. Used so the compiled module is
72
72
  * runnable under pure-Wasm engines (wasmtime, wasmer) without a JS host. */
73
73
  standalone?: boolean;
74
+ /** (#2796) Diff-test-harness fidelity: in JS-host mode, export the top-level
75
+ * `__module_init` and do NOT run it via the wasm `start` section, so the host
76
+ * invokes it AFTER `setExports` (symmetric with the standalone `_start`
77
+ * model). Default false → top-level runs in the start section. See
78
+ * `CompileOptions.deferTopLevelInit`. WASI is unaffected (it already exports
79
+ * `_start`). */
80
+ deferTopLevelInit?: boolean;
74
81
  /**
75
82
  * Experimental: route a narrow set of functions through the middle-end IR
76
83
  * (see `src/ir/`). Defaults to **on** since #1131 (the front-end driver
@@ -842,6 +849,24 @@ export interface CodegenContext {
842
849
  */
843
850
  holeTypeIdx: number;
844
851
  holeGlobalIdx: number | undefined;
852
+ /**
853
+ * (#2800) The mutable i32 `__in_module_init` flag — 1 for the duration of
854
+ * `__module_init` (the Wasm `start` section in gc/host mode, which runs INSIDE
855
+ * `WebAssembly.instantiate`, BEFORE the host wires struct getters via
856
+ * `__setExports`), 0 otherwise. The delete-aware `any`-receiver READ
857
+ * (`tryEmitDeleteAwareDynamicGet`) branches on it: while init runs (host
858
+ * `__extern_get` can't reach `__sget_<field>` → returns undefined for every
859
+ * struct field), read the slot HOST-FREE via the `__get_member_<name>`
860
+ * dispatcher; at runtime use the tombstone-aware host `__extern_get`.
861
+ *
862
+ * `inModuleInitFlagReads` collects the `global.get` flag-read Instr objects
863
+ * emitted at read sites (with a placeholder index); `finalizeInModuleInitFlag`
864
+ * allocates the i32 global AFTER every import settles and patches their
865
+ * `.index` + records the final slot in `inModuleInitGlobalIdx`. Undefined/empty
866
+ * for delete-free / standalone / WASI modules (byte-identical).
867
+ */
868
+ inModuleInitFlagReads: Instr[] | undefined;
869
+ inModuleInitGlobalIdx: number | undefined;
845
870
  /**
846
871
  * (#2580 M0) Value-rep dynamic-read substrate. Set true by a call site that
847
872
  * needs the runtime property-presence read primitives (`__dyn_has` /
@@ -1675,6 +1700,11 @@ export interface CodegenContext {
1675
1700
  * `__unbox_string`, `__str_from_mem`, `__str_to_mem`,
1676
1701
  * `__str_extern_len`). Implies `nativeStrings === true`. */
1677
1702
  standalone: boolean;
1703
+ /** (#2796) Diff-test-harness fidelity: in JS-host mode, export the top-level
1704
+ * `__module_init` and do NOT wire the wasm `start` section to it, so the host
1705
+ * runs it after `setExports` (symmetric with the standalone `_start` model).
1706
+ * Default false. WASI is unaffected. */
1707
+ deferTopLevelInit: boolean;
1678
1708
  /** (#2179) True when the module body contains any `delete` of a property or
1679
1709
  * element access (e.g. `delete o.a` / `delete o[k]`). Pre-scanned once at
1680
1710
  * module setup. When true, `any`/`unknown`-typed property READS in JS-host
@@ -1745,10 +1775,27 @@ export interface CodegenContext {
1745
1775
  * `node:fs` `readSync`/`writeSync` calls (over a shim-owned, imported linear
1746
1776
  * memory) instead of inline `fd_read`/`fd_write`. console.log/warn/error and
1747
1777
  * process.std*.write lower to `writeSync(1|2, …)`; the bespoke
1748
- * `js2wasm:node-process` shim was retired (#2633). See `linkNodeShims` in
1749
- * `CodegenOptions`.
1778
+ * `js2wasm:node-process` shim was retired (#2633). Driven by the `link` set
1779
+ * (`["node:fs"]`).
1780
+ *
1781
+ * #2783 — an INTERNAL convenience boolean, **derived** from
1782
+ * `linkedNamespaces.has("node:fs")` (there is no user-facing `linkNodeShims`
1783
+ * option anymore — `link: string[]` is the only input). The two
1784
+ * are computed together in `create-context.ts` from the same (WASI-gated)
1785
+ * `link` set so they can never drift. Keeping this boolean lets the ~30
1786
+ * existing `ctx.linkNodeShims` read sites stay zero-churn while the underlying
1787
+ * state generalizes to an arbitrary set of linked namespaces.
1750
1788
  */
1751
1789
  linkNodeShims: boolean;
1790
+ /**
1791
+ * #2783 — the set of external namespaces left as **link-time imports** for
1792
+ * this compile (WASI-gated; empty for non-WASI targets). `node:fs` membership
1793
+ * additionally drives the import-and-link std-IO codegen path (see
1794
+ * `linkNodeShims`, derived from this set). For an arbitrary namespace,
1795
+ * membership only permits its imports past the strict `--no-host-imports` /
1796
+ * WASI leaked-host-import gate (`assertNoLeakedHostImports`).
1797
+ */
1798
+ linkedNamespaces: ReadonlySet<string>;
1752
1799
  /** #2631/#2633: func index of the imported `node:fs::readSync` (fd,ptr,len)->i32 (-1 = not registered). */
1753
1800
  nodeFsReadSyncIdx: number;
1754
1801
  /** #2631/#2633: func index of the imported `node:fs::writeSync` (fd,ptr,len)->i32 (-1 = not registered). */
@@ -85,6 +85,43 @@ export interface FnctorEscapeGateResult {
85
85
  * `undefined`.
86
86
  */
87
87
  export declare function resolveFnctorSymbol(checker: ts.TypeChecker, calleeExpr: ts.Expression): ts.Symbol | undefined;
88
+ /**
89
+ * #2681/#2686 — resolve the fnctor `F` that OWNS the enclosing method a node sits
90
+ * in, for a `new this(…)` site or a lifted method body. `this` inside a method
91
+ * `F.method = function(){…}` / `F.prototype.m = function(){…}` / aliased `var pp =
92
+ * F.prototype; pp.m = function(){…}` binds to `F` (static) or an `F` instance
93
+ * (prototype). Walks up to the nearest non-arrow function (arrows do not rebind
94
+ * `this`) and resolves its defining assignment's holder to a fnctor symbol.
95
+ *
96
+ * Returns `{ name, sym, viaPrototype }` where `viaPrototype` is true for a
97
+ * prototype/aliased method (`this` is an INSTANCE — the read-dispatch case) and
98
+ * false for a direct static method (`this` is the CONSTRUCTOR — the `new this()`
99
+ * reconstruct case). `undefined` when the enclosing function is not a fnctor
100
+ * method, or the holder does not resolve to a user fnctor.
101
+ */
102
+ export declare function resolveEnclosingFnctorOwner(checker: ts.TypeChecker, node: ts.Node): {
103
+ name: string;
104
+ sym: ts.Symbol;
105
+ viaPrototype: boolean;
106
+ } | undefined;
107
+ /**
108
+ * #2681/#2686 A3 — the `__fnctor_<F>` struct name a lifted PROTOTYPE method's
109
+ * `this` receiver resolves to, when `F` is approved for reconstruction. Sets
110
+ * `FunctionContext.thisStructName` (closures.ts) so the dynamic `this.<field>`
111
+ * read dispatch (property-access.ts) routes through the finalize-filled
112
+ * `__get_member_<name>` dispatcher.
113
+ *
114
+ * Deliberately NOT gated on `ctx.structMap.has(__fnctor_<F>)`: the reader method
115
+ * frequently compiles BEFORE the `new this()` site that registers the struct
116
+ * (acorn defines `pp.parseExprAtom` long before the static `Parser.parse`). The
117
+ * dispatcher is reserved at the read site and FILLED at finalize over the
118
+ * COMPLETE type table, so a struct registered later is still enumerated — pinning
119
+ * on `approvedNames` (frozen pre-codegen at index.ts) is order-independent and
120
+ * correct, while a `structMap.has` gate would race the compile order and miss.
121
+ * Excludes static methods (`viaPrototype === false`) — their `this` is the
122
+ * constructor function-value, not an instance.
123
+ */
124
+ export declare function resolveLiftedMethodThisStruct(ctx: CodegenContext, fn: ts.FunctionExpression | ts.ArrowFunction): string | undefined;
88
125
  /**
89
126
  * #2660 PART-1 — resolve the WasmGC struct a member-access RECEIVER expression
90
127
  * concretely is, for the dynamic read/write/compound dispatch to PIN to.
@@ -80,7 +80,7 @@ export declare function lookupAllowlistEntry(name: string): HostImportAllowlistE
80
80
  * `nativeStrings` mode which strict mode auto-enables; they should not
81
81
  * appear when strict mode is on.)
82
82
  */
83
- export declare function isHostImportAllowed(module: string, name: string): {
83
+ export declare function isHostImportAllowed(module: string, name: string, linkedNamespaces?: ReadonlySet<string>): {
84
84
  allowed: true;
85
85
  } | {
86
86
  allowed: false;
@@ -129,7 +129,7 @@ export interface LeakedHostImport {
129
129
  export declare function scanForLeakedHostImports(imports: ReadonlyArray<{
130
130
  module: string;
131
131
  name: string;
132
- }>): LeakedHostImport[];
132
+ }>, linkedNamespaces?: ReadonlySet<string>): LeakedHostImport[];
133
133
  /**
134
134
  * (#2094) Build the structured compile error for a host import that leaked
135
135
  * into a finished standalone/strict binary. Distinct from
@@ -89,6 +89,23 @@ export declare function generateModule(ast: TypedAST, options?: CodegenOptions):
89
89
  message: string;
90
90
  }[];
91
91
  };
92
+ /**
93
+ * Emit __vec_get(externref, i32) -> externref and __vec_len(externref) -> i32
94
+ * exports so the runtime can iterate WasmGC vec structs that were coerced to
95
+ * externref (e.g. arrays stored in `any`-typed variables).
96
+ *
97
+ * For each registered vec type, emits ref.test/ref.cast dispatch to extract
98
+ * the length or the indexed element, boxing the result to externref.
99
+ */
100
+ /**
101
+ * (#2784 S3) Reserve a `__vec_push` / `__vec_pop` helper funcIdx UP FRONT so the
102
+ * native-vec method dispatch (calls.ts) can bake the call at compile time — the
103
+ * helper bodies are only built in the finalize `emitVecAccessExports` pass, which
104
+ * runs AFTER the method-call site compiles. Pushes a valid placeholder body +
105
+ * export + funcMap entry (shift-tracked); the finalize pass FILLS the body in
106
+ * place (fill-or-build in `_emitVecAccessExportsInner`). Idempotent.
107
+ */
108
+ export declare function reserveVecMethodHelper(ctx: CodegenContext, kind: "push" | "pop" | "get"): number;
92
109
  /**
93
110
  * Compile multiple typed source files into a single WasmModule IR.
94
111
  * All source files share the same codegen context (funcMap, structMap, etc.).
@@ -245,7 +262,7 @@ export declare function ensureWasiWriteAnyStringHelper(ctx: CodegenContext, useS
245
262
  * it to the *runtime* fd. This backs the STRING overload of `node:fs`
246
263
  * `writeSync(fd, str, position?, encoding?)`, where the fd is an arbitrary
247
264
  * integer (not just stdout/stderr). Two modes (#2655):
248
- * - shim (`--link-node-shims`): `writeSync(fd, ptr, len)` returns the byte count.
265
+ * - shim (`--link node:fs`): `writeSync(fd, ptr, len)` returns the byte count.
249
266
  * - direct (standalone `--target wasi`): build a `{ base=ptr, len }` iovec at
250
267
  * memory[0..7], call `fd_write(fd, iovs=0, 1, nwritten=8)`, load nwritten.
251
268
  */
@@ -42,6 +42,33 @@ export declare function compileObjectLiteralAsExternref(ctx: CodegenContext, fct
42
42
  * value semantics suffice; plain data structs keep `extern.convert_any`.
43
43
  */
44
44
  export declare function materializeStructAsDynamicObject(ctx: CodegenContext, fctx: FunctionContext, structTypeIdx: number): boolean;
45
+ /**
46
+ * (#2714 / #2804) True when a spread-containing object literal must be built via
47
+ * the host plain-object (`$Object`/externref) path rather than a closed struct,
48
+ * because its evaluation context does not pin a CONCRETE object SHAPE the struct
49
+ * path could faithfully build/enumerate: `any` / `unknown` / `object`, NO
50
+ * contextual type at all, or a shapeless object type with zero own properties
51
+ * (e.g. the `object` param of `Object.keys`).
52
+ *
53
+ * This is the single source of truth for the routing decision below AND for the
54
+ * variable-declaration local typing (statements/variables.ts, index.ts var
55
+ * hoist). Keeping them in lockstep is what fixes #2804: `const b = { ...a, z: 3 }`
56
+ * (no annotation) has NO contextual type, so the literal takes the host path —
57
+ * but the receiving variable's INFERRED type is a concrete struct `{x;y;z}`, so
58
+ * without this shared predicate the local was typed as that struct while the
59
+ * initializer produced a host `$Object`, and the externref→struct coercion
60
+ * (ref.test/ref.cast) failed at runtime → `b.x` read NaN / null. The variable
61
+ * sites consult this predicate and force an externref local so the local
62
+ * representation matches the host-object value (and `b.x` routes through
63
+ * `__extern_get`, preserving the spread's insertion-order keys + values, which
64
+ * the struct path cannot — its field order follows TS's own-prop-first inferred
65
+ * type, not the runtime CopyDataProperties insertion order).
66
+ *
67
+ * A CONCRETE annotated target (`const x: { a: number } = { ...o }`, ≥1 property)
68
+ * has a specific contextual type → returns false → keeps the struct path so
69
+ * typed consumers still receive a struct (#2714 control).
70
+ */
71
+ export declare function objectLiteralSpreadTakesHostPath(ctx: CodegenContext, expr: ts.ObjectLiteralExpression): boolean;
45
72
  export declare function compileObjectLiteral(ctx: CodegenContext, fctx: FunctionContext, expr: ts.ObjectLiteralExpression): ValType | null;
46
73
  /**
47
74
  * Try to evaluate an expression to a constant numeric or string value at compile time.
@@ -1,4 +1,4 @@
1
- import { CodegenContext } from './context/types.js';
1
+ import { CodegenContext, FunctionContext } from './context/types.js';
2
2
  /**
3
3
  * Reserve (or fetch) the member-set dispatcher `__set_member_<name>(recv, val)`
4
4
  * funcIdx with a placeholder body. The real body is built by
@@ -14,7 +14,7 @@ import { CodegenContext } from './context/types.js';
14
14
  * - `__box_number`/`__unbox_number` (union imports — the per-struct arms may
15
15
  * unbox the externref value into an f64/i32 field via `coercionInstrs`).
16
16
  */
17
- export declare function reserveMemberSetDispatch(ctx: CodegenContext, propName: string, strict: boolean): number | undefined;
17
+ export declare function reserveMemberSetDispatch(ctx: CodegenContext, propName: string, strict: boolean, fctx?: FunctionContext): number | undefined;
18
18
  /**
19
19
  * Fill every reserved `__set_member_<name>` dispatcher body at FINALIZE, after
20
20
  * every struct type (incl. late-registered fnctor structs) is known. READ-ONLY
@@ -1,4 +1,4 @@
1
- import { Import } from '../../ir/types.js';
1
+ import { Import, Instr } from '../../ir/types.js';
2
2
  import { CodegenContext } from '../context/types.js';
3
3
  /**
4
4
  * Register an import (`module.name`) on the current module.
@@ -37,6 +37,24 @@ export declare function addImport(ctx: CodegenContext, module: string, name: str
37
37
  export declare function addStringConstantGlobal(ctx: CodegenContext, value: string): void;
38
38
  /** Return the absolute Wasm global index for a new module-defined global. */
39
39
  export declare function nextModuleGlobalIdx(ctx: CodegenContext): number;
40
+ /**
41
+ * (#2800) Record a `global.get __in_module_init` instruction for FINALIZE-time
42
+ * index resolution. Returns a fresh `global.get` Instr with a PLACEHOLDER index
43
+ * and registers it on `ctx.inModuleInitFlagReads`; the caller bakes this exact
44
+ * object into its body. `finalizeInModuleInitFlag` (codegen/index.ts) allocates
45
+ * the i32 flag global AFTER every import global has settled and patches each
46
+ * recorded instr's `.index` to the final slot — so no read can desync when a
47
+ * later string-constant import shifts the module-global range (the live-baked
48
+ * index hazard #2043 across closure bodies the per-add fixup can miss).
49
+ *
50
+ * The flag is 1 only while `__module_init` runs; the delete-aware `any`-receiver
51
+ * read branches on it (init → host-free `__get_member_<name>` slot dispatcher;
52
+ * runtime → tombstone-aware host `__extern_get`). gc/host runs `__module_init`
53
+ * via the Wasm `start` section INSIDE `WebAssembly.instantiate`, before the host
54
+ * wires struct getters (`__setExports`), so the host read returns undefined for
55
+ * every struct field at init — this flag is what makes init reads correct.
56
+ */
57
+ export declare function recordInModuleInitFlagRead(ctx: CodegenContext): Instr;
40
58
  /** Convert an absolute Wasm global index to a local module-globals array index. */
41
59
  export declare function localGlobalIdx(ctx: CodegenContext, absIdx: number): number;
42
60
  /**
@@ -64,6 +64,30 @@ export declare function flushLateImportShifts(ctx: CodegenContext, fctx: Functio
64
64
  * Check if a ValType is the any-value boxed type used for TS `any`.
65
65
  */
66
66
  export declare function isAnyValue(type: ValType, ctx: CodegenContext): boolean;
67
+ /**
68
+ * (#2770, S5b of #2773) Brand an extern-method result ValType as a *boolean*
69
+ * when the method's declared TS return type is exactly `boolean`, so the
70
+ * `any`/return coercion boxes it via `__box_boolean` (→ `true`/`false`) instead
71
+ * of `__box_number` (→ `1`/`0`).
72
+ *
73
+ * Why a per-call-site wrap (not just registration): a boolean extern method's
74
+ * func type carries an `i32` result, and `funcTypeKey` (registry/types.ts) keys
75
+ * results on `.kind` only — so a branded `{i32,boolean:true}` result func type
76
+ * dedups to a pre-existing *unbranded* `…->i32` type. `getWasmFuncReturnType`
77
+ * then reads back the deduped unbranded i32 and the brand is lost at every
78
+ * `getWasmFuncReturnType(ctx, idx) ?? resolveWasmType(ctx, retType)` dispatch
79
+ * site. Re-branding from the call's TS return type here recovers it regardless
80
+ * of dedup. (Registration is also branded so the direct `methodInfo.results[0]`
81
+ * path in extern.ts is honest at source.)
82
+ *
83
+ * Over-boxing guards — idempotent, never widens:
84
+ * - only a *bare* `i32` is touched (f64/externref/ref/ref_null pass through);
85
+ * - an already `{i32,boolean:true}` value short-circuits (idempotent);
86
+ * - only an *exactly*-`boolean` declared return is branded (numbers, unions,
87
+ * `void`, etc. pass through unchanged — `map.get`/`indexOf`/`.size` stay
88
+ * numbers).
89
+ */
90
+ export declare function brandExternMethodResult(_ctx: CodegenContext, tsReturnType: ts.Type | undefined, valType: ValType): ValType;
67
91
  type EnsureAnyHelpersFn = (ctx: CodegenContext) => void;
68
92
  export declare function registerEnsureAnyHelpers(fn: EnsureAnyHelpersFn): void;
69
93
  type AddUnionImportsFn = (ctx: CodegenContext) => void;
package/dist/index.d.ts CHANGED
@@ -315,19 +315,48 @@ export interface CompileOptions {
315
315
  * Default: false (calls to fs.readFileSync / fs.writeFileSync raise a compile error). */
316
316
  allowFs?: boolean;
317
317
  /**
318
- * #2625 / #2633 emit the per-module linkable `node:<mod>` shims instead of
319
- * inlining the host APIs. WASI-only (ignored for other targets). When set,
320
- * std-IO is routed through `node:fs`: the user module imports `readSync`/
321
- * `writeSync` plus its linear memory from `node:fs` and carries no
322
- * `wasi_snapshot_preview1` import for stream IO; console.log / process.std*.write
323
- * lower to `writeSync(1|2, …)` and synchronous stdin is `readSync(0, …)`. Link
324
- * against `node-fs.wasm` (or `--preload node:fs=node-fs.wasm` under wasmtime).
325
- * Default off the self-contained inline `fd_read`/`fd_write` path stays. The
326
- * bespoke `js2wasm:node-process` shim (`process.stdin.read`/`stdout_write`/…)
327
- * was retired in #2633; `process.stdin.read(buf, offset)` is no longer a
328
- * recognised API (it matched no real Node surface — use `node:fs` `readSync`).
318
+ * (#2796) Differential-test-harness fidelity flag. In the default JS-host
319
+ * (WasmGC) target, top-level module code runs via the wasm `start` section —
320
+ * i.e. DURING `WebAssembly.instantiate`, BEFORE the host can call
321
+ * `setExports(instance.exports)`. Top-level code that introspects WasmGC
322
+ * structs (`for…in` / `Object.keys` over a runtime-shaped object) needs the
323
+ * `__struct_field_names` / `__sget_*` exports, which only exist once the
324
+ * instance is constructed so during the start section they resolve to
325
+ * nothing and a `for…in` enumerates zero keys. The standalone/WASI path does
326
+ * NOT hit this: it runs top-level code via an explicitly-called `_start`
327
+ * export AFTER instantiation, when every export is reachable.
328
+ *
329
+ * When `true`, emit the top-level `__module_init` as an EXPORT and do NOT run
330
+ * it via the wasm `start` section, so the host can invoke
331
+ * `instance.exports.__module_init()` AFTER wiring `setExports` — symmetric
332
+ * with the standalone `_start` model. The differential-test harness
333
+ * (`scripts/diff-test.ts`) sets this so the HOST lane runs top-level code with
334
+ * the same fully-wired runtime the standalone lane uses, rather than tripping
335
+ * over an exports-timing artifact of the harness. Default `false` →
336
+ * byte-identical output (top-level runs in the wasm `start` section) for every
337
+ * other consumer (website, playground, test262, library users).
338
+ */
339
+ deferTopLevelInit?: boolean;
340
+ /**
341
+ * #2783 — general `--link <namespace>` dynamic-linking axis (the ONLY
342
+ * link-vs-inline control; the old `linkNodeShims` boolean was removed). Each
343
+ * listed namespace is left as a **link-time import** (satisfied at
344
+ * instantiation by a preloaded provider module, e.g.
345
+ * `wasmtime --preload node:fs=node-fs.wasm`) instead of being inline-lowered to
346
+ * a self-contained module. "Leave-as-import" is the universal capability (any
347
+ * external namespace can be a wasm import); "inline-lower" is the special
348
+ * capability the compiler only has for a known few (`node:fs` fd IO). So for an
349
+ * arbitrary namespace `link: ["acme:telemetry"]` simply permits its imports
350
+ * past the strict `--no-host-imports` / WASI gate; for `node:fs` it
351
+ * additionally selects the import-and-link std-IO path (the user module imports
352
+ * `readSync`/`writeSync` + its linear memory from `node:fs` and carries no
353
+ * `wasi_snapshot_preview1` import for stream IO; console.log /
354
+ * process.std*.write lower to `writeSync(1|2, …)`, stdin is `readSync(0, …)`).
355
+ *
356
+ * WASI-gated: ignored for non-WASI targets. Default empty — every namespace
357
+ * stays standalone / inline-lowered. CLI: `--link <ns>` (repeatable).
329
358
  */
330
- linkNodeShims?: boolean;
359
+ link?: string[];
331
360
  /**
332
361
  * Node API emulation (#2603). Opt-in via `--emulate node`. When set, the
333
362
  * checker is given an ambient `process` declaration so Node globals js2wasm
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as path from "path";
2
- import { i as isKnownLibName, g as getLibSourceFile, a as getDefaultEnvironment, r as rewriteCjsRequire, f as forEachChild, e as entryHasRelativeImports, c as compileSource, b as compileMultiSource, d as buildImports, h as compileFilesSource, j as compileToObjectSource } from "./runtime-d_SmcBlN.js";
3
- import { k, l, m, n, o, p, q, s, t } from "./runtime-d_SmcBlN.js";
2
+ import { i as isKnownLibName, g as getLibSourceFile, a as getDefaultEnvironment, r as rewriteCjsRequire, f as forEachChild, e as entryHasRelativeImports, c as compileSource, b as compileMultiSource, d as buildImports, h as compileFilesSource, j as compileToObjectSource } from "./runtime-UuI75J3h.js";
3
+ import { k, l, m, n, o, p, q, s, t } from "./runtime-UuI75J3h.js";
4
4
  import ts from "typescript";
5
5
  class IncrementalLanguageService {
6
6
  currentSource = "";