Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: swiftwasm/JavaScriptKit
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: swiftwasm/JavaScriptKit
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: bridgejs-abi-instruction-stream
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 7 commits
  • 31 files changed
  • 2 contributors

Commits on Jul 16, 2026

  1. 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
    kateinoigakukun and claude committed Jul 16, 2026
    Configuration menu
    Copy the full SHA
    4710403 View commit details
    Browse the repository at this point in the history
  2. 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
    kateinoigakukun and claude committed Jul 16, 2026
    Configuration menu
    Copy the full SHA
    b7060dd View commit details
    Browse the repository at this point in the history
  3. 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
    kateinoigakukun and claude committed Jul 16, 2026
    Configuration menu
    Copy the full SHA
    316416a View commit details
    Browse the repository at this point in the history
  4. 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_01RtoNMUDZsVAG4cd9ENb72t
    kateinoigakukun and claude committed Jul 16, 2026
    Configuration menu
    Copy the full SHA
    1bd6099 View commit details
    Browse the repository at this point in the history
  5. 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_01RtoNMUDZsVAG4cd9ENb72t
    kateinoigakukun and claude committed Jul 16, 2026
    Configuration menu
    Copy the full SHA
    0c44394 View commit details
    Browse the repository at this point in the history

Commits on Jul 19, 2026

  1. 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_01RtoNMUDZsVAG4cd9ENb72t
    kateinoigakukun and claude committed Jul 19, 2026
    Configuration menu
    Copy the full SHA
    4c47ace View commit details
    Browse the repository at this point in the history
  2. 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
    kateinoigakukun and claude committed Jul 19, 2026
    Configuration menu
    Copy the full SHA
    fb05ba6 View commit details
    Browse the repository at this point in the history
Loading