Skip to content

Commit cea489c

Browse files
committed
BridgeJS: Add bulk copy for JS-to-Swift typed array parameters
Use the string-lowering retain-allocate-callback pattern for typed array parameters: JS retains the TypedArray, passes (id, count) as WASM params, Swift allocates via Array(unsafeUninitializedCapacity:), then calls swift_js_init_typed_array_memory to bulk copy the data. This completes bidirectional typed array optimization. Both Swift->JS and JS->Swift now use bulk memory copy instead of element-by-element stack serialization for top-level typed array parameters and returns. Nested contexts (struct fields, dictionary values) continue using element-by-element as a safe fallback.
1 parent d4fbbbf commit cea489c

60 files changed

Lines changed: 478 additions & 66 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/BridgeJSCore/ExportSwift.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,14 @@ public class ExportSwift {
137137
case .swiftStruct(let structName):
138138
typeNameForIntrinsic = structName
139139
liftingExpr = ExprSyntax("\(raw: structName).bridgeJSLiftParameter()")
140-
case .array, .typedArray:
140+
case .array:
141141
typeNameForIntrinsic = param.type.swiftType
142142
liftingExpr = StackCodegen().liftExpression(for: param.type)
143+
case .typedArray:
144+
typeNameForIntrinsic = param.type.swiftType
145+
liftingExpr = ExprSyntax(
146+
"_bridgeJS_typedArrayLiftParameter(\(raw: argumentsToLift.joined(separator: ", ")))"
147+
)
143148
case .nullable(let wrappedType, let kind):
144149
let optionalSwiftType: String
145150
if case .null = kind {
@@ -1517,7 +1522,7 @@ extension BridgeType {
15171522

15181523
var isStackUsingParameter: Bool {
15191524
switch self {
1520-
case .swiftStruct, .array, .dictionary, .associatedValueEnum, .typedArray:
1525+
case .swiftStruct, .array, .dictionary, .associatedValueEnum:
15211526
return true
15221527
case .nullable(let wrapped, _):
15231528
return wrapped.isStackUsingParameter
@@ -1577,8 +1582,10 @@ extension BridgeType {
15771582
throw BridgeJSCoreError("Namespace enums are not supported to pass as parameters")
15781583
case .closure:
15791584
return LiftingIntrinsicInfo(parameters: [("callbackId", .i32)])
1580-
case .array, .dictionary, .typedArray:
1585+
case .array, .dictionary:
15811586
return LiftingIntrinsicInfo(parameters: [])
1587+
case .typedArray:
1588+
return LiftingIntrinsicInfo(parameters: [("sourceId", .i32), ("count", .i32)])
15821589
}
15831590
}
15841591

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,22 @@ public struct BridgeJSLink {
397397
printer.write("bytes.set(source);")
398398
}
399399
printer.write("}")
400+
printer.write(
401+
"bjs[\"swift_js_init_typed_array_memory\"] = function(sourceId, destPtr, byteLength) {"
402+
)
403+
printer.indent {
404+
printer.write(
405+
"const source = \(JSGlueVariableScope.reservedSwift).\(JSGlueVariableScope.reservedMemory).getObject(sourceId);"
406+
)
407+
printer.write(
408+
"\(JSGlueVariableScope.reservedSwift).\(JSGlueVariableScope.reservedMemory).release(sourceId);"
409+
)
410+
printer.write(
411+
"const dest = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, destPtr, byteLength);"
412+
)
413+
printer.write("dest.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));")
414+
}
415+
printer.write("}")
400416
printer.write("bjs[\"swift_js_make_js_string\"] = function(ptr, len) {")
401417
printer.indent {
402418
printer.write(

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,7 +1325,7 @@ struct IntrinsicJSFragment: Sendable {
13251325
case .array(let elementType):
13261326
return try arrayLower(elementType: elementType)
13271327
case .typedArray(let kind):
1328-
return try typedArrayLower(kind: kind)
1328+
return try typedArrayLowerParameter(kind: kind)
13291329
case .dictionary(let valueType):
13301330
return try dictionaryLower(valueType: valueType)
13311331
default:
@@ -1567,7 +1567,7 @@ struct IntrinsicJSFragment: Sendable {
15671567
case .array(let elementType):
15681568
return try arrayLower(elementType: elementType)
15691569
case .typedArray(let kind):
1570-
return try typedArrayLower(kind: kind)
1570+
return try typedArrayLowerElementByElement(kind: kind)
15711571
case .dictionary(let valueType):
15721572
return try dictionaryLower(valueType: valueType)
15731573
default:
@@ -1964,10 +1964,34 @@ struct IntrinsicJSFragment: Sendable {
19641964
)
19651965
}
19661966

1967-
/// Lowers a TypedArray from JS to Swift: fall back to array element-by-element protocol.
1968-
/// JS → Swift typed array optimization is deferred (no WASM allocator available).
1969-
static func typedArrayLower(kind: TypedArrayKind) throws -> IntrinsicJSFragment {
1970-
// Convert the TypedArrayKind back to a BridgeType element type and delegate to arrayLower
1967+
/// Lowers a TypedArray from JS to Swift by retaining it in the JS heap and passing
1968+
/// (retainedId, length) as two i32 WASM params. The Swift side then bulk-copies
1969+
/// via `_bridgeJS_typedArrayLiftParameter`. Mirrors the string lowering pattern.
1970+
/// Used for JS → Swift parameter lowering (export direction).
1971+
static func typedArrayLowerParameter(kind: TypedArrayKind) throws -> IntrinsicJSFragment {
1972+
let jsConstructorName = kind.jsConstructorName
1973+
return IntrinsicJSFragment(
1974+
parameters: ["arr"],
1975+
printCode: { arguments, context in
1976+
let (scope, printer) = (context.scope, context.printer)
1977+
let arr = arguments[0]
1978+
let typedVar = scope.variable("\(arr)Typed")
1979+
let retainedIdVar = scope.variable("\(arr)Id")
1980+
// Ensure input is a proper TypedArray (plain Array<number> must be converted)
1981+
printer.write(
1982+
"const \(typedVar) = \(arr) instanceof \(jsConstructorName) ? \(arr) : new \(jsConstructorName)(\(arr));"
1983+
)
1984+
printer.write(
1985+
"const \(retainedIdVar) = \(JSGlueVariableScope.reservedSwift).memory.retain(\(typedVar));"
1986+
)
1987+
return [retainedIdVar, "\(typedVar).length"]
1988+
}
1989+
)
1990+
}
1991+
1992+
/// Lowers a TypedArray from JS to Swift using element-by-element stack protocol.
1993+
/// Used for return value lowering (import direction) and nested contexts (struct fields).
1994+
static func typedArrayLowerElementByElement(kind: TypedArrayKind) throws -> IntrinsicJSFragment {
19711995
let elementType: BridgeType
19721996
switch kind {
19731997
case .int8: elementType = .integer(.int8)
@@ -2270,7 +2294,7 @@ struct IntrinsicJSFragment: Sendable {
22702294
case .dictionary(let valueType):
22712295
return try dictionaryLower(valueType: valueType)
22722296
case .typedArray(let kind):
2273-
return try typedArrayLower(kind: kind)
2297+
return try typedArrayLowerElementByElement(kind: kind)
22742298
default:
22752299
throw BridgeJSLinkError(message: "Unsupported array element type for lowering: \(elementType)")
22762300
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/TypedArrayOverride.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
@_expose(wasm, "bjs_processData")
22
@_cdecl("bjs_processData")
3-
public func _bjs_processData() -> Void {
3+
public func _bjs_processData(_ dataSourceId: Int32, _ dataCount: Int32) -> Void {
44
#if arch(wasm32)
5-
let ret = processData(_: [UInt8].bridgeJSStackPop())
5+
let ret = processData(_: _bridgeJS_typedArrayLiftParameter(dataSourceId, dataCount))
66
_bridgeJS_typedArrayPush(ret)
77
#else
88
fatalError("Only available on WebAssembly")
@@ -11,9 +11,9 @@ public func _bjs_processData() -> Void {
1111

1212
@_expose(wasm, "bjs_processFloat32")
1313
@_cdecl("bjs_processFloat32")
14-
public func _bjs_processFloat32() -> Void {
14+
public func _bjs_processFloat32(_ dataSourceId: Int32, _ dataCount: Int32) -> Void {
1515
#if arch(wasm32)
16-
let ret = processFloat32(_: [Float].bridgeJSStackPop())
16+
let ret = processFloat32(_: _bridgeJS_typedArrayLiftParameter(dataSourceId, dataCount))
1717
_bridgeJS_typedArrayPush(ret)
1818
#else
1919
fatalError("Only available on WebAssembly")
@@ -55,9 +55,9 @@ public func _bjs_GPURenderer_init() -> UnsafeMutableRawPointer {
5555

5656
@_expose(wasm, "bjs_GPURenderer_writeVertexData")
5757
@_cdecl("bjs_GPURenderer_writeVertexData")
58-
public func _bjs_GPURenderer_writeVertexData(_ _self: UnsafeMutableRawPointer) -> Void {
58+
public func _bjs_GPURenderer_writeVertexData(_ _self: UnsafeMutableRawPointer, _ dataSourceId: Int32, _ dataCount: Int32) -> Void {
5959
#if arch(wasm32)
60-
let ret = GPURenderer.bridgeJSLiftParameter(_self).writeVertexData(_: [Float].bridgeJSStackPop())
60+
let ret = GPURenderer.bridgeJSLiftParameter(_self).writeVertexData(_: _bridgeJS_typedArrayLiftParameter(dataSourceId, dataCount))
6161
_bridgeJS_typedArrayPush(ret)
6262
#else
6363
fatalError("Only available on WebAssembly")

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/TypedArrayTypes.TypedArray.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
@_expose(wasm, "bjs_processUint8Array")
22
@_cdecl("bjs_processUint8Array")
3-
public func _bjs_processUint8Array() -> Void {
3+
public func _bjs_processUint8Array(_ dataSourceId: Int32, _ dataCount: Int32) -> Void {
44
#if arch(wasm32)
5-
let ret = processUint8Array(_: [UInt8].bridgeJSStackPop())
5+
let ret = processUint8Array(_: _bridgeJS_typedArrayLiftParameter(dataSourceId, dataCount))
66
_bridgeJS_typedArrayPush(ret)
77
#else
88
fatalError("Only available on WebAssembly")
@@ -11,9 +11,9 @@ public func _bjs_processUint8Array() -> Void {
1111

1212
@_expose(wasm, "bjs_processFloat32Array")
1313
@_cdecl("bjs_processFloat32Array")
14-
public func _bjs_processFloat32Array() -> Void {
14+
public func _bjs_processFloat32Array(_ dataSourceId: Int32, _ dataCount: Int32) -> Void {
1515
#if arch(wasm32)
16-
let ret = processFloat32Array(_: [Float].bridgeJSStackPop())
16+
let ret = processFloat32Array(_: _bridgeJS_typedArrayLiftParameter(dataSourceId, dataCount))
1717
_bridgeJS_typedArrayPush(ret)
1818
#else
1919
fatalError("Only available on WebAssembly")
@@ -22,9 +22,9 @@ public func _bjs_processFloat32Array() -> Void {
2222

2323
@_expose(wasm, "bjs_processFloat64Array")
2424
@_cdecl("bjs_processFloat64Array")
25-
public func _bjs_processFloat64Array() -> Void {
25+
public func _bjs_processFloat64Array(_ dataSourceId: Int32, _ dataCount: Int32) -> Void {
2626
#if arch(wasm32)
27-
let ret = processFloat64Array(_: [Double].bridgeJSStackPop())
27+
let ret = processFloat64Array(_: _bridgeJS_typedArrayLiftParameter(dataSourceId, dataCount))
2828
_bridgeJS_typedArrayPush(ret)
2929
#else
3030
fatalError("Only available on WebAssembly")
@@ -33,9 +33,9 @@ public func _bjs_processFloat64Array() -> Void {
3333

3434
@_expose(wasm, "bjs_processInt32Array")
3535
@_cdecl("bjs_processInt32Array")
36-
public func _bjs_processInt32Array() -> Void {
36+
public func _bjs_processInt32Array(_ dataSourceId: Int32, _ dataCount: Int32) -> Void {
3737
#if arch(wasm32)
38-
let ret = processInt32Array(_: [Int32].bridgeJSStackPop())
38+
let ret = processInt32Array(_: _bridgeJS_typedArrayLiftParameter(dataSourceId, dataCount))
3939
_bridgeJS_typedArrayPush(ret)
4040
#else
4141
fatalError("Only available on WebAssembly")
@@ -44,9 +44,9 @@ public func _bjs_processInt32Array() -> Void {
4444

4545
@_expose(wasm, "bjs_processInt16Array")
4646
@_cdecl("bjs_processInt16Array")
47-
public func _bjs_processInt16Array() -> Void {
47+
public func _bjs_processInt16Array(_ dataSourceId: Int32, _ dataCount: Int32) -> Void {
4848
#if arch(wasm32)
49-
let ret = processInt16Array(_: [Int16].bridgeJSStackPop())
49+
let ret = processInt16Array(_: _bridgeJS_typedArrayLiftParameter(dataSourceId, dataCount))
5050
_bridgeJS_typedArrayPush(ret)
5151
#else
5252
fatalError("Only available on WebAssembly")

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ export async function createInstantiator(options, swift) {
7373
const bytes = new Uint8Array(memory.buffer, bytesPtr);
7474
bytes.set(source);
7575
}
76+
bjs["swift_js_init_typed_array_memory"] = function(sourceId, destPtr, byteLength) {
77+
const source = swift.memory.getObject(sourceId);
78+
swift.memory.release(sourceId);
79+
const dest = new Uint8Array(memory.buffer, destPtr, byteLength);
80+
dest.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));
81+
}
7682
bjs["swift_js_make_js_string"] = function(ptr, len) {
7783
return swift.memory.retain(decodeString(ptr, len));
7884
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ export async function createInstantiator(options, swift) {
4848
const bytes = new Uint8Array(memory.buffer, bytesPtr);
4949
bytes.set(source);
5050
}
51+
bjs["swift_js_init_typed_array_memory"] = function(sourceId, destPtr, byteLength) {
52+
const source = swift.memory.getObject(sourceId);
53+
swift.memory.release(sourceId);
54+
const dest = new Uint8Array(memory.buffer, destPtr, byteLength);
55+
dest.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));
56+
}
5157
bjs["swift_js_make_js_string"] = function(ptr, len) {
5258
return swift.memory.retain(decodeString(ptr, len));
5359
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ export async function createInstantiator(options, swift) {
163163
const bytes = new Uint8Array(memory.buffer, bytesPtr);
164164
bytes.set(source);
165165
}
166+
bjs["swift_js_init_typed_array_memory"] = function(sourceId, destPtr, byteLength) {
167+
const source = swift.memory.getObject(sourceId);
168+
swift.memory.release(sourceId);
169+
const dest = new Uint8Array(memory.buffer, destPtr, byteLength);
170+
dest.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));
171+
}
166172
bjs["swift_js_make_js_string"] = function(ptr, len) {
167173
return swift.memory.retain(decodeString(ptr, len));
168174
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ export async function createInstantiator(options, swift) {
162162
const bytes = new Uint8Array(memory.buffer, bytesPtr);
163163
bytes.set(source);
164164
}
165+
bjs["swift_js_init_typed_array_memory"] = function(sourceId, destPtr, byteLength) {
166+
const source = swift.memory.getObject(sourceId);
167+
swift.memory.release(sourceId);
168+
const dest = new Uint8Array(memory.buffer, destPtr, byteLength);
169+
dest.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));
170+
}
165171
bjs["swift_js_make_js_string"] = function(ptr, len) {
166172
return swift.memory.retain(decodeString(ptr, len));
167173
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ export async function createInstantiator(options, swift) {
9090
const bytes = new Uint8Array(memory.buffer, bytesPtr);
9191
bytes.set(source);
9292
}
93+
bjs["swift_js_init_typed_array_memory"] = function(sourceId, destPtr, byteLength) {
94+
const source = swift.memory.getObject(sourceId);
95+
swift.memory.release(sourceId);
96+
const dest = new Uint8Array(memory.buffer, destPtr, byteLength);
97+
dest.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));
98+
}
9399
bjs["swift_js_make_js_string"] = function(ptr, len) {
94100
return swift.memory.retain(decodeString(ptr, len));
95101
}

0 commit comments

Comments
 (0)