-
-
Notifications
You must be signed in to change notification settings - Fork 75
Comparing changes
Open a pull request
base repository: swiftwasm/JavaScriptKit
base: main
head repository: swiftwasm/JavaScriptKit
compare: bridgejs-abi-instruction-stream
- 7 commits
- 31 files changed
- 2 contributors
Commits on Jul 16, 2026
-
BridgeJS: Describe the ABI once, generate/interpret both sides from it
The ABI (Wasm signatures + the typed push/pop "stack ABI") used to be re-derived by hand in six places that had to agree but weren't checked and had already drifted: four tables in ExportSwift/ImportTS, `wasmParams` in JSGlueGen, and the runtime intrinsics in BridgeJSIntrinsics.swift. Adding one BridgeType case meant editing all of them. Now there is one description and everything reads it: - `BridgeJSABI.swift` computes the flat Wasm signature (`shape(of:cell:)`); the four tables + `wasmParams` become thin projections of it. - The stack ABI is compiled straight from `BridgeType` into a flat `StackOp` instruction program (`StackABIProgram.swift`) and interpreted by a stack machine, following wasm-bindgen's Descriptor -> Instruction -> JsBuilder shape: BridgeType is the descriptor, `StackOp.compile` the lowering, `JSStackMachine` the interpreter. Lower/lift are separate programs (its incoming/outgoing split), so the interpreter is a straight forward pass. - The runtime's scalar conformances are generated from the same description by `BridgeJSTool emit-intrinsics` into Generated/BridgeJSIntrinsics+ABI.swift (wired into bridge-js-generate.sh, so CI's check-bridgejs-generated enforces it), mirroring wasm-bindgen's describe/trait native side. The irregular cases (String's two direction-specific formats, Array's -1 typed-array bulk path, the presence-flag optional) are named `StackOp` compound ops rather than smoothed over, so they are enumerable in one place. Output is byte-identical: unit + link snapshots unchanged, 187 runtime tests pass end-to-end (make unittest), generated intrinsics idempotent. Net ~850 lines of duplicated ABI logic removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtoNMUDZsVAG4cd9ENb72t
Configuration menu - View commit details
-
Copy full SHA for 4710403 - Browse repository at this point
Copy the full SHA 4710403View commit details -
BridgeJS: Contain the array bulk/counted discriminator to numeric arrays
The typed-array `-1` discriminator used to be checked on the lift of *every* array, even though only numeric element types (`_BridgedNumericArray`) can take the typed-array fast path. The instruction-stream codegen knows the element type statically, so it now emits the discriminator only for numeric-looking elements and plain counted code (no discriminator) for everything else - strings, structs, objects, enums, optionals, nested arrays. That removes a dead branch from the common case; net ~220 fewer lines of generated JS, no wire change, no runtime change. Kept, deliberately, for numeric-looking elements: an earlier attempt to drop the discriminator entirely (specialize numeric arrays to a pure typed-array pop) desynced on `@JS(as: Int)` aliases - the codegen unaliases `[UserId]` to `[Int]` and would pick the bulk path, but the alias's array bridges through the counted path because the wrapper isn't layout-compatible with `[Int]`. `make unittest` caught it. The lesson: the runtime `-1` signal decouples the JS codegen from the runtime's bulk-eligibility rules, which the codegen can't safely replicate. So the discriminator stays where the runtime might bulk, and the runtime remains the single authority; `isPossiblyBulkNumericElement` is intentionally a "might", not a "does". All 187 runtime tests pass end-to-end (incl. empty and nested numeric arrays, and the alias arrays that exposed the desync); snapshots regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtoNMUDZsVAG4cd9ENb72t
Configuration menu - View commit details
-
Copy full SHA for b7060dd - Browse repository at this point
Copy the full SHA b7060ddView commit details -
BridgeJS: Derive Swift/TS/mangled spellings from a type's essence
swiftType, tsType, and mangleTypeName were three ~20-arm switches over BridgeType, scattered across ExportSwift, BridgeJSLink, and BridgeJSSkeleton. Adding a type meant editing all three. They are now derived from `BridgeType.essence` (TypeEssence.swift): every BridgeType reduces to one of a few structural classes -- scalar (three irreducible names), nominal (a name + a kind that alone determines the mangling suffix V/O/P/C and the spelling shape), and the combinators -- and each facet is one general rule that folds over the essence rather than a per-case switch. Adding a scalar is now one essence entry with no new switch arm anywhere; the general rules switch on the handful of structural classes, not the 20 type cases. `essence(of:)` is the single place that enumerates every BridgeType case. Output is byte-identical (snapshots unchanged); TypeEssenceTests pins the derived spellings against goldens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtoNMUDZsVAG4cd9ENb72t
Configuration menu - View commit details
-
Copy full SHA for 316416a - Browse repository at this point
Copy the full SHA 316416aView commit details -
BridgeJS: Remove namespaceEnum from BridgeType — a namespace is not a…
… value `BridgeType.namespaceEnum` represented an empty `@JS enum` (a namespace) used in a value position. But you can never have a *value* of an empty enum, so every ABI path rejected it and it never appeared in a value position in any snapshot -- it was a placeholder for an always-invalid use, sitting as a peer of real value types. It is gone. Using a namespace enum as a value type is now rejected where it is resolved -- at the parser for same-module use ("'X' is a namespace, not a value type"), and generically for cross-module references -- instead of being carried through the whole pipeline as a never-valid bridged type and thrown on at the ABI layer. This deletes the `.namespaceEnum` arm from ~20 switches. (`StaticContext.namespaceEnum`, which marks a static member's namespace, is a different enum and is unaffected.) Note: `Utils?` (an optional of a namespace enum -- always nil) was previously accepted and is now rejected too, which is the point: a namespace has no values, optional or otherwise. No runtime test or example used it; the one unit test that pinned the old behavior now pins the rejection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtoNMUDZsVAG4cd9ENb72tConfiguration menu - View commit details
-
Copy full SHA for 1bd6099 - Browse repository at this point
Copy the full SHA 1bd6099View commit details -
BridgeJS: Keep a namespace's identity so its misuse diagnoses helpfully
Separating a type's declaration identity from its ABI is what lets diagnostics stay helpful: a namespace has an identity ("Utils") but no value ABI, and the two must not be conflated. When namespaceEnum was removed, the external-module index simply dropped namespace enums, discarding that identity -- so a cross-module `Utils?` (a namespace used as a value) fell through to a generic "Unsupported type 'Utils'". The index now retains namespace identities separately from value types, so resolution reports the specific "'Utils' is a namespace, not a value type" -- the same message a same-module use already gets. Also fixes a latent double-diagnostic: a `T?` whose wrapped type is invalid now reports once (at the wrapped type, where the real problem is) instead of adding a second generic error for the whole optional. All unit tests + 187 runtime tests pass; snapshots unchanged (diagnostics-only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtoNMUDZsVAG4cd9ENb72tConfiguration menu - View commit details
-
Copy full SHA for 0c44394 - Browse repository at this point
Copy the full SHA 0c44394View commit details
Commits on Jul 19, 2026
-
BridgeJS: Desugar alias in the ABI description itself, uniformly
`.alias` transparency used to be expressed from the outside: callers pre-resolved sugar with scattered `.unaliased` calls, and the import-side shape functions asserted that discipline with two `preconditionFailure("must be resolved by .unaliased before reaching...")` arms -- while the export-side functions quietly delegated through `.alias` per-case. Same fact, three spellings, and a contract ("you should have desugared before calling me") that pushed the ABI's own invariant onto every caller. This follows how the Swift compiler models typealias sugar (TypeAliasType/ SugarType): the identity is preserved for spelling and diagnostics, and *semantic* queries desugar themselves -- you cannot hand `getAs<T>()` sugar and get a wrong answer. Applied here: - `importParameterShape`/`importReturnShape` now handle `.alias` exactly like the export cells: a delegation arm to the underlying type. Both preconditionFailures are gone. Their nested structural matches (optional-of-`@JS struct`, optional-of-jsObject) match on the canonical wrapped type, so aliases of either take the same path. - `StackOp.isPossiblyBulkNumericElement` sees through an aliased element itself instead of relying on callers to pre-desugar -- previously a `@JS(as: Int)` element handed in sugared would have silently lost the bulk/counted discriminator. - The now-redundant pre-desugaring at the enum-payload fragments is gone (`StackOp.compile` desugars on its own). - The convention is written once, on `BridgeType.unaliased`: `@JS(as:)` is sugar for the wire but a newtype for the runtime. Semantic queries desugar per-case; nested structural matches canonicalize the child at the match point; the JS glue fragment layer canonicalizes deeply at its entry points (it never needs the alias's identity); surface facets and the Swift emitters preserve the alias and dispatch on the spelled type via `_BridgedSwiftAlias`. - `ABIConformanceTests` pins the new contract: alias transparency is asserted for every leaf type in every cell, in both contexts, and behind one level of each container (full ABIShape equality, borrowing included), plus `payloadSlots` and compiled `StackOp` programs including the array-element position. Also strips the file's unused pattern bindings and a stale comment citing the removed `namespaceEnum` case. Behaviour-preserving: zero snapshot drift, and the wasm e2e suite passes (Tests: 187), including the alias round-trip tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtoNMUDZsVAG4cd9ENb72tConfiguration menu - View commit details
-
Copy full SHA for 4c47ace - Browse repository at this point
Copy the full SHA 4c47aceView commit details -
BridgeJS: Apply review cleanups across the ABI rework
Findings from an adversarially-verified review of the branch; all are duplication, dead code, or documentation that described code that no longer exists. No behaviour changes: zero snapshot drift, generated glue/intrinsics unchanged, wasm e2e passes (Tests: 187). - Remove code orphaned by the instruction-stream rework: JSGlueGen's `popExpression`/`emitPush`/`varHint` (their logic lives in `JSStackMachine` / `StackOp.pop` hints now), and `ABICoercion.asUintN64` plus its VM arm -- no compiler ever produces it; the UInt64 flat-return coercion deliberately lives in the emitters, as the ABICoercion doc says. - Derive `wasmSwiftTypeName` from `WasmCoreType.swiftType` -- the same spelling map the thunk emitters use -- instead of restating it, so the generated intrinsics cannot drift from the thunk signatures. Drop the `slot` parameter `conversion(for:)` never read; the conversion is derived entirely from the BridgeType. - Fix docs that named things that do not exist: `BridgeABI`'s header pointed at `term(of:direction:context:)` (the stack ABI is compiled to `StackOp` programs and interpreted by `JSStackMachine`), and `JSStackABIVM`'s header claimed a `SwiftStackABIInterpreter` runs the same programs -- the Swift half is generated by `SwiftRuntimeABIEmitter` for scalars and hand-written generic protocol code for containers. - `ExternalModuleIndex`: drop the `namespacesByModule` mirror map; it was fully derivable from `namespacesByPath`, and two maps that must be populated in lockstep are how the scoped and unscoped answers to "is X a namespace" drift apart. - Share the namespace-used-as-value diagnostic between the same-module and cross-module lookups (`DiagnosticError.namespaceUsedAsValue`) instead of duplicating the strings verbatim. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RtoNMUDZsVAG4cd9ENb72t
Configuration menu - View commit details
-
Copy full SHA for fb05ba6 - Browse repository at this point
Copy the full SHA fb05ba6View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff main...bridgejs-abi-instruction-stream