Skip to content

BridgeJS: Fix argument order for stack-lowered imported parameters - #794

Merged
krodak merged 1 commit into
swiftwasm:mainfrom
PassiveLogic:kr/fix-import-stack-param-order
Jul 28, 2026
Merged

BridgeJS: Fix argument order for stack-lowered imported parameters#794
krodak merged 1 commit into
swiftwasm:mainfrom
PassiveLogic:kr/fix-import-stack-param-order

Conversation

@krodak

@krodak krodak commented Jul 28, 2026

Copy link
Copy Markdown
Member

Overview

Imported parameters that push onto the shared value stacks are lowered in reverse declaration order, because the JavaScript side pops them in declaration order and the stacks are LIFO. CallJSEmission selected that bucket with loweredParameters.isEmpty, which is wrong for a parameter that does both: an optional container passes an isSome discriminator as a wasm parameter and its payload on the stack, so it was emitted in forward order ahead of everything in the reversed bucket.

Combining one with any other stack-lowered parameter therefore transposed the arguments. With [String: V]?, a @JS struct?, or a @JS enum with associated values it is worse than a transposition: the array codec reads the other parameter's scalar as an element count and builds a garbage-length array.

Every parameter is now lowered into stackLoweringStmts in reverse declaration order, so there is one order rather than two and no per-type judgement to keep in sync. The JavaScript side is unchanged, and the 21 changed snapshots are a pure reordering of independent scalar lowerings.

Before / after

@JSFunction func f(_ a: [Int]?, _ b: [Int]) throws(JSException) -> Int

f([1, 2], [7, 8, 9])   // before: JavaScript received f([7, 8, 9], [1, 2])
// before — a's payload is pushed first, so JS pops it as b
let aIsSome = a.bridgeJSLowerParameter()
let _ = b.bridgeJSLowerParameter()

// after
let _ = b.bridgeJSLowerParameter()
let aIsSome = a.bridgeJSLowerParameter()

@krodak krodak self-assigned this Jul 28, 2026
@krodak
krodak requested a review from kateinoigakukun July 28, 2026 16:38
@krodak
krodak merged commit b4b8027 into swiftwasm:main Jul 28, 2026
16 checks passed
krodak added a commit to PassiveLogic/JavaScriptKit that referenced this pull request Aug 11, 2026
An imported optional whose payload is stack-only ([T]?, [String: V]?,
@js struct?) used a hybrid convention: the isSome flag crossed as a wasm
i32 parameter while the payload was conditionally pushed onto the shared
stacks. Optional returns and optional array elements of the same types
already travel entirely on the stacks: payload first, then a 0/1 flag on
the i32 stack.

This lowers those parameters the same way. The Swift thunk pushes the
payload (if some) followed by the flag, the wasm signature carries no
argument for the parameter, and the JS handler pops the flag before
conditionally lifting the payload, through the same fragment already
used for optional returns and elements.

The hybrid shape was the last parameter category that both passed a wasm
argument and pushed stack data, which is what enabled the argument
transposition fixed in swiftwasm#794. Every stack-touching parameter is now
flagless and reverse-ordered, matching returns and elements. All other
optional parameter ABIs (scalars, strings, JSObject, closures, enums,
heap objects) are unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants