Skip to content

Commit 8cb4535

Browse files
committed
BridgeJS: Optimize numeric array transfer with bulk TypedArray copy
Add bridgeJSStackPushAsArray/bridgeJSStackPopAsArray specialization points to _BridgedSwiftStackType protocol. Numeric types override bridgeJSStackPushAsArray with bulk TypedArray transfer via swift_js_push_typed_array. Array.bridgeJSStackPush() delegates to Element.bridgeJSStackPushAsArray() — no codegen changes needed. JS arrayLift uses -1 count discriminator to detect bulk path and pops pre-built Array from taStack instead of element-by-element. Non-numeric arrays use the default element-by-element implementation.
1 parent fc672e7 commit 8cb4535

55 files changed

Lines changed: 1208 additions & 362 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ public struct BridgeJSLink {
347347
"let \(JSGlueVariableScope.reservedF32Stack) = [];",
348348
"let \(JSGlueVariableScope.reservedF64Stack) = [];",
349349
"let \(JSGlueVariableScope.reservedPointerStack) = [];",
350+
"let taStack = [];",
350351
"const \(JSGlueVariableScope.reservedEnumHelpers) = {};",
351352
"const \(JSGlueVariableScope.reservedStructHelpers) = {};",
352353
"",
@@ -489,6 +490,21 @@ public struct BridgeJSLink {
489490
printer.write("return \(JSGlueVariableScope.reservedI64Stack).pop();")
490491
}
491492
printer.write("}")
493+
// Typed array constructors indexed by kind (must match _BridgedNumericArrayKind)
494+
printer.write(
495+
"const taCtors = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array];"
496+
)
497+
printer.write("bjs[\"swift_js_push_typed_array\"] = function(kind, ptr, count) {")
498+
printer.indent {
499+
printer.write("const Ctor = taCtors[kind];")
500+
printer.write("const byteLen = count * Ctor.BYTES_PER_ELEMENT;")
501+
// slice() copies the bytes into a new ArrayBuffer that is properly aligned
502+
printer.write(
503+
"const copy = \(JSGlueVariableScope.reservedMemory).buffer.slice(ptr, ptr + byteLen);"
504+
)
505+
printer.write("taStack.push(Array.from(new Ctor(copy)));")
506+
}
507+
printer.write("}")
492508
if !allStructs.isEmpty {
493509
for structDef in allStructs {
494510
printer.write("bjs[\"swift_js_struct_lower_\(structDef.abiName)\"] = function(objectId) {")

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,28 +1888,43 @@ struct IntrinsicJSFragment: Sendable {
18881888
)
18891889
}
18901890

1891-
/// Lifts an array from Swift to JS by popping elements from stacks
1891+
/// Lifts an array from Swift to JS by popping elements from stacks.
1892+
///
1893+
/// When the count discriminator is `-1`, the Swift side used the bulk
1894+
/// typed-array push path and the result is already on `taStack`.
1895+
/// Otherwise, elements are popped one-by-one from the typed stacks.
18921896
static func arrayLift(elementType: BridgeType) throws -> IntrinsicJSFragment {
18931897
return IntrinsicJSFragment(
18941898
parameters: [],
18951899
printCode: { arguments, context in
18961900
let (scope, printer) = (context.scope, context.printer)
18971901
let resultVar = scope.variable("arrayResult")
18981902
let lenVar = scope.variable("arrayLen")
1899-
let iVar = scope.variable("i")
19001903

19011904
printer.write("const \(lenVar) = \(scope.popI32());")
1902-
printer.write("const \(resultVar) = [];")
1903-
printer.write("for (let \(iVar) = 0; \(iVar) < \(lenVar); \(iVar)++) {")
1905+
printer.write("let \(resultVar);")
1906+
printer.write("if (\(lenVar) === -1) {")
1907+
printer.indent {
1908+
// Bulk path: Swift pushed a typed array onto taStack
1909+
printer.write("\(resultVar) = taStack.pop();")
1910+
}
1911+
printer.write("} else {")
19041912
try printer.indent {
1905-
let elementFragment = try stackLiftFragment(elementType: elementType)
1906-
let elementResults = try elementFragment.printCode([], context)
1907-
if let elementExpr = elementResults.first {
1908-
printer.write("\(resultVar).push(\(elementExpr));")
1913+
// Element-by-element path (original behavior)
1914+
let iVar = scope.variable("i")
1915+
printer.write("\(resultVar) = [];")
1916+
printer.write("for (let \(iVar) = 0; \(iVar) < \(lenVar); \(iVar)++) {")
1917+
try printer.indent {
1918+
let elementFragment = try stackLiftFragment(elementType: elementType)
1919+
let elementResults = try elementFragment.printCode([], context)
1920+
if let elementExpr = elementResults.first {
1921+
printer.write("\(resultVar).push(\(elementExpr));")
1922+
}
19091923
}
1924+
printer.write("}")
1925+
printer.write("\(resultVar).reverse();")
19101926
}
19111927
printer.write("}")
1912-
printer.write("\(resultVar).reverse();")
19131928
return [resultVar]
19141929
}
19151930
)

0 commit comments

Comments
 (0)