Skip to content

Commit 5afa814

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 f1f290b commit 5afa814

53 files changed

Lines changed: 1212 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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ public struct BridgeJSLink {
345345
"let \(JSGlueVariableScope.reservedF32Stack) = [];",
346346
"let \(JSGlueVariableScope.reservedF64Stack) = [];",
347347
"let \(JSGlueVariableScope.reservedPointerStack) = [];",
348+
"let taStack = [];",
348349
"const \(JSGlueVariableScope.reservedEnumHelpers) = {};",
349350
"const \(JSGlueVariableScope.reservedStructHelpers) = {};",
350351
"",
@@ -487,6 +488,19 @@ public struct BridgeJSLink {
487488
printer.write("return \(JSGlueVariableScope.reservedI64Stack).pop();")
488489
}
489490
printer.write("}")
491+
// Typed array constructors indexed by kind (must match _BridgedNumericArrayKind)
492+
printer.write("const taCtors = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array];")
493+
printer.write("bjs[\"swift_js_push_typed_array\"] = function(kind, ptr, count) {")
494+
printer.indent {
495+
printer.write("const Ctor = taCtors[kind];")
496+
printer.write("const byteLen = count * Ctor.BYTES_PER_ELEMENT;")
497+
// slice() copies the bytes into a new ArrayBuffer that is properly aligned
498+
printer.write(
499+
"const copy = \(JSGlueVariableScope.reservedMemory).buffer.slice(ptr, ptr + byteLen);"
500+
)
501+
printer.write("taStack.push(Array.from(new Ctor(copy)));")
502+
}
503+
printer.write("}")
490504
if !allStructs.isEmpty {
491505
for structDef in allStructs {
492506
printer.write("bjs[\"swift_js_struct_lower_\(structDef.abiName)\"] = function(objectId) {")
@@ -3594,6 +3608,9 @@ extension BridgeType {
35943608
case .bool:
35953609
return "boolean"
35963610
case .jsObject(let name):
3611+
if let name, let tsName = Self.jsTypedArrayTSNames[name] {
3612+
return tsName
3613+
}
35973614
return name ?? "any"
35983615
case .jsValue:
35993616
return "any"
@@ -3630,6 +3647,18 @@ extension BridgeType {
36303647
return "Record<string, \(valueType.tsType)>"
36313648
}
36323649
}
3650+
3651+
/// Maps JSTypedArray Swift typealias names to their JavaScript TypedArray constructor names.
3652+
private static let jsTypedArrayTSNames: [String: String] = [
3653+
"JSInt8Array": "Int8Array",
3654+
"JSUint8Array": "Uint8Array",
3655+
"JSInt16Array": "Int16Array",
3656+
"JSUint16Array": "Uint16Array",
3657+
"JSInt32Array": "Int32Array",
3658+
"JSUint32Array": "Uint32Array",
3659+
"JSFloat32Array": "Float32Array",
3660+
"JSFloat64Array": "Float64Array",
3661+
]
36333662
}
36343663

36353664
extension WasmCoreType {

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)