Skip to content

Commit 2990083

Browse files
authored
Optimized String bridging (Swift -> JS) (#816)
1 parent 777848d commit 2990083

104 files changed

Lines changed: 13775 additions & 1458 deletions

File tree

Some content is hidden

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

‎Benchmarks/Sources/Generated/BridgeJS.swift‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,20 +2429,20 @@ func _$benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void {
24292429

24302430
#if arch(wasm32)
24312431
@_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkRunner")
2432-
fileprivate func bjs_benchmarkRunner_extern(_ nameBytes: Int32, _ nameLength: Int32, _ body: Int32) -> Void
2432+
fileprivate func bjs_benchmarkRunner_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ body: Int32) -> Void
24332433
#else
2434-
fileprivate func bjs_benchmarkRunner_extern(_ nameBytes: Int32, _ nameLength: Int32, _ body: Int32) -> Void {
2434+
fileprivate func bjs_benchmarkRunner_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ body: Int32) -> Void {
24352435
fatalError("Only available on WebAssembly")
24362436
}
24372437
#endif
2438-
@inline(never) fileprivate func bjs_benchmarkRunner(_ nameBytes: Int32, _ nameLength: Int32, _ body: Int32) -> Void {
2439-
return bjs_benchmarkRunner_extern(nameBytes, nameLength, body)
2438+
@inline(never) fileprivate func bjs_benchmarkRunner(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ body: Int32) -> Void {
2439+
return bjs_benchmarkRunner_extern(nameWord0, nameWord1, nameWord2, body)
24402440
}
24412441

24422442
func _$benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Void {
2443-
name.bridgeJSWithLoweredParameter { (nameBytes, nameLength) in
2443+
name.bridgeJSWithLoweredParameter { (nameWord0, nameWord1, nameWord2) in
24442444
let bodyValue = body.bridgeJSLowerParameter()
2445-
bjs_benchmarkRunner(nameBytes, nameLength, bodyValue)
2445+
bjs_benchmarkRunner(nameWord0, nameWord1, nameWord2, bodyValue)
24462446
}
24472447
if let error = _swift_js_take_exception() {
24482448
throw error

‎Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,20 +268,20 @@ func _$createTS2Swift() throws(JSException) -> TS2Swift {
268268

269269
#if arch(wasm32)
270270
@_extern(wasm, module: "PlayBridgeJS", name: "bjs_TS2Swift_convert")
271-
fileprivate func bjs_TS2Swift_convert_extern(_ self: Int32, _ tsBytes: Int32, _ tsLength: Int32) -> Int32
271+
fileprivate func bjs_TS2Swift_convert_extern(_ self: Int32, _ tsWord0: Int32, _ tsWord1: Int32, _ tsWord2: Int32) -> Int32
272272
#else
273-
fileprivate func bjs_TS2Swift_convert_extern(_ self: Int32, _ tsBytes: Int32, _ tsLength: Int32) -> Int32 {
273+
fileprivate func bjs_TS2Swift_convert_extern(_ self: Int32, _ tsWord0: Int32, _ tsWord1: Int32, _ tsWord2: Int32) -> Int32 {
274274
fatalError("Only available on WebAssembly")
275275
}
276276
#endif
277-
@inline(never) fileprivate func bjs_TS2Swift_convert(_ self: Int32, _ tsBytes: Int32, _ tsLength: Int32) -> Int32 {
278-
return bjs_TS2Swift_convert_extern(self, tsBytes, tsLength)
277+
@inline(never) fileprivate func bjs_TS2Swift_convert(_ self: Int32, _ tsWord0: Int32, _ tsWord1: Int32, _ tsWord2: Int32) -> Int32 {
278+
return bjs_TS2Swift_convert_extern(self, tsWord0, tsWord1, tsWord2)
279279
}
280280

281281
func _$TS2Swift_convert(_ self: JSObject, _ ts: String) throws(JSException) -> String {
282-
let ret0 = ts.bridgeJSWithLoweredParameter { (tsBytes, tsLength) in
282+
let ret0 = ts.bridgeJSWithLoweredParameter { (tsWord0, tsWord1, tsWord2) in
283283
let selfValue = self.bridgeJSLowerParameter()
284-
let ret = bjs_TS2Swift_convert(selfValue, tsBytes, tsLength)
284+
let ret = bjs_TS2Swift_convert(selfValue, tsWord0, tsWord1, tsWord2)
285285
return ret
286286
}
287287
let ret = ret0

‎Plugins/BridgeJS/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ The ABI will not be stable, and not meant to be interposed by other tools.
139139
Parameter passing follows Wasm calling conventions, with custom handling for complex types:
140140

141141
- **Primitives**: Passed directly as Wasm arguments (`i32`, `i64`, `f32`, `f64`)
142-
- **Strings**: UTF-8 bytes stored in `swift.memory`, ID + length passed as Wasm arguments
142+
- **Strings (JS → Swift)**: UTF-8 bytes stored in `swift.memory`, ID + length passed as Wasm arguments
143+
- **Strings (Swift → JS)**: the wasm32 12-byte `String` layout passed as three `i32` words. JS fast-decodes small strings, caches large immortals by a 53-bit `ptr + len` pack when `len < 2**21`, and otherwise reads UTF-8 at `word1 + 20` (`nativeBias`). This layout is a Swift stdlib implementation detail and can change with the toolchain.
143144
- **Swift Classes**: Raw Swift heap pointer passed as `i32`
144145
- **JSObjects**: Object stored in `swift.memory.heap`, object ID passed as `i32`
145146
- **Structs/Arrays**: Fields/elements pushed to type-specific stacks, Swift pops in reverse order
@@ -150,7 +151,7 @@ Parameter passing follows Wasm calling conventions, with custom handling for com
150151
Return values use direct Wasm returns for primitives, and imported intrinsic functions for complex types:
151152

152153
- **Primitives**: Returned directly via Wasm return value
153-
- **Strings**: Swift writes UTF-8 bytes to shared memory, JS decodes
154+
- **Strings**: Swift passes the 12-byte `String` layout; JS decodes (small / immortal-cached / UTF-8)
154155
- **Swift Classes**: Pointer returned directly, JS wraps in `SwiftHeapObject` with `FinalizationRegistry`
155156
- **Structs/Arrays**: Swift pushes fields/elements to type-specific stacks, JS reconstructs
156157

‎Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -744,9 +744,7 @@ public class ExportSwift {
744744
/// public func _bjs_Greeter_greet(pointer: UnsafeMutableRawPointer) -> Void {
745745
/// let _self = Unmanaged<Greeter>.fromOpaque(pointer).takeUnretainedValue()
746746
/// var ret = _self.greet()
747-
/// return ret.withUTF8 { ptr in
748-
/// _swift_js_return_string(ptr.baseAddress, Int32(ptr.count))
749-
/// }
747+
/// return ret.bridgeJSLowerReturn()
750748
/// }
751749
/// @_expose(wasm, "bjs_Greeter_deinit")
752750
/// @_cdecl("bjs_Greeter_deinit")

‎Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ extension BridgeType {
927927
static let float = LoweringParameterInfo(loweredParameters: [("value", .f32)])
928928
static let double = LoweringParameterInfo(loweredParameters: [("value", .f64)])
929929
static let string = LoweringParameterInfo(
930-
loweredParameters: [("bytes", .i32), ("length", .i32)],
930+
loweredParameters: [("word0", .i32), ("word1", .i32), ("word2", .i32)],
931931
useBorrowing: true
932932
)
933933
static let jsObject = LoweringParameterInfo(loweredParameters: [("value", .i32)])

0 commit comments

Comments
 (0)