diff --git a/Benchmarks/Sources/Generated/BridgeJS.swift b/Benchmarks/Sources/Generated/BridgeJS.swift index 86f2516e6..fc46623f2 100644 --- a/Benchmarks/Sources/Generated/BridgeJS.swift +++ b/Benchmarks/Sources/Generated/BridgeJS.swift @@ -2429,20 +2429,20 @@ func _$benchmarkHelperNoopWithNumber(_ n: Double) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "Benchmarks", name: "bjs_benchmarkRunner") -fileprivate func bjs_benchmarkRunner_extern(_ nameBytes: Int32, _ nameLength: Int32, _ body: Int32) -> Void +fileprivate func bjs_benchmarkRunner_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ body: Int32) -> Void #else -fileprivate func bjs_benchmarkRunner_extern(_ nameBytes: Int32, _ nameLength: Int32, _ body: Int32) -> Void { +fileprivate func bjs_benchmarkRunner_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ body: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_benchmarkRunner(_ nameBytes: Int32, _ nameLength: Int32, _ body: Int32) -> Void { - return bjs_benchmarkRunner_extern(nameBytes, nameLength, body) +@inline(never) fileprivate func bjs_benchmarkRunner(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ body: Int32) -> Void { + return bjs_benchmarkRunner_extern(nameWord0, nameWord1, nameWord2, body) } func _$benchmarkRunner(_ name: String, _ body: JSObject) throws(JSException) -> Void { - name.bridgeJSWithLoweredParameter { (nameBytes, nameLength) in + name.bridgeJSWithLoweredParameter { (nameWord0, nameWord1, nameWord2) in let bodyValue = body.bridgeJSLowerParameter() - bjs_benchmarkRunner(nameBytes, nameLength, bodyValue) + bjs_benchmarkRunner(nameWord0, nameWord1, nameWord2, bodyValue) } if let error = _swift_js_take_exception() { throw error diff --git a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift index eed55bdea..ee8b1f436 100644 --- a/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift +++ b/Examples/PlayBridgeJS/Sources/PlayBridgeJS/Generated/BridgeJS.swift @@ -268,20 +268,20 @@ func _$createTS2Swift() throws(JSException) -> TS2Swift { #if arch(wasm32) @_extern(wasm, module: "PlayBridgeJS", name: "bjs_TS2Swift_convert") -fileprivate func bjs_TS2Swift_convert_extern(_ self: Int32, _ tsBytes: Int32, _ tsLength: Int32) -> Int32 +fileprivate func bjs_TS2Swift_convert_extern(_ self: Int32, _ tsWord0: Int32, _ tsWord1: Int32, _ tsWord2: Int32) -> Int32 #else -fileprivate func bjs_TS2Swift_convert_extern(_ self: Int32, _ tsBytes: Int32, _ tsLength: Int32) -> Int32 { +fileprivate func bjs_TS2Swift_convert_extern(_ self: Int32, _ tsWord0: Int32, _ tsWord1: Int32, _ tsWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_TS2Swift_convert(_ self: Int32, _ tsBytes: Int32, _ tsLength: Int32) -> Int32 { - return bjs_TS2Swift_convert_extern(self, tsBytes, tsLength) +@inline(never) fileprivate func bjs_TS2Swift_convert(_ self: Int32, _ tsWord0: Int32, _ tsWord1: Int32, _ tsWord2: Int32) -> Int32 { + return bjs_TS2Swift_convert_extern(self, tsWord0, tsWord1, tsWord2) } func _$TS2Swift_convert(_ self: JSObject, _ ts: String) throws(JSException) -> String { - let ret0 = ts.bridgeJSWithLoweredParameter { (tsBytes, tsLength) in + let ret0 = ts.bridgeJSWithLoweredParameter { (tsWord0, tsWord1, tsWord2) in let selfValue = self.bridgeJSLowerParameter() - let ret = bjs_TS2Swift_convert(selfValue, tsBytes, tsLength) + let ret = bjs_TS2Swift_convert(selfValue, tsWord0, tsWord1, tsWord2) return ret } let ret = ret0 diff --git a/Plugins/BridgeJS/README.md b/Plugins/BridgeJS/README.md index 0905695c5..4d93dc875 100644 --- a/Plugins/BridgeJS/README.md +++ b/Plugins/BridgeJS/README.md @@ -139,7 +139,8 @@ The ABI will not be stable, and not meant to be interposed by other tools. Parameter passing follows Wasm calling conventions, with custom handling for complex types: - **Primitives**: Passed directly as Wasm arguments (`i32`, `i64`, `f32`, `f64`) -- **Strings**: UTF-8 bytes stored in `swift.memory`, ID + length passed as Wasm arguments +- **Strings (JS → Swift)**: UTF-8 bytes stored in `swift.memory`, ID + length passed as Wasm arguments +- **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. - **Swift Classes**: Raw Swift heap pointer passed as `i32` - **JSObjects**: Object stored in `swift.memory.heap`, object ID passed as `i32` - **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 Return values use direct Wasm returns for primitives, and imported intrinsic functions for complex types: - **Primitives**: Returned directly via Wasm return value -- **Strings**: Swift writes UTF-8 bytes to shared memory, JS decodes +- **Strings**: Swift passes the 12-byte `String` layout; JS decodes (small / immortal-cached / UTF-8) - **Swift Classes**: Pointer returned directly, JS wraps in `SwiftHeapObject` with `FinalizationRegistry` - **Structs/Arrays**: Swift pushes fields/elements to type-specific stacks, JS reconstructs diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift index 6b5d3ef76..df8c589ea 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift @@ -744,9 +744,7 @@ public class ExportSwift { /// public func _bjs_Greeter_greet(pointer: UnsafeMutableRawPointer) -> Void { /// let _self = Unmanaged.fromOpaque(pointer).takeUnretainedValue() /// var ret = _self.greet() - /// return ret.withUTF8 { ptr in - /// _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) - /// } + /// return ret.bridgeJSLowerReturn() /// } /// @_expose(wasm, "bjs_Greeter_deinit") /// @_cdecl("bjs_Greeter_deinit") diff --git a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift index 378fe2012..841c0409c 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift @@ -927,7 +927,7 @@ extension BridgeType { static let float = LoweringParameterInfo(loweredParameters: [("value", .f32)]) static let double = LoweringParameterInfo(loweredParameters: [("value", .f64)]) static let string = LoweringParameterInfo( - loweredParameters: [("bytes", .i32), ("length", .i32)], + loweredParameters: [("word0", .i32), ("word1", .i32), ("word2", .i32)], useBorrowing: true ) static let jsObject = LoweringParameterInfo(loweredParameters: [("value", .i32)]) diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift index 40b1c13c3..fa664ca6b 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift @@ -313,12 +313,199 @@ public struct BridgeJSLink { return data } + /// Emits `decodeString`, including small-string helpers used only by it. + /// + /// The wasm32 12-byte Swift `String` layout is a standard-library + /// implementation detail, not a stable ABI. Small strings (`word2 & 0x2000`) + /// store up to 10 UTF-8 bytes inline. Large strings store the UTF-8 count in + /// `word0` and a variant/object pointer in `word1`; UTF-8 starts at + /// `word1 + nativeBias` (20 on 32-bit). Immortal large strings + /// (`word2 & 0x8000`) are cached by a 53-bit `ptr + len * 2**32` key when + /// `len < 2**21`; longer immortals skip the cache. + private func emitDecodeString(_ printer: CodeFragmentPrinter) { + printer.write( + multilineString: """ + \(JSGlueVariableScope.reservedDecodeString) = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return \(JSGlueVariableScope.reservedDecodeUTF8)(ptr, len); + const key = ptr + len * 0x100000000; + const cached = \(JSGlueVariableScope.reservedImmortalStrings).get(key); + if (cached !== undefined) return cached; + const value = \(JSGlueVariableScope.reservedDecodeUTF8)(ptr, len); + \(JSGlueVariableScope.reservedImmortalStrings).set(key, value); + return value; + }; + })(); + """ + ) + } + private func generateVariableDeclarations() -> [String] { var declarations: [String] = [ "let \(JSGlueVariableScope.reservedInstance);", "let \(JSGlueVariableScope.reservedMemory);", "let \(JSGlueVariableScope.reservedSetException);", "let \(JSGlueVariableScope.reservedDecodeString);", + "let \(JSGlueVariableScope.reservedDecodeUTF8);", + "const \(JSGlueVariableScope.reservedImmortalStrings) = new Map();", "const \(JSGlueVariableScope.reservedTextDecoder) = new TextDecoder(\"utf-8\");", "const \(JSGlueVariableScope.reservedTextEncoder) = new TextEncoder(\"utf-8\");", "let \(JSGlueVariableScope.reservedStorageToReturnString);", @@ -495,10 +682,10 @@ public struct BridgeJSLink { "const imports = options.getImports(importsContext);" ]) } - printer.write("bjs[\"swift_js_return_string\"] = function(ptr, len) {") + printer.write("bjs[\"swift_js_return_string\"] = function(word0, word1, word2) {") printer.indent { printer.write( - "\(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedDecodeString)(ptr, len);" + "\(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedDecodeString)(word0, word1, word2);" ) } printer.write("}") @@ -516,10 +703,10 @@ public struct BridgeJSLink { printer.write("bytes.set(source);") } printer.write("}") - printer.write("bjs[\"swift_js_make_js_string\"] = function(ptr, len) {") + printer.write("bjs[\"swift_js_make_js_string\"] = function(word0, word1, word2) {") printer.indent { printer.write( - "return \(JSGlueVariableScope.reservedSwift).\(JSGlueVariableScope.reservedMemory).retain(\(JSGlueVariableScope.reservedDecodeString)(ptr, len));" + "return \(JSGlueVariableScope.reservedSwift).\(JSGlueVariableScope.reservedMemory).retain(\(JSGlueVariableScope.reservedDecodeString)(word0, word1, word2));" ) } printer.write("}") @@ -566,9 +753,11 @@ public struct BridgeJSLink { printer.write("\(JSGlueVariableScope.reservedF64Stack).push(v);") } printer.write("}") - printer.write("bjs[\"swift_js_push_string\"] = function(ptr, len) {") + printer.write("bjs[\"swift_js_push_string\"] = function(word0, word1, word2) {") printer.indent { - printer.write("const value = \(JSGlueVariableScope.reservedDecodeString)(ptr, len);") + printer.write( + "const value = \(JSGlueVariableScope.reservedDecodeString)(word0, word1, word2);" + ) printer.write("\(JSGlueVariableScope.reservedStringStack).push(value);") } printer.write("}") @@ -732,7 +921,7 @@ public struct BridgeJSLink { printer.write("}") } printer.write("}") - printer.write("bjs[\"swift_js_return_optional_string\"] = function(isSome, ptr, len) {") + printer.write("bjs[\"swift_js_return_optional_string\"] = function(isSome, word0, word1, word2) {") printer.indent { printer.write("if (isSome === 0) {") printer.indent { @@ -741,7 +930,7 @@ public struct BridgeJSLink { printer.write("} else {") printer.indent { printer.write(lines: [ - "\(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedDecodeString)(ptr, len);" + "\(JSGlueVariableScope.reservedStorageToReturnString) = \(JSGlueVariableScope.reservedDecodeString)(word0, word1, word2);" ]) } printer.write("}") @@ -879,7 +1068,7 @@ public struct BridgeJSLink { helperPrinter.write("let length = 0;") helperPrinter.write("while (bytes[length] !== 0) { length += 1; }") helperPrinter.write( - "const fileID = \(JSGlueVariableScope.reservedDecodeString)(state.file, length);" + "const fileID = \(JSGlueVariableScope.reservedDecodeUTF8)(state.file, length);" ) helperPrinter.write( "throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`);" @@ -1289,19 +1478,21 @@ public struct BridgeJSLink { printer.write("setInstance: (i) => {") printer.indent { printer.write(lines: [ + "if (\(JSGlueVariableScope.reservedMemory) !== i.exports.memory) \(JSGlueVariableScope.reservedImmortalStrings).clear();", "\(JSGlueVariableScope.reservedInstance) = i;", "\(JSGlueVariableScope.reservedMemory) = \(JSGlueVariableScope.reservedInstance).exports.memory;", ]) printer.nextLine() if sharedMemory { printer.write( - "\(JSGlueVariableScope.reservedDecodeString) = (ptr, len) => { const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr >>> 0, len >>> 0).slice(); return \(JSGlueVariableScope.reservedTextDecoder).decode(bytes); }" + "\(JSGlueVariableScope.reservedDecodeUTF8) = (ptr, len) => { const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr >>> 0, len >>> 0).slice(); return \(JSGlueVariableScope.reservedTextDecoder).decode(bytes); }" ) } else { printer.write( - "\(JSGlueVariableScope.reservedDecodeString) = (ptr, len) => { const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr >>> 0, len >>> 0); return \(JSGlueVariableScope.reservedTextDecoder).decode(bytes); }" + "\(JSGlueVariableScope.reservedDecodeUTF8) = (ptr, len) => { const bytes = new Uint8Array(\(JSGlueVariableScope.reservedMemory).buffer, ptr >>> 0, len >>> 0); return \(JSGlueVariableScope.reservedTextDecoder).decode(bytes); }" ) } + emitDecodeString(printer) printer.nextLine() // Error handling printer.write("\(JSGlueVariableScope.reservedSetException) = (error) => {") diff --git a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift index 747c9f532..2da07f2be 100644 --- a/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift +++ b/Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift @@ -14,6 +14,8 @@ final class JSGlueVariableScope { static let reservedMemory = "memory" static let reservedSetException = "setException" static let reservedDecodeString = "decodeString" + static let reservedDecodeUTF8 = "decodeUTF8" + static let reservedImmortalStrings = "immortalStrings" static let reservedStorageToReturnString = "tmpRetString" static let reservedStorageToReturnBytes = "tmpRetBytes" static let reservedStorageToReturnException = "tmpRetException" @@ -49,6 +51,8 @@ final class JSGlueVariableScope { reservedMemory, reservedSetException, reservedDecodeString, + reservedDecodeUTF8, + reservedImmortalStrings, reservedStorageToReturnString, reservedStorageToReturnBytes, reservedStorageToReturnException, @@ -678,14 +682,15 @@ struct IntrinsicJSFragment: Sendable { } ) static let stringLiftParameter = IntrinsicJSFragment( - parameters: ["bytes", "count"], + parameters: ["word0", "word1", "word2"], printCode: { arguments, context in let (scope, printer) = (context.scope, context.printer) - let bytesExpr = arguments[0] - let countExpr = arguments[1] + let word0Expr = arguments[0] + let word1Expr = arguments[1] + let word2Expr = arguments[2] let stringLabel = scope.variable("string") printer.write( - "const \(stringLabel) = \(JSGlueVariableScope.reservedDecodeString)(\(bytesExpr), \(countExpr));" + "const \(stringLabel) = \(JSGlueVariableScope.reservedDecodeString)(\(word0Expr), \(word1Expr), \(word2Expr));" ) return [stringLabel] } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/StringDecoderTests.mjs b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/StringDecoderTests.mjs new file mode 100644 index 000000000..c83a0d8db --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/StringDecoderTests.mjs @@ -0,0 +1,90 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; + +// Execute generated glue rather than a duplicate of the decoder implementation. +const source = await readFile(process.argv[2] ?? new URL('../__Snapshots__/BridgeJSLinkTests/StringReturn.js', import.meta.url), 'utf8'); +const shared = process.argv[3] === 'shared'; +const { createInstantiator } = await import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`); +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); +const swift = { memory: { heap: [], retain: (value) => value } }; +const bridge = await createInstantiator({ getImports: () => ({}) }, swift); +const imports = {}; +bridge.addImports(imports, {}); +let words; +function instance(memory) { + return { exports: { memory, bjs_checkString: () => imports.bjs.swift_js_return_string(...words) } }; +} +let memory = new WebAssembly.Memory({ initial: 40, maximum: 41, shared }); +let wasm = instance(memory); +bridge.setInstance(wasm); +let exports = bridge.createExports(wasm); +function small(value) { + const bytes = encoder.encode(value); + assert.ok(bytes.length <= 10); + const storage = new Uint8Array(12); + storage.set(bytes.subarray(0, 8)); + storage[8] = bytes[8] ?? 0; + storage[10] = bytes[9] ?? 0; + storage[9] = (bytes.every((b) => b < 128) ? 0xe0 : 0xa0) | bytes.length; + const view = new DataView(storage.buffer); + return [0, 4, 8].map((offset) => view.getInt32(offset, true)); +} +function large(value, immortal = false, ptr = 64) { + const bytes = encoder.encode(value); + new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes); + return [bytes.length, ptr - 20, immortal ? 0x8000 : 0]; +} +const values = ['', 'div', 'abcdefgh', 'abcdefghi', 'abcdefghij', 'abcdefé', 'abcdefgé', + 'abcdefghé', 'abcdefghié', 'abc€', 'abcdefg€', 'abc😄', 'abcdef😄', 'abcdefg😄', + 'a\0b', '\ufeff', '\ufeffx', 'x\ufeff', '\ufeff\ufeffx', '\ufeffabcdefghijk', + 'abc\ufeffdefghijk', '\ufeff\ufeffabcdefghijk']; +for (const value of values) { + const expected = decoder.decode(encoder.encode(value)); + if (encoder.encode(value).length <= 10) { + words = small(value); + assert.equal(exports.checkString(), expected); + } + words = large(value); + assert.equal(exports.checkString(), expected); +} +// Alternate full and short inputs: bytes outside count must never leak into the result. +for (const value of ['abcdefghé', 'é', 'abcdef😄', '\ufeff', 'abcdefghij', '', 'x\0', '€', '\ufeff\ufeff']) { + words = small(value); + assert.equal(exports.checkString(), decoder.decode(encoder.encode(value))); +} +// Every ASCII byte, including NUL, at every small-string length. +for (let byte = 0; byte < 128; byte++) { + for (let count = 0; count <= 10; count++) { + const value = String.fromCharCode(byte).repeat(count); + words = small(value); + assert.equal(exports.checkString(), value); + } +} +words = large('first literal', true); +assert.equal(exports.checkString(), 'first literal'); +// A new instance using the same memory must preserve valid cache entries. +wasm = instance(memory); +bridge.setInstance(wasm); +exports = bridge.createExports(wasm); +assert.equal(exports.checkString(), 'first literal'); +// A different memory may reuse the same pointer/length for different contents. +memory = new WebAssembly.Memory({ initial: 40, maximum: 41, shared }); +wasm = instance(memory); +bridge.setInstance(wasm); +exports = bridge.createExports(wasm); +words = large('other literal', true); +assert.equal(exports.checkString(), 'other literal'); +// Same pointer with a different length must have a distinct key. +words = large('other', true); +assert.equal(exports.checkString(), 'other'); +// Values at the numeric-key cutoff bypass the cache. +const huge = 'x'.repeat(2 ** 21); +words = large(huge, true); +assert.equal(exports.checkString(), huge); +new Uint8Array(memory.buffer)[64] = 121; +assert.equal(exports.checkString(), 'y' + huge.slice(1)); +memory.grow(1); +words = large('after memory growth'); +assert.equal(exports.checkString(), 'after memory growth'); +console.log('String decoder regressions passed'); diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/StringDecoderTests.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/StringDecoderTests.swift new file mode 100644 index 000000000..96449b4de --- /dev/null +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/StringDecoderTests.swift @@ -0,0 +1,44 @@ +import Foundation +import SwiftParser +import Testing +@testable import BridgeJSCore +@testable import BridgeJSLink +@testable import TS2Swift + +@Suite struct StringDecoderTests { + @Test(arguments: [false, true]) + func generatedStringDecoder(sharedMemory: Bool) throws { + let directory = URL(fileURLWithPath: #filePath).deletingLastPathComponent() + let fixture = directory.appendingPathComponent("Inputs/MacroSwift/StringReturn.swift") + let collector = SwiftToSkeleton( + progress: .silent, + moduleName: "TestModule", + exposeToGlobal: false, + externalModuleIndex: .empty + ) + collector.addSourceFile( + Parser.parse(source: try String(contentsOf: fixture, encoding: .utf8)), + inputFilePath: fixture.path + ) + var linker = BridgeJSLink() + try linker.addSkeletonFile(data: JSONEncoder().encode(collector.finalize())) + let output = try linker.link(sharedMemory: sharedMemory) + guard let node = which("node") else { + Issue.record("Node.js is required for string decoder regression tests") + return + } + try withTemporaryDirectory { temporaryDirectory, _ in + let glue = temporaryDirectory.appendingPathComponent("bridge.js") + try output.outputJs.write(to: glue, atomically: true, encoding: .utf8) + let process = Process() + process.executableURL = node + process.arguments = [ + directory.appendingPathComponent("Inputs/StringDecoderTests.mjs").path, + glue.path, sharedMemory ? "shared" : "unshared", + ] + try process.run() + process.waitUntilExit() + #expect(process.terminationStatus == 0) + } + } +} diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift index 2529f22d3..7f8b3750d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Alias.swift @@ -297,19 +297,19 @@ extension UserId: _BridgedSwiftAlias, _BridgedSwiftStackType {} #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_acceptTagged") -fileprivate func bjs_acceptTagged_extern(_ taggedBytes: Int32, _ taggedLength: Int32) -> Void +fileprivate func bjs_acceptTagged_extern(_ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Void #else -fileprivate func bjs_acceptTagged_extern(_ taggedBytes: Int32, _ taggedLength: Int32) -> Void { +fileprivate func bjs_acceptTagged_extern(_ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_acceptTagged(_ taggedBytes: Int32, _ taggedLength: Int32) -> Void { - return bjs_acceptTagged_extern(taggedBytes, taggedLength) +@inline(never) fileprivate func bjs_acceptTagged(_ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Void { + return bjs_acceptTagged_extern(taggedWord0, taggedWord1, taggedWord2) } func _$acceptTagged(_ tagged: Tagged) throws(JSException) -> Void { - tagged.bridgeJSWithLoweredParameter { (taggedBytes, taggedLength) in - bjs_acceptTagged(taggedBytes, taggedLength) + tagged.bridgeJSWithLoweredParameter { (taggedWord0, taggedWord1, taggedWord2) in + bjs_acceptTagged(taggedWord0, taggedWord1, taggedWord2) } if let error = _swift_js_take_exception() { throw error @@ -318,19 +318,19 @@ func _$acceptTagged(_ tagged: Tagged) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_acceptOptionalTagged") -fileprivate func bjs_acceptOptionalTagged_extern(_ taggedIsSome: Int32, _ taggedBytes: Int32, _ taggedLength: Int32) -> Void +fileprivate func bjs_acceptOptionalTagged_extern(_ taggedIsSome: Int32, _ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Void #else -fileprivate func bjs_acceptOptionalTagged_extern(_ taggedIsSome: Int32, _ taggedBytes: Int32, _ taggedLength: Int32) -> Void { +fileprivate func bjs_acceptOptionalTagged_extern(_ taggedIsSome: Int32, _ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_acceptOptionalTagged(_ taggedIsSome: Int32, _ taggedBytes: Int32, _ taggedLength: Int32) -> Void { - return bjs_acceptOptionalTagged_extern(taggedIsSome, taggedBytes, taggedLength) +@inline(never) fileprivate func bjs_acceptOptionalTagged(_ taggedIsSome: Int32, _ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Void { + return bjs_acceptOptionalTagged_extern(taggedIsSome, taggedWord0, taggedWord1, taggedWord2) } func _$acceptOptionalTagged(_ tagged: Optional) throws(JSException) -> Void { - tagged.bridgeJSWithLoweredParameter { (taggedIsSome, taggedBytes, taggedLength) in - bjs_acceptOptionalTagged(taggedIsSome, taggedBytes, taggedLength) + tagged.bridgeJSWithLoweredParameter { (taggedIsSome, taggedWord0, taggedWord1, taggedWord2) in + bjs_acceptOptionalTagged(taggedIsSome, taggedWord0, taggedWord1, taggedWord2) } if let error = _swift_js_take_exception() { throw error @@ -339,19 +339,19 @@ func _$acceptOptionalTagged(_ tagged: Optional) throws(JSException) -> V #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_roundtripTagged") -fileprivate func bjs_roundtripTagged_extern(_ taggedBytes: Int32, _ taggedLength: Int32) -> Int32 +fileprivate func bjs_roundtripTagged_extern(_ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Int32 #else -fileprivate func bjs_roundtripTagged_extern(_ taggedBytes: Int32, _ taggedLength: Int32) -> Int32 { +fileprivate func bjs_roundtripTagged_extern(_ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_roundtripTagged(_ taggedBytes: Int32, _ taggedLength: Int32) -> Int32 { - return bjs_roundtripTagged_extern(taggedBytes, taggedLength) +@inline(never) fileprivate func bjs_roundtripTagged(_ taggedWord0: Int32, _ taggedWord1: Int32, _ taggedWord2: Int32) -> Int32 { + return bjs_roundtripTagged_extern(taggedWord0, taggedWord1, taggedWord2) } func _$roundtripTagged(_ tagged: Tagged) throws(JSException) -> Tagged { - let ret0 = tagged.bridgeJSWithLoweredParameter { (taggedBytes, taggedLength) in - let ret = bjs_roundtripTagged(taggedBytes, taggedLength) + let ret0 = tagged.bridgeJSWithLoweredParameter { (taggedWord0, taggedWord1, taggedWord2) in + let ret = bjs_roundtripTagged(taggedWord0, taggedWord1, taggedWord2) return ret } let ret = ret0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift index ae03fc475..2d0143ea9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Async.swift @@ -423,20 +423,20 @@ func _$Promise_resolve_Si(_ promise: JSObject, _ value: Int) throws(JSException) #if arch(wasm32) @_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_SS") -fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func promise_resolve_TestModule_SS(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return promise_resolve_TestModule_SS_extern(promise, valueBytes, valueLength) +@inline(never) fileprivate func promise_resolve_TestModule_SS(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_TestModule_SS_extern(promise, valueWord0, valueWord1, valueWord2) } func _$Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException) -> Void { - value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in let promiseValue = promise.bridgeJSLowerParameter() - promise_resolve_TestModule_SS(promiseValue, valueBytes, valueLength) + promise_resolve_TestModule_SS(promiseValue, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error } } @@ -571,20 +571,20 @@ func _$Promise_resolve_14AsyncDirectionO(_ promise: JSObject, _ value: AsyncDire #if arch(wasm32) @_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_10AsyncThemeO") -fileprivate func promise_resolve_TestModule_10AsyncThemeO_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func promise_resolve_TestModule_10AsyncThemeO_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func promise_resolve_TestModule_10AsyncThemeO_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func promise_resolve_TestModule_10AsyncThemeO_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func promise_resolve_TestModule_10AsyncThemeO(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return promise_resolve_TestModule_10AsyncThemeO_extern(promise, valueBytes, valueLength) +@inline(never) fileprivate func promise_resolve_TestModule_10AsyncThemeO(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_TestModule_10AsyncThemeO_extern(promise, valueWord0, valueWord1, valueWord2) } func _$Promise_resolve_10AsyncThemeO(_ promise: JSObject, _ value: AsyncTheme) throws(JSException) -> Void { - value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in let promiseValue = promise.bridgeJSLowerParameter() - promise_resolve_TestModule_10AsyncThemeO(promiseValue, valueBytes, valueLength) + promise_resolve_TestModule_10AsyncThemeO(promiseValue, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error } } @@ -614,20 +614,20 @@ func _$Promise_resolve_Sq14AsyncDirectionO(_ promise: JSObject, _ value: Optiona #if arch(wasm32) @_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_Sq10AsyncThemeO") -fileprivate func promise_resolve_TestModule_Sq10AsyncThemeO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func promise_resolve_TestModule_Sq10AsyncThemeO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func promise_resolve_TestModule_Sq10AsyncThemeO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func promise_resolve_TestModule_Sq10AsyncThemeO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func promise_resolve_TestModule_Sq10AsyncThemeO(_ promise: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return promise_resolve_TestModule_Sq10AsyncThemeO_extern(promise, valueIsSome, valueBytes, valueLength) +@inline(never) fileprivate func promise_resolve_TestModule_Sq10AsyncThemeO(_ promise: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_TestModule_Sq10AsyncThemeO_extern(promise, valueIsSome, valueWord0, valueWord1, valueWord2) } func _$Promise_resolve_Sq10AsyncThemeO(_ promise: JSObject, _ value: Optional) throws(JSException) -> Void { - value.bridgeJSWithLoweredParameter { (valueIsSome, valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueIsSome, valueWord0, valueWord1, valueWord2) in let promiseValue = promise.bridgeJSLowerParameter() - promise_resolve_TestModule_Sq10AsyncThemeO(promiseValue, valueIsSome, valueBytes, valueLength) + promise_resolve_TestModule_Sq10AsyncThemeO(promiseValue, valueIsSome, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AsyncImport.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AsyncImport.swift index 509f79578..42cb4f52b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AsyncImport.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/AsyncImport.swift @@ -124,14 +124,14 @@ public func _invoke_swift_closure_TestModule_10TestModules8JSObjectC_y(_ boxPtr: #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModulesSS_y") -fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModulesSS_y_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModulesSS_y_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -151,9 +151,9 @@ private enum _BJS_Closure_10TestModulesSS_y { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModulesSS_y(callbackValue, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModulesSS_y(callbackValue, param0Word0, param0Word1, param0Word2) } #else fatalError("Only available on WebAssembly") @@ -479,14 +479,14 @@ func _$asyncRoundTripInt(_ v: Int) async throws(JSException) -> Int { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_asyncRoundTripString") -fileprivate func bjs_asyncRoundTripString_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void +fileprivate func bjs_asyncRoundTripString_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void #else -fileprivate func bjs_asyncRoundTripString_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void { +fileprivate func bjs_asyncRoundTripString_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_asyncRoundTripString(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void { - return bjs_asyncRoundTripString_extern(resolveRef, rejectRef, vBytes, vLength) +@inline(never) fileprivate func bjs_asyncRoundTripString(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void { + return bjs_asyncRoundTripString_extern(resolveRef, rejectRef, vWord0, vWord1, vWord2) } func _$asyncRoundTripString(_ v: String) async throws(JSException) -> String { @@ -495,8 +495,8 @@ func _$asyncRoundTripString(_ v: String) async throws(JSException) -> String { }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - v.bridgeJSWithLoweredParameter { (vBytes, vLength) in - bjs_asyncRoundTripString(resolveRef, rejectRef, vBytes, vLength) + v.bridgeJSWithLoweredParameter { (vWord0, vWord1, vWord2) in + bjs_asyncRoundTripString(resolveRef, rejectRef, vWord0, vWord1, vWord2) } } return resolved diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumRawType.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumRawType.swift index 2cee26721..da2e3916f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumRawType.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/EnumRawType.swift @@ -528,19 +528,19 @@ extension Ratio: BridgedSwiftGenericBridgeable { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_takesFeatureFlag") -fileprivate func bjs_takesFeatureFlag_extern(_ flagBytes: Int32, _ flagLength: Int32) -> Void +fileprivate func bjs_takesFeatureFlag_extern(_ flagWord0: Int32, _ flagWord1: Int32, _ flagWord2: Int32) -> Void #else -fileprivate func bjs_takesFeatureFlag_extern(_ flagBytes: Int32, _ flagLength: Int32) -> Void { +fileprivate func bjs_takesFeatureFlag_extern(_ flagWord0: Int32, _ flagWord1: Int32, _ flagWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_takesFeatureFlag(_ flagBytes: Int32, _ flagLength: Int32) -> Void { - return bjs_takesFeatureFlag_extern(flagBytes, flagLength) +@inline(never) fileprivate func bjs_takesFeatureFlag(_ flagWord0: Int32, _ flagWord1: Int32, _ flagWord2: Int32) -> Void { + return bjs_takesFeatureFlag_extern(flagWord0, flagWord1, flagWord2) } func _$takesFeatureFlag(_ flag: FeatureFlag) throws(JSException) -> Void { - flag.bridgeJSWithLoweredParameter { (flagBytes, flagLength) in - bjs_takesFeatureFlag(flagBytes, flagLength) + flag.bridgeJSWithLoweredParameter { (flagWord0, flagWord1, flagWord2) in + bjs_takesFeatureFlag(flagWord0, flagWord1, flagWord2) } if let error = _swift_js_take_exception() { throw error diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.swift index ecb1512cf..8a9a11197 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GenericImports.swift @@ -321,19 +321,19 @@ func _$genericRoundTrip(_ value: T) throws(JSE #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_genericParse") -fileprivate func bjs_genericParse_extern(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void +fileprivate func bjs_genericParse_extern(_ jsonWord0: Int32, _ jsonWord1: Int32, _ jsonWord2: Int32, _ _generic0TypeId: Int32) -> Void #else -fileprivate func bjs_genericParse_extern(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void { +fileprivate func bjs_genericParse_extern(_ jsonWord0: Int32, _ jsonWord1: Int32, _ jsonWord2: Int32, _ _generic0TypeId: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_genericParse(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void { - return bjs_genericParse_extern(jsonBytes, jsonLength, _generic0TypeId) +@inline(never) fileprivate func bjs_genericParse(_ jsonWord0: Int32, _ jsonWord1: Int32, _ jsonWord2: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_genericParse_extern(jsonWord0, jsonWord1, jsonWord2, _generic0TypeId) } func _$genericParse(_ json: String) throws(JSException) -> T { - json.bridgeJSWithLoweredParameter { (jsonBytes, jsonLength) in - bjs_genericParse(jsonBytes, jsonLength, T.bridgeJSTypeID) + json.bridgeJSWithLoweredParameter { (jsonWord0, jsonWord1, jsonWord2) in + bjs_genericParse(jsonWord0, jsonWord1, jsonWord2, T.bridgeJSTypeID) } if let error = _swift_js_take_exception() { throw error @@ -514,21 +514,21 @@ func _$constrainedComposition Int32 +fileprivate func bjs_GenericPairFactory_init_extern(_ tagWord0: Int32, _ tagWord1: Int32, _ tagWord2: Int32, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Int32 #else -fileprivate func bjs_GenericPairFactory_init_extern(_ tagBytes: Int32, _ tagLength: Int32, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Int32 { +fileprivate func bjs_GenericPairFactory_init_extern(_ tagWord0: Int32, _ tagWord1: Int32, _ tagWord2: Int32, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_GenericPairFactory_init(_ tagBytes: Int32, _ tagLength: Int32, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Int32 { - return bjs_GenericPairFactory_init_extern(tagBytes, tagLength, _generic0TypeId, _generic1TypeId) +@inline(never) fileprivate func bjs_GenericPairFactory_init(_ tagWord0: Int32, _ tagWord1: Int32, _ tagWord2: Int32, _ _generic0TypeId: Int32, _ _generic1TypeId: Int32) -> Int32 { + return bjs_GenericPairFactory_init_extern(tagWord0, tagWord1, tagWord2, _generic0TypeId, _generic1TypeId) } func _$GenericPairFactory_init(_ tag: String, _ first: T, _ second: U) throws(JSException) -> JSObject { - let ret0 = tag.bridgeJSWithLoweredParameter { (tagBytes, tagLength) in + let ret0 = tag.bridgeJSWithLoweredParameter { (tagWord0, tagWord1, tagWord2) in second.bridgeJSStackPush() first.bridgeJSStackPush() - let ret = bjs_GenericPairFactory_init(tagBytes, tagLength, T.bridgeJSTypeID, U.bridgeJSTypeID) + let ret = bjs_GenericPairFactory_init(tagWord0, tagWord1, tagWord2, T.bridgeJSTypeID, U.bridgeJSTypeID) return ret } let ret = ret0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.swift index 68afce080..14d4b1907 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalGetter.swift @@ -20,20 +20,20 @@ func _$console_get() throws(JSException) -> JSConsole { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_JSConsole_log") -fileprivate func bjs_JSConsole_log_extern(_ self: Int32, _ messageBytes: Int32, _ messageLength: Int32) -> Void +fileprivate func bjs_JSConsole_log_extern(_ self: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void #else -fileprivate func bjs_JSConsole_log_extern(_ self: Int32, _ messageBytes: Int32, _ messageLength: Int32) -> Void { +fileprivate func bjs_JSConsole_log_extern(_ self: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_JSConsole_log(_ self: Int32, _ messageBytes: Int32, _ messageLength: Int32) -> Void { - return bjs_JSConsole_log_extern(self, messageBytes, messageLength) +@inline(never) fileprivate func bjs_JSConsole_log(_ self: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void { + return bjs_JSConsole_log_extern(self, messageWord0, messageWord1, messageWord2) } func _$JSConsole_log(_ self: JSObject, _ message: String) throws(JSException) -> Void { - message.bridgeJSWithLoweredParameter { (messageBytes, messageLength) in + message.bridgeJSWithLoweredParameter { (messageWord0, messageWord1, messageWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_JSConsole_log(selfValue, messageBytes, messageLength) + bjs_JSConsole_log(selfValue, messageWord0, messageWord1, messageWord2) } if let error = _swift_js_take_exception() { throw error diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.swift index 4ba7a26fe..896c4e36d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/GlobalThisImports.swift @@ -20,19 +20,19 @@ func _$console_get() throws(JSException) -> JSConsole { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_parseInt") -fileprivate func bjs_parseInt_extern(_ stringBytes: Int32, _ stringLength: Int32) -> Float64 +fileprivate func bjs_parseInt_extern(_ stringWord0: Int32, _ stringWord1: Int32, _ stringWord2: Int32) -> Float64 #else -fileprivate func bjs_parseInt_extern(_ stringBytes: Int32, _ stringLength: Int32) -> Float64 { +fileprivate func bjs_parseInt_extern(_ stringWord0: Int32, _ stringWord1: Int32, _ stringWord2: Int32) -> Float64 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_parseInt(_ stringBytes: Int32, _ stringLength: Int32) -> Float64 { - return bjs_parseInt_extern(stringBytes, stringLength) +@inline(never) fileprivate func bjs_parseInt(_ stringWord0: Int32, _ stringWord1: Int32, _ stringWord2: Int32) -> Float64 { + return bjs_parseInt_extern(stringWord0, stringWord1, stringWord2) } func _$parseInt(_ string: String) throws(JSException) -> Double { - let ret0 = string.bridgeJSWithLoweredParameter { (stringBytes, stringLength) in - let ret = bjs_parseInt(stringBytes, stringLength) + let ret0 = string.bridgeJSWithLoweredParameter { (stringWord0, stringWord1, stringWord2) in + let ret = bjs_parseInt(stringWord0, stringWord1, stringWord2) return ret } let ret = ret0 @@ -44,20 +44,20 @@ func _$parseInt(_ string: String) throws(JSException) -> Double { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_JSConsole_log") -fileprivate func bjs_JSConsole_log_extern(_ self: Int32, _ messageBytes: Int32, _ messageLength: Int32) -> Void +fileprivate func bjs_JSConsole_log_extern(_ self: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void #else -fileprivate func bjs_JSConsole_log_extern(_ self: Int32, _ messageBytes: Int32, _ messageLength: Int32) -> Void { +fileprivate func bjs_JSConsole_log_extern(_ self: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_JSConsole_log(_ self: Int32, _ messageBytes: Int32, _ messageLength: Int32) -> Void { - return bjs_JSConsole_log_extern(self, messageBytes, messageLength) +@inline(never) fileprivate func bjs_JSConsole_log(_ self: Int32, _ messageWord0: Int32, _ messageWord1: Int32, _ messageWord2: Int32) -> Void { + return bjs_JSConsole_log_extern(self, messageWord0, messageWord1, messageWord2) } func _$JSConsole_log(_ self: JSObject, _ message: String) throws(JSException) -> Void { - message.bridgeJSWithLoweredParameter { (messageBytes, messageLength) in + message.bridgeJSWithLoweredParameter { (messageWord0, messageWord1, messageWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_JSConsole_log(selfValue, messageBytes, messageLength) + bjs_JSConsole_log(selfValue, messageWord0, messageWord1, messageWord2) } if let error = _swift_js_take_exception() { throw error @@ -66,14 +66,14 @@ func _$JSConsole_log(_ self: JSObject, _ message: String) throws(JSException) -> #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WebSocket_init") -fileprivate func bjs_WebSocket_init_extern(_ urlBytes: Int32, _ urlLength: Int32) -> Int32 +fileprivate func bjs_WebSocket_init_extern(_ urlWord0: Int32, _ urlWord1: Int32, _ urlWord2: Int32) -> Int32 #else -fileprivate func bjs_WebSocket_init_extern(_ urlBytes: Int32, _ urlLength: Int32) -> Int32 { +fileprivate func bjs_WebSocket_init_extern(_ urlWord0: Int32, _ urlWord1: Int32, _ urlWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WebSocket_init(_ urlBytes: Int32, _ urlLength: Int32) -> Int32 { - return bjs_WebSocket_init_extern(urlBytes, urlLength) +@inline(never) fileprivate func bjs_WebSocket_init(_ urlWord0: Int32, _ urlWord1: Int32, _ urlWord2: Int32) -> Int32 { + return bjs_WebSocket_init_extern(urlWord0, urlWord1, urlWord2) } #if arch(wasm32) @@ -89,8 +89,8 @@ fileprivate func bjs_WebSocket_close_extern(_ self: Int32) -> Void { } func _$WebSocket_init(_ url: String) throws(JSException) -> JSObject { - let ret0 = url.bridgeJSWithLoweredParameter { (urlBytes, urlLength) in - let ret = bjs_WebSocket_init(urlBytes, urlLength) + let ret0 = url.bridgeJSWithLoweredParameter { (urlWord0, urlWord1, urlWord2) in + let ret = bjs_WebSocket_init(urlWord0, urlWord1, urlWord2) return ret } let ret = ret0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportArray.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportArray.swift index 12abbd1e6..a501dd2f5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportArray.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/ImportArray.swift @@ -63,21 +63,21 @@ func _$optionalArrayThenArray(_ a: Optional<[Int]>, _ b: [Int]) throws(JSExcepti #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_borrowedStringAroundStackParams") -fileprivate func bjs_borrowedStringAroundStackParams_extern(_ sBytes: Int32, _ sLength: Int32) -> Int32 +fileprivate func bjs_borrowedStringAroundStackParams_extern(_ sWord0: Int32, _ sWord1: Int32, _ sWord2: Int32) -> Int32 #else -fileprivate func bjs_borrowedStringAroundStackParams_extern(_ sBytes: Int32, _ sLength: Int32) -> Int32 { +fileprivate func bjs_borrowedStringAroundStackParams_extern(_ sWord0: Int32, _ sWord1: Int32, _ sWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_borrowedStringAroundStackParams(_ sBytes: Int32, _ sLength: Int32) -> Int32 { - return bjs_borrowedStringAroundStackParams_extern(sBytes, sLength) +@inline(never) fileprivate func bjs_borrowedStringAroundStackParams(_ sWord0: Int32, _ sWord1: Int32, _ sWord2: Int32) -> Int32 { + return bjs_borrowedStringAroundStackParams_extern(sWord0, sWord1, sWord2) } func _$borrowedStringAroundStackParams(_ s: String, _ a: Optional<[Int]>, _ b: [Int]) throws(JSException) -> Int { - let ret0 = s.bridgeJSWithLoweredParameter { (sBytes, sLength) in + let ret0 = s.bridgeJSWithLoweredParameter { (sWord0, sWord1, sWord2) in let _ = b.bridgeJSLowerParameter() let _ = a.bridgeJSLowerParameter() - let ret = bjs_borrowedStringAroundStackParams(sBytes, sLength) + let ret = bjs_borrowedStringAroundStackParams(sWord0, sWord1, sWord2) return ret } let ret = ret0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/InvalidPropertyNames.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/InvalidPropertyNames.swift index f82632737..3adaeb88f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/InvalidPropertyNames.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/InvalidPropertyNames.swift @@ -136,14 +136,14 @@ fileprivate func bjs_WeirdNaming_Any_get_extern(_ self: Int32) -> Int32 { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WeirdNaming_normalProperty_set") -fileprivate func bjs_WeirdNaming_normalProperty_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_WeirdNaming_normalProperty_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_WeirdNaming_normalProperty_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_WeirdNaming_normalProperty_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WeirdNaming_normalProperty_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_WeirdNaming_normalProperty_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_WeirdNaming_normalProperty_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_WeirdNaming_normalProperty_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -172,14 +172,14 @@ fileprivate func bjs_WeirdNaming__123invalidStart_set_extern(_ self: Int32, _ ne #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WeirdNaming_property_with_spaces_set") -fileprivate func bjs_WeirdNaming_property_with_spaces_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_WeirdNaming_property_with_spaces_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_WeirdNaming_property_with_spaces_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_WeirdNaming_property_with_spaces_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WeirdNaming_property_with_spaces_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_WeirdNaming_property_with_spaces_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_WeirdNaming_property_with_spaces_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_WeirdNaming_property_with_spaces_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -196,38 +196,38 @@ fileprivate func bjs_WeirdNaming__specialChar_set_extern(_ self: Int32, _ newVal #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WeirdNaming_constructor_set") -fileprivate func bjs_WeirdNaming_constructor_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_WeirdNaming_constructor_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_WeirdNaming_constructor_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_WeirdNaming_constructor_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WeirdNaming_constructor_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_WeirdNaming_constructor_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_WeirdNaming_constructor_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_WeirdNaming_constructor_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WeirdNaming_for_set") -fileprivate func bjs_WeirdNaming_for_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_WeirdNaming_for_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_WeirdNaming_for_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_WeirdNaming_for_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WeirdNaming_for_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_WeirdNaming_for_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_WeirdNaming_for_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_WeirdNaming_for_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WeirdNaming_any_set") -fileprivate func bjs_WeirdNaming_any_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_WeirdNaming_any_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_WeirdNaming_any_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_WeirdNaming_any_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WeirdNaming_any_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_WeirdNaming_any_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_WeirdNaming_any_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_WeirdNaming_any_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -327,9 +327,9 @@ func _$WeirdNaming_Any_get(_ self: JSObject) throws(JSException) -> String { } func _$WeirdNaming_normalProperty_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WeirdNaming_normalProperty_set(selfValue, newValueBytes, newValueLength) + bjs_WeirdNaming_normalProperty_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -355,9 +355,9 @@ func _$WeirdNaming__123invalidStart_set(_ self: JSObject, _ newValue: Bool) thro } func _$WeirdNaming_property_with_spaces_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WeirdNaming_property_with_spaces_set(selfValue, newValueBytes, newValueLength) + bjs_WeirdNaming_property_with_spaces_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -374,9 +374,9 @@ func _$WeirdNaming__specialChar_set(_ self: JSObject, _ newValue: Double) throws } func _$WeirdNaming_constructor_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WeirdNaming_constructor_set(selfValue, newValueBytes, newValueLength) + bjs_WeirdNaming_constructor_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -384,9 +384,9 @@ func _$WeirdNaming_constructor_set(_ self: JSObject, _ newValue: String) throws( } func _$WeirdNaming_for_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WeirdNaming_for_set(selfValue, newValueBytes, newValueLength) + bjs_WeirdNaming_for_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -394,9 +394,9 @@ func _$WeirdNaming_for_set(_ self: JSObject, _ newValue: String) throws(JSExcept } func _$WeirdNaming_any_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WeirdNaming_any_set(selfValue, newValueBytes, newValueLength) + bjs_WeirdNaming_any_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClass.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClass.swift index 37009f318..95e020b23 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClass.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSClass.swift @@ -20,14 +20,14 @@ func _$returnAnimatable() throws(JSException) -> Animatable { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Greeter_init") -fileprivate func bjs_Greeter_init_extern(_ nameBytes: Int32, _ nameLength: Int32) -> Int32 +fileprivate func bjs_Greeter_init_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Int32 #else -fileprivate func bjs_Greeter_init_extern(_ nameBytes: Int32, _ nameLength: Int32) -> Int32 { +fileprivate func bjs_Greeter_init_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_Greeter_init(_ nameBytes: Int32, _ nameLength: Int32) -> Int32 { - return bjs_Greeter_init_extern(nameBytes, nameLength) +@inline(never) fileprivate func bjs_Greeter_init(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Int32 { + return bjs_Greeter_init_extern(nameWord0, nameWord1, nameWord2) } #if arch(wasm32) @@ -56,14 +56,14 @@ fileprivate func bjs_Greeter_age_get_extern(_ self: Int32) -> Float64 { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Greeter_name_set") -fileprivate func bjs_Greeter_name_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_Greeter_name_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_Greeter_name_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_Greeter_name_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_Greeter_name_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_Greeter_name_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_Greeter_name_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_Greeter_name_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -80,19 +80,19 @@ fileprivate func bjs_Greeter_greet_extern(_ self: Int32) -> Int32 { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_Greeter_changeName") -fileprivate func bjs_Greeter_changeName_extern(_ self: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void +fileprivate func bjs_Greeter_changeName_extern(_ self: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void #else -fileprivate func bjs_Greeter_changeName_extern(_ self: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { +fileprivate func bjs_Greeter_changeName_extern(_ self: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_Greeter_changeName(_ self: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { - return bjs_Greeter_changeName_extern(self, nameBytes, nameLength) +@inline(never) fileprivate func bjs_Greeter_changeName(_ self: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void { + return bjs_Greeter_changeName_extern(self, nameWord0, nameWord1, nameWord2) } func _$Greeter_init(_ name: String) throws(JSException) -> JSObject { - let ret0 = name.bridgeJSWithLoweredParameter { (nameBytes, nameLength) in - let ret = bjs_Greeter_init(nameBytes, nameLength) + let ret0 = name.bridgeJSWithLoweredParameter { (nameWord0, nameWord1, nameWord2) in + let ret = bjs_Greeter_init(nameWord0, nameWord1, nameWord2) return ret } let ret = ret0 @@ -121,9 +121,9 @@ func _$Greeter_age_get(_ self: JSObject) throws(JSException) -> Double { } func _$Greeter_name_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_Greeter_name_set(selfValue, newValueBytes, newValueLength) + bjs_Greeter_name_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -140,9 +140,9 @@ func _$Greeter_greet(_ self: JSObject) throws(JSException) -> String { } func _$Greeter_changeName(_ self: JSObject, _ name: String) throws(JSException) -> Void { - name.bridgeJSWithLoweredParameter { (nameBytes, nameLength) in + name.bridgeJSWithLoweredParameter { (nameWord0, nameWord1, nameWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_Greeter_changeName(selfValue, nameBytes, nameLength) + bjs_Greeter_changeName(selfValue, nameWord0, nameWord1, nameWord2) } if let error = _swift_js_take_exception() { throw error diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportBareModule.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportBareModule.swift index 4baf8d0f0..69de3b2ff 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportBareModule.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportBareModule.swift @@ -40,19 +40,19 @@ func _$localDefaultExport_get() throws(JSException) -> JSObject { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_nodeBasename") -fileprivate func bjs_nodeBasename_extern(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 +fileprivate func bjs_nodeBasename_extern(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 #else -fileprivate func bjs_nodeBasename_extern(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 { +fileprivate func bjs_nodeBasename_extern(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_nodeBasename(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 { - return bjs_nodeBasename_extern(pathBytes, pathLength) +@inline(never) fileprivate func bjs_nodeBasename(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 { + return bjs_nodeBasename_extern(pathWord0, pathWord1, pathWord2) } func _$nodeBasename(_ path: String) throws(JSException) -> String { - let ret0 = path.bridgeJSWithLoweredParameter { (pathBytes, pathLength) in - let ret = bjs_nodeBasename(pathBytes, pathLength) + let ret0 = path.bridgeJSWithLoweredParameter { (pathWord0, pathWord1, pathWord2) in + let ret = bjs_nodeBasename(pathWord0, pathWord1, pathWord2) return ret } let ret = ret0 @@ -64,19 +64,19 @@ func _$nodeBasename(_ path: String) throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_nodeDirname") -fileprivate func bjs_nodeDirname_extern(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 +fileprivate func bjs_nodeDirname_extern(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 #else -fileprivate func bjs_nodeDirname_extern(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 { +fileprivate func bjs_nodeDirname_extern(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_nodeDirname(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 { - return bjs_nodeDirname_extern(pathBytes, pathLength) +@inline(never) fileprivate func bjs_nodeDirname(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 { + return bjs_nodeDirname_extern(pathWord0, pathWord1, pathWord2) } func _$nodeDirname(_ path: String) throws(JSException) -> String { - let ret0 = path.bridgeJSWithLoweredParameter { (pathBytes, pathLength) in - let ret = bjs_nodeDirname(pathBytes, pathLength) + let ret0 = path.bridgeJSWithLoweredParameter { (pathWord0, pathWord1, pathWord2) in + let ret = bjs_nodeDirname(pathWord0, pathWord1, pathWord2) return ret } let ret = ret0 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportBareModuleFallback.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportBareModuleFallback.swift index 7780b5eb0..459130153 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportBareModuleFallback.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/JSImportBareModuleFallback.swift @@ -40,20 +40,20 @@ func _$kebabCaseFunction() throws(JSException) -> Int { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_joinPaths") -fileprivate func bjs_joinPaths_extern(_ lhsBytes: Int32, _ lhsLength: Int32, _ rhsBytes: Int32, _ rhsLength: Int32) -> Int32 +fileprivate func bjs_joinPaths_extern(_ lhsWord0: Int32, _ lhsWord1: Int32, _ lhsWord2: Int32, _ rhsWord0: Int32, _ rhsWord1: Int32, _ rhsWord2: Int32) -> Int32 #else -fileprivate func bjs_joinPaths_extern(_ lhsBytes: Int32, _ lhsLength: Int32, _ rhsBytes: Int32, _ rhsLength: Int32) -> Int32 { +fileprivate func bjs_joinPaths_extern(_ lhsWord0: Int32, _ lhsWord1: Int32, _ lhsWord2: Int32, _ rhsWord0: Int32, _ rhsWord1: Int32, _ rhsWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_joinPaths(_ lhsBytes: Int32, _ lhsLength: Int32, _ rhsBytes: Int32, _ rhsLength: Int32) -> Int32 { - return bjs_joinPaths_extern(lhsBytes, lhsLength, rhsBytes, rhsLength) +@inline(never) fileprivate func bjs_joinPaths(_ lhsWord0: Int32, _ lhsWord1: Int32, _ lhsWord2: Int32, _ rhsWord0: Int32, _ rhsWord1: Int32, _ rhsWord2: Int32) -> Int32 { + return bjs_joinPaths_extern(lhsWord0, lhsWord1, lhsWord2, rhsWord0, rhsWord1, rhsWord2) } func _$joinPaths(_ lhs: String, _ rhs: String) throws(JSException) -> String { - let ret0 = lhs.bridgeJSWithLoweredParameter { (lhsBytes, lhsLength) in - let ret1 = rhs.bridgeJSWithLoweredParameter { (rhsBytes, rhsLength) in - let ret = bjs_joinPaths(lhsBytes, lhsLength, rhsBytes, rhsLength) + let ret0 = lhs.bridgeJSWithLoweredParameter { (lhsWord0, lhsWord1, lhsWord2) in + let ret1 = rhs.bridgeJSWithLoweredParameter { (rhsWord0, rhsWord1, rhsWord2) in + let ret = bjs_joinPaths(lhsWord0, lhsWord1, lhsWord2, rhsWord0, rhsWord1, rhsWord2) return ret } return ret1 diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Optionals.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Optionals.swift index f45a0bb1e..c35ea24e6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Optionals.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Optionals.swift @@ -484,14 +484,14 @@ fileprivate func _bjs_OptionalPropertyHolder_wrap_extern(_ pointer: UnsafeMutabl #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WithOptionalJSClass_init") -fileprivate func bjs_WithOptionalJSClass_init_extern(_ valueOrNullIsSome: Int32, _ valueOrNullBytes: Int32, _ valueOrNullLength: Int32, _ valueOrUndefinedIsSome: Int32, _ valueOrUndefinedBytes: Int32, _ valueOrUndefinedLength: Int32) -> Int32 +fileprivate func bjs_WithOptionalJSClass_init_extern(_ valueOrNullIsSome: Int32, _ valueOrNullWord0: Int32, _ valueOrNullWord1: Int32, _ valueOrNullWord2: Int32, _ valueOrUndefinedIsSome: Int32, _ valueOrUndefinedWord0: Int32, _ valueOrUndefinedWord1: Int32, _ valueOrUndefinedWord2: Int32) -> Int32 #else -fileprivate func bjs_WithOptionalJSClass_init_extern(_ valueOrNullIsSome: Int32, _ valueOrNullBytes: Int32, _ valueOrNullLength: Int32, _ valueOrUndefinedIsSome: Int32, _ valueOrUndefinedBytes: Int32, _ valueOrUndefinedLength: Int32) -> Int32 { +fileprivate func bjs_WithOptionalJSClass_init_extern(_ valueOrNullIsSome: Int32, _ valueOrNullWord0: Int32, _ valueOrNullWord1: Int32, _ valueOrNullWord2: Int32, _ valueOrUndefinedIsSome: Int32, _ valueOrUndefinedWord0: Int32, _ valueOrUndefinedWord1: Int32, _ valueOrUndefinedWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WithOptionalJSClass_init(_ valueOrNullIsSome: Int32, _ valueOrNullBytes: Int32, _ valueOrNullLength: Int32, _ valueOrUndefinedIsSome: Int32, _ valueOrUndefinedBytes: Int32, _ valueOrUndefinedLength: Int32) -> Int32 { - return bjs_WithOptionalJSClass_init_extern(valueOrNullIsSome, valueOrNullBytes, valueOrNullLength, valueOrUndefinedIsSome, valueOrUndefinedBytes, valueOrUndefinedLength) +@inline(never) fileprivate func bjs_WithOptionalJSClass_init(_ valueOrNullIsSome: Int32, _ valueOrNullWord0: Int32, _ valueOrNullWord1: Int32, _ valueOrNullWord2: Int32, _ valueOrUndefinedIsSome: Int32, _ valueOrUndefinedWord0: Int32, _ valueOrUndefinedWord1: Int32, _ valueOrUndefinedWord2: Int32) -> Int32 { + return bjs_WithOptionalJSClass_init_extern(valueOrNullIsSome, valueOrNullWord0, valueOrNullWord1, valueOrNullWord2, valueOrUndefinedIsSome, valueOrUndefinedWord0, valueOrUndefinedWord1, valueOrUndefinedWord2) } #if arch(wasm32) @@ -604,26 +604,26 @@ fileprivate func bjs_WithOptionalJSClass_childOrNull_get_extern(_ self: Int32) - #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WithOptionalJSClass_stringOrNull_set") -fileprivate func bjs_WithOptionalJSClass_stringOrNull_set_extern(_ self: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_WithOptionalJSClass_stringOrNull_set_extern(_ self: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_WithOptionalJSClass_stringOrNull_set_extern(_ self: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_WithOptionalJSClass_stringOrNull_set_extern(_ self: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WithOptionalJSClass_stringOrNull_set(_ self: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_WithOptionalJSClass_stringOrNull_set_extern(self, newValueIsSome, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_WithOptionalJSClass_stringOrNull_set(_ self: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_WithOptionalJSClass_stringOrNull_set_extern(self, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WithOptionalJSClass_stringOrUndefined_set") -fileprivate func bjs_WithOptionalJSClass_stringOrUndefined_set_extern(_ self: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_WithOptionalJSClass_stringOrUndefined_set_extern(_ self: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_WithOptionalJSClass_stringOrUndefined_set_extern(_ self: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_WithOptionalJSClass_stringOrUndefined_set_extern(_ self: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WithOptionalJSClass_stringOrUndefined_set(_ self: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_WithOptionalJSClass_stringOrUndefined_set_extern(self, newValueIsSome, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_WithOptionalJSClass_stringOrUndefined_set(_ self: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_WithOptionalJSClass_stringOrUndefined_set_extern(self, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -712,26 +712,26 @@ fileprivate func bjs_WithOptionalJSClass_childOrNull_set_extern(_ self: Int32, _ #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WithOptionalJSClass_roundTripStringOrNull") -fileprivate func bjs_WithOptionalJSClass_roundTripStringOrNull_extern(_ self: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func bjs_WithOptionalJSClass_roundTripStringOrNull_extern(_ self: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func bjs_WithOptionalJSClass_roundTripStringOrNull_extern(_ self: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func bjs_WithOptionalJSClass_roundTripStringOrNull_extern(_ self: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WithOptionalJSClass_roundTripStringOrNull(_ self: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return bjs_WithOptionalJSClass_roundTripStringOrNull_extern(self, valueIsSome, valueBytes, valueLength) +@inline(never) fileprivate func bjs_WithOptionalJSClass_roundTripStringOrNull(_ self: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return bjs_WithOptionalJSClass_roundTripStringOrNull_extern(self, valueIsSome, valueWord0, valueWord1, valueWord2) } #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_WithOptionalJSClass_roundTripStringOrUndefined") -fileprivate func bjs_WithOptionalJSClass_roundTripStringOrUndefined_extern(_ self: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func bjs_WithOptionalJSClass_roundTripStringOrUndefined_extern(_ self: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func bjs_WithOptionalJSClass_roundTripStringOrUndefined_extern(_ self: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func bjs_WithOptionalJSClass_roundTripStringOrUndefined_extern(_ self: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WithOptionalJSClass_roundTripStringOrUndefined(_ self: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return bjs_WithOptionalJSClass_roundTripStringOrUndefined_extern(self, valueIsSome, valueBytes, valueLength) +@inline(never) fileprivate func bjs_WithOptionalJSClass_roundTripStringOrUndefined(_ self: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return bjs_WithOptionalJSClass_roundTripStringOrUndefined_extern(self, valueIsSome, valueWord0, valueWord1, valueWord2) } #if arch(wasm32) @@ -819,9 +819,9 @@ fileprivate func bjs_WithOptionalJSClass_roundTripChildOrNull_extern(_ self: Int } func _$WithOptionalJSClass_init(_ valueOrNull: Optional, _ valueOrUndefined: JSUndefinedOr) throws(JSException) -> JSObject { - let ret0 = valueOrNull.bridgeJSWithLoweredParameter { (valueOrNullIsSome, valueOrNullBytes, valueOrNullLength) in - let ret1 = valueOrUndefined.bridgeJSWithLoweredParameter { (valueOrUndefinedIsSome, valueOrUndefinedBytes, valueOrUndefinedLength) in - let ret = bjs_WithOptionalJSClass_init(valueOrNullIsSome, valueOrNullBytes, valueOrNullLength, valueOrUndefinedIsSome, valueOrUndefinedBytes, valueOrUndefinedLength) + let ret0 = valueOrNull.bridgeJSWithLoweredParameter { (valueOrNullIsSome, valueOrNullWord0, valueOrNullWord1, valueOrNullWord2) in + let ret1 = valueOrUndefined.bridgeJSWithLoweredParameter { (valueOrUndefinedIsSome, valueOrUndefinedWord0, valueOrUndefinedWord1, valueOrUndefinedWord2) in + let ret = bjs_WithOptionalJSClass_init(valueOrNullIsSome, valueOrNullWord0, valueOrNullWord1, valueOrNullWord2, valueOrUndefinedIsSome, valueOrUndefinedWord0, valueOrUndefinedWord1, valueOrUndefinedWord2) return ret } return ret1 @@ -915,9 +915,9 @@ func _$WithOptionalJSClass_childOrNull_get(_ self: JSObject) throws(JSException) } func _$WithOptionalJSClass_stringOrNull_set(_ self: JSObject, _ newValue: Optional) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WithOptionalJSClass_stringOrNull_set(selfValue, newValueIsSome, newValueBytes, newValueLength) + bjs_WithOptionalJSClass_stringOrNull_set(selfValue, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -925,9 +925,9 @@ func _$WithOptionalJSClass_stringOrNull_set(_ self: JSObject, _ newValue: Option } func _$WithOptionalJSClass_stringOrUndefined_set(_ self: JSObject, _ newValue: JSUndefinedOr) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WithOptionalJSClass_stringOrUndefined_set(selfValue, newValueIsSome, newValueBytes, newValueLength) + bjs_WithOptionalJSClass_stringOrUndefined_set(selfValue, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -998,9 +998,9 @@ func _$WithOptionalJSClass_childOrNull_set(_ self: JSObject, _ newValue: Optiona } func _$WithOptionalJSClass_roundTripStringOrNull(_ self: JSObject, _ value: Optional) throws(JSException) -> Optional { - value.bridgeJSWithLoweredParameter { (valueIsSome, valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueIsSome, valueWord0, valueWord1, valueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WithOptionalJSClass_roundTripStringOrNull(selfValue, valueIsSome, valueBytes, valueLength) + bjs_WithOptionalJSClass_roundTripStringOrNull(selfValue, valueIsSome, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error @@ -1009,9 +1009,9 @@ func _$WithOptionalJSClass_roundTripStringOrNull(_ self: JSObject, _ value: Opti } func _$WithOptionalJSClass_roundTripStringOrUndefined(_ self: JSObject, _ value: JSUndefinedOr) throws(JSException) -> JSUndefinedOr { - value.bridgeJSWithLoweredParameter { (valueIsSome, valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueIsSome, valueWord0, valueWord1, valueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WithOptionalJSClass_roundTripStringOrUndefined(selfValue, valueIsSome, valueBytes, valueLength) + bjs_WithOptionalJSClass_roundTripStringOrUndefined(selfValue, valueIsSome, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift index ec3d2f65a..a91524da5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/Protocol.swift @@ -5,9 +5,9 @@ extension MyViewControllerDelegate where Self: _BridgedSwiftProtocolWrapper { } func onValueChanged(_ value: String) -> Void { - value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in let jsObjectValue = jsObject.bridgeJSLowerParameter() - bjs_MyViewControllerDelegate_onValueChanged(jsObjectValue, valueBytes, valueLength) + bjs_MyViewControllerDelegate_onValueChanged(jsObjectValue, valueWord0, valueWord1, valueWord2) } } @@ -19,10 +19,10 @@ extension MyViewControllerDelegate where Self: _BridgedSwiftProtocolWrapper { } func onLabelUpdated(_ prefix: String, _ suffix: String) -> Void { - prefix.bridgeJSWithLoweredParameter { (prefixBytes, prefixLength) in - suffix.bridgeJSWithLoweredParameter { (suffixBytes, suffixLength) in + prefix.bridgeJSWithLoweredParameter { (prefixWord0, prefixWord1, prefixWord2) in + suffix.bridgeJSWithLoweredParameter { (suffixWord0, suffixWord1, suffixWord2) in let jsObjectValue = jsObject.bridgeJSLowerParameter() - bjs_MyViewControllerDelegate_onLabelUpdated(jsObjectValue, prefixBytes, prefixLength, suffixBytes, suffixLength) + bjs_MyViewControllerDelegate_onLabelUpdated(jsObjectValue, prefixWord0, prefixWord1, prefixWord2, suffixWord0, suffixWord1, suffixWord2) } } } @@ -103,9 +103,9 @@ extension MyViewControllerDelegate where Self: _BridgedSwiftProtocolWrapper { return Optional.bridgeJSLiftReturnFromSideChannel() } set { - newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueWord0, newValueWord1, newValueWord2) in let jsObjectValue = jsObject.bridgeJSLowerParameter() - bjs_MyViewControllerDelegate_optionalName_set(jsObjectValue, newValueIsSome, newValueBytes, newValueLength) + bjs_MyViewControllerDelegate_optionalName_set(jsObjectValue, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } } } @@ -117,9 +117,9 @@ extension MyViewControllerDelegate where Self: _BridgedSwiftProtocolWrapper { return Optional.bridgeJSLiftReturnFromSideChannel() } set { - newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueWord0, newValueWord1, newValueWord2) in let jsObjectValue = jsObject.bridgeJSLowerParameter() - bjs_MyViewControllerDelegate_optionalRawEnum_set(jsObjectValue, newValueIsSome, newValueBytes, newValueLength) + bjs_MyViewControllerDelegate_optionalRawEnum_set(jsObjectValue, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } } } @@ -131,9 +131,9 @@ extension MyViewControllerDelegate where Self: _BridgedSwiftProtocolWrapper { return ExampleEnum.bridgeJSLiftReturn(ret) } set { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let jsObjectValue = jsObject.bridgeJSLowerParameter() - bjs_MyViewControllerDelegate_rawStringEnum_set(jsObjectValue, newValueBytes, newValueLength) + bjs_MyViewControllerDelegate_rawStringEnum_set(jsObjectValue, newValueWord0, newValueWord1, newValueWord2) } } } @@ -239,14 +239,14 @@ fileprivate func bjs_MyViewControllerDelegate_onSomethingHappened_extern(_ jsObj #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_onValueChanged") -fileprivate func bjs_MyViewControllerDelegate_onValueChanged_extern(_ jsObject: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func bjs_MyViewControllerDelegate_onValueChanged_extern(_ jsObject: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func bjs_MyViewControllerDelegate_onValueChanged_extern(_ jsObject: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func bjs_MyViewControllerDelegate_onValueChanged_extern(_ jsObject: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_MyViewControllerDelegate_onValueChanged(_ jsObject: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return bjs_MyViewControllerDelegate_onValueChanged_extern(jsObject, valueBytes, valueLength) +@inline(never) fileprivate func bjs_MyViewControllerDelegate_onValueChanged(_ jsObject: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return bjs_MyViewControllerDelegate_onValueChanged_extern(jsObject, valueWord0, valueWord1, valueWord2) } #if arch(wasm32) @@ -263,14 +263,14 @@ fileprivate func bjs_MyViewControllerDelegate_onCountUpdated_extern(_ jsObject: #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_onLabelUpdated") -fileprivate func bjs_MyViewControllerDelegate_onLabelUpdated_extern(_ jsObject: Int32, _ prefixBytes: Int32, _ prefixLength: Int32, _ suffixBytes: Int32, _ suffixLength: Int32) -> Void +fileprivate func bjs_MyViewControllerDelegate_onLabelUpdated_extern(_ jsObject: Int32, _ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32, _ suffixWord0: Int32, _ suffixWord1: Int32, _ suffixWord2: Int32) -> Void #else -fileprivate func bjs_MyViewControllerDelegate_onLabelUpdated_extern(_ jsObject: Int32, _ prefixBytes: Int32, _ prefixLength: Int32, _ suffixBytes: Int32, _ suffixLength: Int32) -> Void { +fileprivate func bjs_MyViewControllerDelegate_onLabelUpdated_extern(_ jsObject: Int32, _ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32, _ suffixWord0: Int32, _ suffixWord1: Int32, _ suffixWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_MyViewControllerDelegate_onLabelUpdated(_ jsObject: Int32, _ prefixBytes: Int32, _ prefixLength: Int32, _ suffixBytes: Int32, _ suffixLength: Int32) -> Void { - return bjs_MyViewControllerDelegate_onLabelUpdated_extern(jsObject, prefixBytes, prefixLength, suffixBytes, suffixLength) +@inline(never) fileprivate func bjs_MyViewControllerDelegate_onLabelUpdated(_ jsObject: Int32, _ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32, _ suffixWord0: Int32, _ suffixWord1: Int32, _ suffixWord2: Int32) -> Void { + return bjs_MyViewControllerDelegate_onLabelUpdated_extern(jsObject, prefixWord0, prefixWord1, prefixWord2, suffixWord0, suffixWord1, suffixWord2) } #if arch(wasm32) @@ -419,14 +419,14 @@ fileprivate func bjs_MyViewControllerDelegate_optionalName_get_extern(_ jsObject #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_optionalName_set") -fileprivate func bjs_MyViewControllerDelegate_optionalName_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_MyViewControllerDelegate_optionalName_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_MyViewControllerDelegate_optionalName_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_MyViewControllerDelegate_optionalName_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_MyViewControllerDelegate_optionalName_set(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_MyViewControllerDelegate_optionalName_set_extern(jsObject, newValueIsSome, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_MyViewControllerDelegate_optionalName_set(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_MyViewControllerDelegate_optionalName_set_extern(jsObject, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -443,14 +443,14 @@ fileprivate func bjs_MyViewControllerDelegate_optionalRawEnum_get_extern(_ jsObj #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_optionalRawEnum_set") -fileprivate func bjs_MyViewControllerDelegate_optionalRawEnum_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_MyViewControllerDelegate_optionalRawEnum_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_MyViewControllerDelegate_optionalRawEnum_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_MyViewControllerDelegate_optionalRawEnum_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_MyViewControllerDelegate_optionalRawEnum_set(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_MyViewControllerDelegate_optionalRawEnum_set_extern(jsObject, newValueIsSome, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_MyViewControllerDelegate_optionalRawEnum_set(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_MyViewControllerDelegate_optionalRawEnum_set_extern(jsObject, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -467,14 +467,14 @@ fileprivate func bjs_MyViewControllerDelegate_rawStringEnum_get_extern(_ jsObjec #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_MyViewControllerDelegate_rawStringEnum_set") -fileprivate func bjs_MyViewControllerDelegate_rawStringEnum_set_extern(_ jsObject: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_MyViewControllerDelegate_rawStringEnum_set_extern(_ jsObject: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_MyViewControllerDelegate_rawStringEnum_set_extern(_ jsObject: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_MyViewControllerDelegate_rawStringEnum_set_extern(_ jsObject: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_MyViewControllerDelegate_rawStringEnum_set(_ jsObject: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_MyViewControllerDelegate_rawStringEnum_set_extern(jsObject, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_MyViewControllerDelegate_rawStringEnum_set(_ jsObject: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_MyViewControllerDelegate_rawStringEnum_set_extern(jsObject, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StringParameter.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StringParameter.swift index f519c3306..f00cbd49c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StringParameter.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/StringParameter.swift @@ -23,19 +23,19 @@ public func _bjs_roundtripString(_ aBytes: Int32, _ aLength: Int32) -> Void { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_checkString") -fileprivate func bjs_checkString_extern(_ aBytes: Int32, _ aLength: Int32) -> Void +fileprivate func bjs_checkString_extern(_ aWord0: Int32, _ aWord1: Int32, _ aWord2: Int32) -> Void #else -fileprivate func bjs_checkString_extern(_ aBytes: Int32, _ aLength: Int32) -> Void { +fileprivate func bjs_checkString_extern(_ aWord0: Int32, _ aWord1: Int32, _ aWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_checkString(_ aBytes: Int32, _ aLength: Int32) -> Void { - return bjs_checkString_extern(aBytes, aLength) +@inline(never) fileprivate func bjs_checkString(_ aWord0: Int32, _ aWord1: Int32, _ aWord2: Int32) -> Void { + return bjs_checkString_extern(aWord0, aWord1, aWord2) } func _$checkString(_ a: String) throws(JSException) -> Void { - a.bridgeJSWithLoweredParameter { (aBytes, aLength) in - bjs_checkString(aBytes, aLength) + a.bridgeJSWithLoweredParameter { (aWord0, aWord1, aWord2) in + bjs_checkString(aWord0, aWord1, aWord2) } if let error = _swift_js_take_exception() { throw error @@ -44,20 +44,20 @@ func _$checkString(_ a: String) throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "TestModule", name: "bjs_checkStringWithLength") -fileprivate func bjs_checkStringWithLength_extern(_ aBytes: Int32, _ aLength: Int32, _ b: Float64) -> Void +fileprivate func bjs_checkStringWithLength_extern(_ aWord0: Int32, _ aWord1: Int32, _ aWord2: Int32, _ b: Float64) -> Void #else -fileprivate func bjs_checkStringWithLength_extern(_ aBytes: Int32, _ aLength: Int32, _ b: Float64) -> Void { +fileprivate func bjs_checkStringWithLength_extern(_ aWord0: Int32, _ aWord1: Int32, _ aWord2: Int32, _ b: Float64) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_checkStringWithLength(_ aBytes: Int32, _ aLength: Int32, _ b: Float64) -> Void { - return bjs_checkStringWithLength_extern(aBytes, aLength, b) +@inline(never) fileprivate func bjs_checkStringWithLength(_ aWord0: Int32, _ aWord1: Int32, _ aWord2: Int32, _ b: Float64) -> Void { + return bjs_checkStringWithLength_extern(aWord0, aWord1, aWord2, b) } func _$checkStringWithLength(_ a: String, _ b: Double) throws(JSException) -> Void { - a.bridgeJSWithLoweredParameter { (aBytes, aLength) in + a.bridgeJSWithLoweredParameter { (aWord0, aWord1, aWord2) in let bValue = b.bridgeJSLowerParameter() - bjs_checkStringWithLength(aBytes, aLength, bValue) + bjs_checkStringWithLength(aWord0, aWord1, aWord2, bValue) } if let error = _swift_js_take_exception() { throw error diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift index af740495c..152b41102 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosure.swift @@ -64,14 +64,14 @@ public func _invoke_swift_closure_TestModule_10TestModule10HttpStatusO_10HttpSta #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO") -fileprivate func invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -91,9 +91,9 @@ private enum _BJS_Closure_10TestModule5ThemeO_5ThemeO { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -387,14 +387,14 @@ public func _invoke_swift_closure_TestModule_10TestModule9DirectionO_9DirectionO #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleKSS_Sb") -fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -414,9 +414,9 @@ private enum _BJS_Closure_10TestModuleKSS_Sb { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] (param0: String) throws(JSException) -> Bool in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_TestModule_10TestModuleKSS_Sb(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_TestModule_10TestModuleKSS_Sb(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -471,14 +471,14 @@ public func _invoke_swift_closure_TestModule_10TestModuleKSS_Sb(_ boxPtr: Unsafe #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleKSS_Si") -fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Si_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Si_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Si_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Si_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Si(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_TestModule_10TestModuleKSS_Si_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Si(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_TestModule_10TestModuleKSS_Si_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -498,9 +498,9 @@ private enum _BJS_Closure_10TestModuleKSS_Si { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] (param0: String) throws(JSException) -> Int in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_TestModule_10TestModuleKSS_Si(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_TestModule_10TestModuleKSS_Si(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -555,14 +555,14 @@ public func _invoke_swift_closure_TestModule_10TestModuleKSS_Si(_ boxPtr: Unsafe #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleSS_SS") -fileprivate func invoke_js_callback_TestModule_10TestModuleSS_SS_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_TestModule_10TestModuleSS_SS_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_TestModule_10TestModuleSS_SS_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_TestModule_10TestModuleSS_SS_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleSS_SS(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_TestModule_10TestModuleSS_SS_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleSS_SS(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_TestModule_10TestModuleSS_SS_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -582,9 +582,9 @@ private enum _BJS_Closure_10TestModuleSS_SS { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_TestModule_10TestModuleSS_SS(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_TestModule_10TestModuleSS_SS(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -942,14 +942,14 @@ public func _invoke_swift_closure_TestModule_10TestModuleSq10HttpStatusO_Sq10Htt #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO") -fileprivate func invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO_extern(callback, param0IsSome, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO_extern(callback, param0IsSome, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -969,9 +969,9 @@ private enum _BJS_Closure_10TestModuleSq5ThemeO_Sq5ThemeO { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO(callbackValue, param0IsSome, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO(callbackValue, param0IsSome, param0Word0, param0Word1, param0Word2) } return Optional.bridgeJSLiftReturnFromSideChannel() #else @@ -1263,14 +1263,14 @@ public func _invoke_swift_closure_TestModule_10TestModuleSq9DirectionO_Sq9Direct #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleSqSS_SqSS") -fileprivate func invoke_js_callback_TestModule_10TestModuleSqSS_SqSS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModuleSqSS_SqSS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModuleSqSS_SqSS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModuleSqSS_SqSS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleSqSS_SqSS(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModuleSqSS_SqSS_extern(callback, param0IsSome, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleSqSS_SqSS(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModuleSqSS_SqSS_extern(callback, param0IsSome, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -1290,9 +1290,9 @@ private enum _BJS_Closure_10TestModuleSqSS_SqSS { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModuleSqSS_SqSS(callbackValue, param0IsSome, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModuleSqSS_SqSS(callbackValue, param0IsSome, param0Word0, param0Word1, param0Word2) } return Optional.bridgeJSLiftReturnFromSideChannel() #else @@ -1584,14 +1584,14 @@ public func _invoke_swift_closure_TestModule_10TestModuleSqSi_SqSi(_ boxPtr: Uns #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleYaKSS_SS") -fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(resolveRef, rejectRef, callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(resolveRef, rejectRef, callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -1616,9 +1616,9 @@ private enum _BJS_Closure_10TestModuleYaKSS_SS { }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModuleYaKSS_SS(resolveRef, rejectRef, callbackValue, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModuleYaKSS_SS(resolveRef, rejectRef, callbackValue, param0Word0, param0Word1, param0Word2) } } return resolved @@ -1727,14 +1727,14 @@ public func _invoke_swift_closure_TestModule_10TestModuleYaKSb_9APIResultO(_ box #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV") -fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV_extern(resolveRef, rejectRef, callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV_extern(resolveRef, rejectRef, callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -1759,9 +1759,9 @@ private enum _BJS_Closure_10TestModuleYaSS_6AnimalV { }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV(resolveRef, rejectRef, callbackValue, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV(resolveRef, rejectRef, callbackValue, param0Word0, param0Word1, param0Word2) } } return resolved @@ -1799,14 +1799,14 @@ public func _invoke_swift_closure_TestModule_10TestModuleYaSS_6AnimalV(_ boxPtr: #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleYaSS_SS") -fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModuleYaSS_SS_extern(resolveRef, rejectRef, callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleYaSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModuleYaSS_SS_extern(resolveRef, rejectRef, callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -1831,9 +1831,9 @@ private enum _BJS_Closure_10TestModuleYaSS_SS { }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModuleYaSS_SS(resolveRef, rejectRef, callbackValue, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModuleYaSS_SS(resolveRef, rejectRef, callbackValue, param0Word0, param0Word1, param0Word2) } } return resolved @@ -2057,14 +2057,14 @@ public func _invoke_swift_closure_TestModule_10TestModules9APIResultO_y(_ boxPtr #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModulesSS_y") -fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModulesSS_y_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModulesSS_y_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -2084,9 +2084,9 @@ private enum _BJS_Closure_10TestModulesSS_y { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModulesSS_y(callbackValue, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModulesSS_y(callbackValue, param0Word0, param0Word1, param0Word2) } #else fatalError("Only available on WebAssembly") @@ -2741,20 +2741,20 @@ func _$Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) #if arch(wasm32) @_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_SS") -fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func promise_resolve_TestModule_SS(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return promise_resolve_TestModule_SS_extern(promise, valueBytes, valueLength) +@inline(never) fileprivate func promise_resolve_TestModule_SS(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_TestModule_SS_extern(promise, valueWord0, valueWord1, valueWord2) } func _$Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException) -> Void { - value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in let promiseValue = promise.bridgeJSLowerParameter() - promise_resolve_TestModule_SS(promiseValue, valueBytes, valueLength) + promise_resolve_TestModule_SS(promiseValue, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosureImports.swift b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosureImports.swift index 3634ad491..5c22db7f2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosureImports.swift +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSCodegenTests/SwiftClosureImports.swift @@ -1,13 +1,13 @@ #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleKSS_Sb") -fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleKSS_Sb(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_TestModule_10TestModuleKSS_Sb_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -27,9 +27,9 @@ private enum _BJS_Closure_10TestModuleKSS_Sb { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] (param0: String) throws(JSException) -> Bool in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_TestModule_10TestModuleKSS_Sb(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_TestModule_10TestModuleKSS_Sb(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -148,14 +148,14 @@ public func _invoke_swift_closure_TestModule_10TestModuleSi_Si(_ boxPtr: UnsafeM #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModuleYaKSS_SS") -fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(resolveRef, rejectRef, callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModuleYaKSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModuleYaKSS_SS_extern(resolveRef, rejectRef, callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -180,9 +180,9 @@ private enum _BJS_Closure_10TestModuleYaKSS_SS { }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModuleYaKSS_SS(resolveRef, rejectRef, callbackValue, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModuleYaKSS_SS(resolveRef, rejectRef, callbackValue, param0Word0, param0Word1, param0Word2) } } return resolved @@ -282,14 +282,14 @@ public func _invoke_swift_closure_TestModule_10TestModules7JSValueV_y(_ boxPtr: #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_TestModule_10TestModulesSS_y") -fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_TestModule_10TestModulesSS_y_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_TestModule_10TestModulesSS_y(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_TestModule_10TestModulesSS_y_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -309,9 +309,9 @@ private enum _BJS_Closure_10TestModulesSS_y { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_TestModule_10TestModulesSS_y(callbackValue, param0Bytes, param0Length) + invoke_js_callback_TestModule_10TestModulesSS_y(callbackValue, param0Word0, param0Word1, param0Word2) } #else fatalError("Only available on WebAssembly") @@ -390,20 +390,20 @@ func _$Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) #if arch(wasm32) @_extern(wasm, module: "bjs", name: "promise_resolve_TestModule_SS") -fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func promise_resolve_TestModule_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func promise_resolve_TestModule_SS(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return promise_resolve_TestModule_SS_extern(promise, valueBytes, valueLength) +@inline(never) fileprivate func promise_resolve_TestModule_SS(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_TestModule_SS_extern(promise, valueWord0, valueWord1, valueWord2) } func _$Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException) -> Void { - value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in let promiseValue = promise.bridgeJSLowerParameter() - promise_resolve_TestModule_SS(promiseValue, valueBytes, valueLength) + promise_resolve_TestModule_SS(promiseValue, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error } } diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js index b1cfb68aa..04c230153 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Alias.js @@ -15,6 +15,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -432,8 +434,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -441,8 +443,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -467,8 +469,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -536,11 +538,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -611,19 +613,19 @@ export async function createInstantiator(options, swift) { return swift.memory.retain(obj); }; const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; - TestModule["bjs_acceptTagged"] = function bjs_acceptTagged(taggedBytes, taggedCount) { + TestModule["bjs_acceptTagged"] = function bjs_acceptTagged(taggedWord0, taggedWord1, taggedWord2) { try { - const string = decodeString(taggedBytes, taggedCount); + const string = decodeString(taggedWord0, taggedWord1, taggedWord2); imports.acceptTagged(string); } catch (error) { setException(error); } } - TestModule["bjs_acceptOptionalTagged"] = function bjs_acceptOptionalTagged(taggedIsSome, taggedBytes, taggedCount) { + TestModule["bjs_acceptOptionalTagged"] = function bjs_acceptOptionalTagged(taggedIsSome, taggedWord0, taggedWord1, taggedWord2) { try { let optResult; if (taggedIsSome) { - const string = decodeString(taggedBytes, taggedCount); + const string = decodeString(taggedWord0, taggedWord1, taggedWord2); optResult = string; } else { optResult = null; @@ -633,9 +635,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_roundtripTagged"] = function bjs_roundtripTagged(taggedBytes, taggedCount) { + TestModule["bjs_roundtripTagged"] = function bjs_roundtripTagged(taggedWord0, taggedWord1, taggedWord2) { try { - const string = decodeString(taggedBytes, taggedCount); + const string = decodeString(taggedWord0, taggedWord1, taggedWord2); let ret = imports.roundtripTagged(string); tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; @@ -678,10 +680,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.js index fa9095cd8..55b7e1126 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AliasInClosure.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -42,7 +44,7 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, state.file >>> 0); let length = 0; while (bytes[length] !== 0) { length += 1; } - const fileID = decodeString(state.file, length); + const fileID = decodeUTF8(state.file, length); throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); } return func(...args); @@ -64,8 +66,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -73,8 +75,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -99,8 +101,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -168,11 +170,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -290,10 +292,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js index 6d3992ce5..1dcebf70f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ArrayTypes.js @@ -22,6 +22,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -495,8 +497,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -504,8 +506,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -530,8 +532,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -606,11 +608,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -740,10 +742,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js index c025cf4f0..12bc60107 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Async.js @@ -19,6 +19,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -407,8 +409,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -416,8 +418,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -442,8 +444,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -504,9 +506,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - bjs["promise_resolve_TestModule_SS"] = function(promise, valueBytes, valueCount) { + bjs["promise_resolve_TestModule_SS"] = function(promise, valueWord0, valueWord1, valueWord2) { try { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(string); } catch (error) { setException(error); @@ -555,9 +557,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - bjs["promise_resolve_TestModule_10AsyncThemeO"] = function(promise, valueBytes, valueCount) { + bjs["promise_resolve_TestModule_10AsyncThemeO"] = function(promise, valueWord0, valueWord1, valueWord2) { try { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(string); } catch (error) { setException(error); @@ -570,11 +572,11 @@ export async function createInstantiator(options, swift) { setException(error); } } - bjs["promise_resolve_TestModule_Sq10AsyncThemeO"] = function(promise, valueIsSome, valueBytes, valueCount) { + bjs["promise_resolve_TestModule_Sq10AsyncThemeO"] = function(promise, valueIsSome, valueWord0, valueWord1, valueWord2) { try { let optResult; if (valueIsSome) { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); optResult = string; } else { optResult = null; @@ -660,11 +662,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -724,10 +726,180 @@ export async function createInstantiator(options, swift) { bjs["swift_js_closure_unregister"] = function(funcRef) {} }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.js index 49b2ff88b..ec946060c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncAssociatedValueEnum.js @@ -16,6 +16,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -172,8 +174,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -181,8 +183,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -207,8 +209,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -306,11 +308,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -370,10 +372,180 @@ export async function createInstantiator(options, swift) { bjs["swift_js_closure_unregister"] = function(funcRef) {} }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js index 96c0d11e8..e4555d1ce 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncImport.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -131,7 +133,7 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, state.file >>> 0); let length = 0; while (bytes[length] !== 0) { length += 1; } - const fileID = decodeString(state.file, length); + const fileID = decodeUTF8(state.file, length); throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); } return func(...args); @@ -154,8 +156,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -163,8 +165,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -189,8 +191,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -257,11 +259,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -365,10 +367,10 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModules8JSObjectC_y); } - bjs["invoke_js_callback_TestModule_10TestModulesSS_y"] = function(callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModulesSS_y"] = function(callbackId, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); callback(string); } catch (error) { setException(error); @@ -479,10 +481,10 @@ export async function createInstantiator(options, swift) { const reject = swift.memory.getObject(rejectRef); imports.asyncRoundTripInt(v).then(resolve, reject); } - TestModule["bjs_asyncRoundTripString"] = function bjs_asyncRoundTripString(resolveRef, rejectRef, vBytes, vCount) { + TestModule["bjs_asyncRoundTripString"] = function bjs_asyncRoundTripString(resolveRef, rejectRef, vWord0, vWord1, vWord2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); - const string = decodeString(vBytes, vCount); + const string = decodeString(vWord0, vWord1, vWord2); imports.asyncRoundTripString(string).then(resolve, reject); } TestModule["bjs_asyncRoundTripBool"] = function bjs_asyncRoundTripBool(resolveRef, rejectRef, v) { @@ -502,10 +504,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js index eea340c35..ccce528a6 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/AsyncStaticImport.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -131,7 +133,7 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, state.file >>> 0); let length = 0; while (bytes[length] !== 0) { length += 1; } - const fileID = decodeString(state.file, length); + const fileID = decodeUTF8(state.file, length); throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); } return func(...args); @@ -154,8 +156,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -163,8 +165,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -189,8 +191,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -257,11 +259,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -398,10 +400,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.js index ecdb0b059..e302251de 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ClassWithNestedTypes.js @@ -14,6 +14,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -56,8 +58,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -65,8 +67,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -91,8 +93,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -167,11 +169,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -239,10 +241,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js index 9fda637df..ad278c725 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DefaultParameters.js @@ -15,6 +15,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -419,8 +421,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -428,8 +430,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -454,8 +456,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -537,11 +539,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -617,10 +619,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js index 8ccda3c19..5c76843c8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DictionaryTypes.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -399,8 +401,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -408,8 +410,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -434,8 +436,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -510,11 +512,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -592,10 +594,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.js index 6d23d394f..97a37a148 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/DocComments.js @@ -15,6 +15,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -56,8 +58,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -65,8 +67,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -91,8 +93,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -167,11 +169,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -256,10 +258,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.js index d8bc06fcf..55dc286af 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAlias.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -215,10 +217,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js index 66f85760c..a13fa75cd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValue.js @@ -90,6 +90,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -994,8 +996,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -1003,8 +1005,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -1029,8 +1031,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -1105,11 +1107,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -1177,10 +1179,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.js index 2b36953bb..aee8b8a04 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumAssociatedValueImport.js @@ -16,6 +16,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -84,8 +86,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -93,8 +95,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -119,8 +121,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -188,11 +190,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -301,10 +303,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.js index 838e3062b..29f861a26 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCase.js @@ -33,6 +33,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -63,8 +65,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -72,8 +74,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -98,8 +100,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -167,11 +169,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -231,10 +233,180 @@ export async function createInstantiator(options, swift) { bjs["swift_js_closure_unregister"] = function(funcRef) {} }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.js index bde5fc31f..b8fbe139a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumCaseImport.js @@ -14,6 +14,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -45,8 +47,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -54,8 +56,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -80,8 +82,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -149,11 +151,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -239,10 +241,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js index 0a691374b..77c82c250 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.Global.js @@ -53,6 +53,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -83,8 +85,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -92,8 +94,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -118,8 +120,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -187,11 +189,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -271,10 +273,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js index 03f0a8d9a..4c80ed652 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumNamespace.js @@ -34,6 +34,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -64,8 +66,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -73,8 +75,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -99,8 +101,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -168,11 +170,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -252,10 +254,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js index 09e03e44a..301dd8793 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/EnumRawType.js @@ -84,6 +84,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -459,8 +461,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -468,8 +470,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -494,8 +496,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -563,11 +565,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -626,9 +628,9 @@ export async function createInstantiator(options, swift) { } bjs["swift_js_closure_unregister"] = function(funcRef) {} const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; - TestModule["bjs_takesFeatureFlag"] = function bjs_takesFeatureFlag(flagBytes, flagCount) { + TestModule["bjs_takesFeatureFlag"] = function bjs_takesFeatureFlag(flagWord0, flagWord1, flagWord2) { try { - const string = decodeString(flagBytes, flagCount); + const string = decodeString(flagWord0, flagWord1, flagWord2); imports.takesFeatureFlag(string); } catch (error) { setException(error); @@ -645,10 +647,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionNestedTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionNestedTypes.js index 85cdb44d4..44ee3d149 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionNestedTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionNestedTypes.js @@ -25,6 +25,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -110,8 +112,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -119,8 +121,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -145,8 +147,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -228,11 +230,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -300,10 +302,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionOrderIndependence.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionOrderIndependence.js index a9091e925..5c68fab90 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionOrderIndependence.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionOrderIndependence.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -59,8 +61,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -68,8 +70,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -94,8 +96,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -170,11 +172,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -242,10 +244,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionScopeParity.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionScopeParity.js index 6b8ae44b1..18f3cf3aa 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionScopeParity.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ExtensionScopeParity.js @@ -13,6 +13,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -55,8 +57,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -64,8 +66,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -90,8 +92,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -166,11 +168,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -242,10 +244,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.js index 56a31b784..51b98268d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/FixedWidthIntegers.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -280,10 +282,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.js index 529f1772e..0c3ec9536 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GenericImports.js @@ -25,6 +25,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -506,8 +508,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -515,8 +517,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -541,8 +543,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -653,11 +655,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -734,10 +736,10 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_genericParse"] = function bjs_genericParse(jsonBytes, jsonCount, tTypeId) { + TestModule["bjs_genericParse"] = function bjs_genericParse(jsonWord0, jsonWord1, jsonWord2, tTypeId) { try { const codecT = __bjs_codecForTypeId(tTypeId); - const string = decodeString(jsonBytes, jsonCount); + const string = decodeString(jsonWord0, jsonWord1, jsonWord2); let ret = imports.genericParse(string); codecT.lower(ret); } catch (error) { @@ -829,11 +831,11 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_GenericPairFactory_init"] = function bjs_GenericPairFactory_init(tagBytes, tagCount, tTypeId, uTypeId) { + TestModule["bjs_GenericPairFactory_init"] = function bjs_GenericPairFactory_init(tagWord0, tagWord1, tagWord2, tTypeId, uTypeId) { try { const codecT = __bjs_codecForTypeId(tTypeId); const codecU = __bjs_codecForTypeId(uTypeId); - const string = decodeString(tagBytes, tagCount); + const string = decodeString(tagWord0, tagWord1, tagWord2); const first = codecT.lift(); const second = codecU.lift(); return swift.memory.retain(new imports.GenericPairFactory(string, first, second)); @@ -938,10 +940,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.js index adb70913c..6e005fed1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalGetter.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -215,9 +217,9 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_JSConsole_log"] = function bjs_JSConsole_log(self, messageBytes, messageCount) { + TestModule["bjs_JSConsole_log"] = function bjs_JSConsole_log(self, messageWord0, messageWord1, messageWord2) { try { - const string = decodeString(messageBytes, messageCount); + const string = decodeString(messageWord0, messageWord1, messageWord2); swift.memory.getObject(self).log(string); } catch (error) { setException(error); @@ -225,10 +227,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.js index 760a6b50c..4cc797a77 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/GlobalThisImports.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -214,9 +216,9 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_parseInt"] = function bjs_parseInt(stringBytes, stringCount) { + TestModule["bjs_parseInt"] = function bjs_parseInt(stringWord0, stringWord1, stringWord2) { try { - const string = decodeString(stringBytes, stringCount); + const string = decodeString(stringWord0, stringWord1, stringWord2); let ret = globalThis.parseInt(string); return ret; } catch (error) { @@ -224,17 +226,17 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_JSConsole_log"] = function bjs_JSConsole_log(self, messageBytes, messageCount) { + TestModule["bjs_JSConsole_log"] = function bjs_JSConsole_log(self, messageWord0, messageWord1, messageWord2) { try { - const string = decodeString(messageBytes, messageCount); + const string = decodeString(messageWord0, messageWord1, messageWord2); swift.memory.getObject(self).log(string); } catch (error) { setException(error); } } - TestModule["bjs_WebSocket_init"] = function bjs_WebSocket_init(urlBytes, urlCount) { + TestModule["bjs_WebSocket_init"] = function bjs_WebSocket_init(urlWord0, urlWord1, urlWord2) { try { - const string = decodeString(urlBytes, urlCount); + const string = decodeString(urlWord0, urlWord1, urlWord2); return swift.memory.retain(new globalThis.WebSocket(string)); } catch (error) { setException(error); @@ -250,10 +252,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js index 1e1d14696..8bd456b5f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.ConfigPointer.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -222,10 +224,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js index c85aee3af..576761318 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.PerClass.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -222,10 +224,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js index c85aee3af..576761318 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/IdentityModeClass.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -222,10 +224,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.js index c92ea6ad3..6c83791a5 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportArray.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -367,8 +369,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -376,8 +378,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -402,8 +404,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -470,11 +472,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -561,9 +563,9 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_borrowedStringAroundStackParams"] = function bjs_borrowedStringAroundStackParams(sBytes, sCount) { + TestModule["bjs_borrowedStringAroundStackParams"] = function bjs_borrowedStringAroundStackParams(sWord0, sWord1, sWord2) { try { - const string = decodeString(sBytes, sCount); + const string = decodeString(sWord0, sWord1, sWord2); const optValue = __bjs_codec_Optional_Array_Int.lift(); const arrayResult = __bjs_codec_Array_Int.lift(); let ret = imports.borrowedStringAroundStackParams(string, optValue, arrayResult); @@ -575,10 +577,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js index 363f6c595..497377caf 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ImportedTypeInExportedInterface.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -403,8 +405,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -412,8 +414,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -438,8 +440,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -514,11 +516,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -587,10 +589,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.js index e8fe5ec10..0156496e2 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/InvalidPropertyNames.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -296,9 +298,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WeirdNaming_normalProperty_set"] = function bjs_WeirdNaming_normalProperty_set(self, newValueBytes, newValueCount) { + TestModule["bjs_WeirdNaming_normalProperty_set"] = function bjs_WeirdNaming_normalProperty_set(self, newValueWord0, newValueWord1, newValueWord2) { try { - const string = decodeString(newValueBytes, newValueCount); + const string = decodeString(newValueWord0, newValueWord1, newValueWord2); swift.memory.getObject(self).normalProperty = string; } catch (error) { setException(error); @@ -318,9 +320,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WeirdNaming_property_with_spaces_set"] = function bjs_WeirdNaming_property_with_spaces_set(self, newValueBytes, newValueCount) { + TestModule["bjs_WeirdNaming_property_with_spaces_set"] = function bjs_WeirdNaming_property_with_spaces_set(self, newValueWord0, newValueWord1, newValueWord2) { try { - const string = decodeString(newValueBytes, newValueCount); + const string = decodeString(newValueWord0, newValueWord1, newValueWord2); swift.memory.getObject(self)["property with spaces"] = string; } catch (error) { setException(error); @@ -333,25 +335,25 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WeirdNaming_constructor_set"] = function bjs_WeirdNaming_constructor_set(self, newValueBytes, newValueCount) { + TestModule["bjs_WeirdNaming_constructor_set"] = function bjs_WeirdNaming_constructor_set(self, newValueWord0, newValueWord1, newValueWord2) { try { - const string = decodeString(newValueBytes, newValueCount); + const string = decodeString(newValueWord0, newValueWord1, newValueWord2); swift.memory.getObject(self).constructor = string; } catch (error) { setException(error); } } - TestModule["bjs_WeirdNaming_for_set"] = function bjs_WeirdNaming_for_set(self, newValueBytes, newValueCount) { + TestModule["bjs_WeirdNaming_for_set"] = function bjs_WeirdNaming_for_set(self, newValueWord0, newValueWord1, newValueWord2) { try { - const string = decodeString(newValueBytes, newValueCount); + const string = decodeString(newValueWord0, newValueWord1, newValueWord2); swift.memory.getObject(self).for = string; } catch (error) { setException(error); } } - TestModule["bjs_WeirdNaming_any_set"] = function bjs_WeirdNaming_any_set(self, newValueBytes, newValueCount) { + TestModule["bjs_WeirdNaming_any_set"] = function bjs_WeirdNaming_any_set(self, newValueWord0, newValueWord1, newValueWord2) { try { - const string = decodeString(newValueBytes, newValueCount); + const string = decodeString(newValueWord0, newValueWord1, newValueWord2); swift.memory.getObject(self).Any = string; } catch (error) { setException(error); @@ -388,10 +390,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.js index 15b48d4b9..b885c3ca8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClass.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -215,9 +217,9 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(nameBytes, nameCount) { + TestModule["bjs_Greeter_init"] = function bjs_Greeter_init(nameWord0, nameWord1, nameWord2) { try { - const string = decodeString(nameBytes, nameCount); + const string = decodeString(nameWord0, nameWord1, nameWord2); return swift.memory.retain(new imports.Greeter(string)); } catch (error) { setException(error); @@ -242,9 +244,9 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_Greeter_name_set"] = function bjs_Greeter_name_set(self, newValueBytes, newValueCount) { + TestModule["bjs_Greeter_name_set"] = function bjs_Greeter_name_set(self, newValueWord0, newValueWord1, newValueWord2) { try { - const string = decodeString(newValueBytes, newValueCount); + const string = decodeString(newValueWord0, newValueWord1, newValueWord2); swift.memory.getObject(self).name = string; } catch (error) { setException(error); @@ -259,9 +261,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_Greeter_changeName"] = function bjs_Greeter_changeName(self, nameBytes, nameCount) { + TestModule["bjs_Greeter_changeName"] = function bjs_Greeter_changeName(self, nameWord0, nameWord1, nameWord2) { try { - const string = decodeString(nameBytes, nameCount); + const string = decodeString(nameWord0, nameWord1, nameWord2); swift.memory.getObject(self).changeName(string); } catch (error) { setException(error); @@ -287,10 +289,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js index 5eaba3c8f..2bd7418a9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSClassStaticFunctions.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -270,10 +272,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.js index 6d1b1b6fb..b6f5d0a12 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModule.js @@ -15,6 +15,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -45,8 +47,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -54,8 +56,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -80,8 +82,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -148,11 +150,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -229,9 +231,9 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_nodeBasename"] = function bjs_nodeBasename(pathBytes, pathCount) { + TestModule["bjs_nodeBasename"] = function bjs_nodeBasename(pathWord0, pathWord1, pathWord2) { try { - const string = decodeString(pathBytes, pathCount); + const string = decodeString(pathWord0, pathWord1, pathWord2); let ret = __bjs_import_3_basename(string); tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; @@ -239,9 +241,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_nodeDirname"] = function bjs_nodeDirname(pathBytes, pathCount) { + TestModule["bjs_nodeDirname"] = function bjs_nodeDirname(pathWord0, pathWord1, pathWord2) { try { - const string = decodeString(pathBytes, pathCount); + const string = decodeString(pathWord0, pathWord1, pathWord2); let ret = __bjs_import_3_dirname(string); tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; @@ -295,10 +297,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.js index 9428698be..3b09b2ba0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportBareModuleFallback.js @@ -12,6 +12,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -42,8 +44,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -51,8 +53,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -77,8 +79,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -145,11 +147,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -226,10 +228,10 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_joinPaths"] = function bjs_joinPaths(lhsBytes, lhsCount, rhsBytes, rhsCount) { + TestModule["bjs_joinPaths"] = function bjs_joinPaths(lhsWord0, lhsWord1, lhsWord2, rhsWord0, rhsWord1, rhsWord2) { try { - const string = decodeString(lhsBytes, lhsCount); - const string1 = decodeString(rhsBytes, rhsCount); + const string = decodeString(lhsWord0, lhsWord1, lhsWord2); + const string1 = decodeString(rhsWord0, rhsWord1, rhsWord2); let ret = __bjs_import_0_join(string, string1); tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; @@ -239,10 +241,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.js index aacf5e61a..edc9f3281 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSImportModule.js @@ -12,6 +12,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -42,8 +44,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -51,8 +53,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -77,8 +79,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -145,11 +147,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -279,10 +281,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.js index 68ac11976..fe118a424 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameOverride.js @@ -14,6 +14,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -61,8 +63,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -70,8 +72,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -96,8 +98,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -172,11 +174,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -244,10 +246,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameTypes.js index fb061d342..520da96b8 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSNameTypes.js @@ -14,6 +14,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -44,8 +46,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -53,8 +55,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -79,8 +81,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -148,11 +150,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -220,10 +222,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.js index f02f6bcf2..d8c39e4ad 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSTypedArrayTypes.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -206,10 +208,180 @@ export async function createInstantiator(options, swift) { bjs["swift_js_closure_unregister"] = function(funcRef) {} }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js index 9fe12ff47..26a3d4574 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/JSValue.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -366,8 +368,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -375,8 +377,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -401,8 +403,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -469,11 +471,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -563,10 +565,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js index 5db38dde5..d078d252f 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedGlobal.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -214,10 +216,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js index 66818952b..85d6df975 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedModules.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -222,10 +224,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js index a732808a6..9c020acb1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/MixedPrivate.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -214,10 +216,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NamespacedClassSignature.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NamespacedClassSignature.js index 082d85f65..6614fae0e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NamespacedClassSignature.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NamespacedClassSignature.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -42,7 +44,7 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, state.file >>> 0); let length = 0; while (bytes[length] !== 0) { length += 1; } - const fileID = decodeString(state.file, length); + const fileID = decodeUTF8(state.file, length); throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); } return func(...args); @@ -64,8 +66,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -73,8 +75,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -99,8 +101,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -167,11 +169,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -266,10 +268,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js index ea49220de..d390ed3e0 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.Global.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -374,8 +376,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -383,8 +385,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -409,8 +411,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -477,11 +479,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -561,10 +563,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js index 25bd44d05..79fe1ae72 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Namespaces.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -374,8 +376,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -383,8 +385,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -409,8 +411,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -477,11 +479,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -561,10 +563,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js index f57c4007b..cf5379258 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedType.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -64,8 +66,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -73,8 +75,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -99,8 +101,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -182,11 +184,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -258,10 +260,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedTypeNameCollision.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedTypeNameCollision.js index 1f4400ce9..f2a47150e 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedTypeNameCollision.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/NestedTypeNameCollision.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -60,8 +62,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -69,8 +71,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -95,8 +97,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -178,11 +180,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -242,10 +244,180 @@ export async function createInstantiator(options, swift) { bjs["swift_js_closure_unregister"] = function(funcRef) {} }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js index b63f360e9..46f1d40d7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Optionals.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -390,8 +392,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -399,8 +401,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -425,8 +427,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -493,11 +495,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -568,18 +570,18 @@ export async function createInstantiator(options, swift) { return swift.memory.retain(obj); }; const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; - TestModule["bjs_WithOptionalJSClass_init"] = function bjs_WithOptionalJSClass_init(valueOrNullIsSome, valueOrNullBytes, valueOrNullCount, valueOrUndefinedIsSome, valueOrUndefinedBytes, valueOrUndefinedCount) { + TestModule["bjs_WithOptionalJSClass_init"] = function bjs_WithOptionalJSClass_init(valueOrNullIsSome, valueOrNullWord0, valueOrNullWord1, valueOrNullWord2, valueOrUndefinedIsSome, valueOrUndefinedWord0, valueOrUndefinedWord1, valueOrUndefinedWord2) { try { let optResult; if (valueOrNullIsSome) { - const string = decodeString(valueOrNullBytes, valueOrNullCount); + const string = decodeString(valueOrNullWord0, valueOrNullWord1, valueOrNullWord2); optResult = string; } else { optResult = null; } let optResult1; if (valueOrUndefinedIsSome) { - const string1 = decodeString(valueOrUndefinedBytes, valueOrUndefinedCount); + const string1 = decodeString(valueOrUndefinedWord0, valueOrUndefinedWord1, valueOrUndefinedWord2); optResult1 = string1; } else { optResult1 = undefined; @@ -670,11 +672,11 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WithOptionalJSClass_stringOrNull_set"] = function bjs_WithOptionalJSClass_stringOrNull_set(self, newValueIsSome, newValueBytes, newValueCount) { + TestModule["bjs_WithOptionalJSClass_stringOrNull_set"] = function bjs_WithOptionalJSClass_stringOrNull_set(self, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) { try { let optResult; if (newValueIsSome) { - const string = decodeString(newValueBytes, newValueCount); + const string = decodeString(newValueWord0, newValueWord1, newValueWord2); optResult = string; } else { optResult = null; @@ -684,11 +686,11 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WithOptionalJSClass_stringOrUndefined_set"] = function bjs_WithOptionalJSClass_stringOrUndefined_set(self, newValueIsSome, newValueBytes, newValueCount) { + TestModule["bjs_WithOptionalJSClass_stringOrUndefined_set"] = function bjs_WithOptionalJSClass_stringOrUndefined_set(self, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) { try { let optResult; if (newValueIsSome) { - const string = decodeString(newValueBytes, newValueCount); + const string = decodeString(newValueWord0, newValueWord1, newValueWord2); optResult = string; } else { optResult = undefined; @@ -747,11 +749,11 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WithOptionalJSClass_roundTripStringOrNull"] = function bjs_WithOptionalJSClass_roundTripStringOrNull(self, valueIsSome, valueBytes, valueCount) { + TestModule["bjs_WithOptionalJSClass_roundTripStringOrNull"] = function bjs_WithOptionalJSClass_roundTripStringOrNull(self, valueIsSome, valueWord0, valueWord1, valueWord2) { try { let optResult; if (valueIsSome) { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); optResult = string; } else { optResult = null; @@ -763,11 +765,11 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_WithOptionalJSClass_roundTripStringOrUndefined"] = function bjs_WithOptionalJSClass_roundTripStringOrUndefined(self, valueIsSome, valueBytes, valueCount) { + TestModule["bjs_WithOptionalJSClass_roundTripStringOrUndefined"] = function bjs_WithOptionalJSClass_roundTripStringOrUndefined(self, valueIsSome, valueWord0, valueWord1, valueWord2) { try { let optResult; if (valueIsSome) { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); optResult = string; } else { optResult = undefined; @@ -843,10 +845,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.js index d4480a65c..2e1aaffa3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveParameters.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -215,10 +217,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.js index e9b298fb2..d0a97f7a3 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PrimitiveReturn.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -226,10 +228,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js index beb9417df..f8abe0d0a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/PropertyTypes.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -214,10 +216,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js index f0c2d3fae..f6b5c4b38 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Protocol.js @@ -33,6 +33,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -434,8 +436,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -443,8 +445,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -469,8 +471,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -538,11 +540,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -650,11 +652,11 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_MyViewControllerDelegate_optionalName_set"] = function bjs_MyViewControllerDelegate_optionalName_set(self, valueIsSome, valueBytes, valueCount) { + TestModule["bjs_MyViewControllerDelegate_optionalName_set"] = function bjs_MyViewControllerDelegate_optionalName_set(self, valueIsSome, valueWord0, valueWord1, valueWord2) { try { let optResult; if (valueIsSome) { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); optResult = string; } else { optResult = null; @@ -672,11 +674,11 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_MyViewControllerDelegate_optionalRawEnum_set"] = function bjs_MyViewControllerDelegate_optionalRawEnum_set(self, valueIsSome, valueBytes, valueCount) { + TestModule["bjs_MyViewControllerDelegate_optionalRawEnum_set"] = function bjs_MyViewControllerDelegate_optionalRawEnum_set(self, valueIsSome, valueWord0, valueWord1, valueWord2) { try { let optResult; if (valueIsSome) { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); optResult = string; } else { optResult = null; @@ -695,9 +697,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_MyViewControllerDelegate_rawStringEnum_set"] = function bjs_MyViewControllerDelegate_rawStringEnum_set(self, valueBytes, valueCount) { + TestModule["bjs_MyViewControllerDelegate_rawStringEnum_set"] = function bjs_MyViewControllerDelegate_rawStringEnum_set(self, valueWord0, valueWord1, valueWord2) { try { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); swift.memory.getObject(self).rawStringEnum = string; } catch (error) { setException(error); @@ -818,9 +820,9 @@ export async function createInstantiator(options, swift) { setException(error); } } - TestModule["bjs_MyViewControllerDelegate_onValueChanged"] = function bjs_MyViewControllerDelegate_onValueChanged(self, valueBytes, valueCount) { + TestModule["bjs_MyViewControllerDelegate_onValueChanged"] = function bjs_MyViewControllerDelegate_onValueChanged(self, valueWord0, valueWord1, valueWord2) { try { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); swift.memory.getObject(self).onValueChanged(string); } catch (error) { setException(error); @@ -835,10 +837,10 @@ export async function createInstantiator(options, swift) { return 0 } } - TestModule["bjs_MyViewControllerDelegate_onLabelUpdated"] = function bjs_MyViewControllerDelegate_onLabelUpdated(self, prefixBytes, prefixCount, suffixBytes, suffixCount) { + TestModule["bjs_MyViewControllerDelegate_onLabelUpdated"] = function bjs_MyViewControllerDelegate_onLabelUpdated(self, prefixWord0, prefixWord1, prefixWord2, suffixWord0, suffixWord1, suffixWord2) { try { - const string = decodeString(prefixBytes, prefixCount); - const string1 = decodeString(suffixBytes, suffixCount); + const string = decodeString(prefixWord0, prefixWord1, prefixWord2); + const string1 = decodeString(suffixWord0, suffixWord1, suffixWord2); swift.memory.getObject(self).onLabelUpdated(string, string1); } catch (error) { setException(error); @@ -913,10 +915,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js index eed26c581..b2ceaf5be 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/ProtocolInClosure.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -42,7 +44,7 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, state.file >>> 0); let length = 0; while (bytes[length] !== 0) { length += 1; } - const fileID = decodeString(state.file, length); + const fileID = decodeUTF8(state.file, length); throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); } return func(...args); @@ -64,8 +66,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -73,8 +75,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -99,8 +101,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -167,11 +169,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -360,10 +362,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/RelativeQualifiedTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/RelativeQualifiedTypes.js index 2765d709f..37f9aee78 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/RelativeQualifiedTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/RelativeQualifiedTypes.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -82,8 +84,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -91,8 +93,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -117,8 +119,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -221,11 +223,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -297,10 +299,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js index f46f42d10..e23e7a885 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.Global.js @@ -20,6 +20,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -83,8 +85,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -92,8 +94,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -118,8 +120,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -187,11 +189,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -259,10 +261,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js index 37d3415a4..869d297b7 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticFunctions.js @@ -20,6 +20,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -83,8 +85,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -92,8 +94,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -118,8 +120,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -187,11 +189,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -259,10 +261,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js index e2a9093b6..895864e0a 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.Global.js @@ -14,6 +14,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -44,8 +46,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -53,8 +55,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -79,8 +81,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -148,11 +150,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -220,10 +222,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js index f442745e5..7ec51867d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StaticProperties.js @@ -14,6 +14,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -44,8 +46,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -53,8 +55,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -79,8 +81,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -148,11 +150,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -220,10 +222,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.js index ebddbefd2..4339f3395 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringParameter.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -206,17 +208,17 @@ export async function createInstantiator(options, swift) { } bjs["swift_js_closure_unregister"] = function(funcRef) {} const TestModule = importObject["TestModule"] = importObject["TestModule"] || {}; - TestModule["bjs_checkString"] = function bjs_checkString(aBytes, aCount) { + TestModule["bjs_checkString"] = function bjs_checkString(aWord0, aWord1, aWord2) { try { - const string = decodeString(aBytes, aCount); + const string = decodeString(aWord0, aWord1, aWord2); imports.checkString(string); } catch (error) { setException(error); } } - TestModule["bjs_checkStringWithLength"] = function bjs_checkStringWithLength(aBytes, aCount, b) { + TestModule["bjs_checkStringWithLength"] = function bjs_checkStringWithLength(aWord0, aWord1, aWord2, b) { try { - const string = decodeString(aBytes, aCount); + const string = decodeString(aWord0, aWord1, aWord2); imports.checkStringWithLength(string, b); } catch (error) { setException(error); @@ -224,10 +226,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.js index d2dfa204f..f8ccb25a9 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StringReturn.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -217,10 +219,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.js index 9104e2084..70146e4bd 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/StructWithNestedTypes.js @@ -24,6 +24,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -98,8 +100,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -107,8 +109,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -133,8 +135,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -230,11 +232,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -294,10 +296,180 @@ export async function createInstantiator(options, swift) { bjs["swift_js_closure_unregister"] = function(funcRef) {} }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js index 92ef435fb..45b693895 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClass.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -242,10 +244,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js index 279a9d3c6..a129f7b8b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosure.js @@ -39,6 +39,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -395,7 +397,7 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, state.file >>> 0); let length = 0; while (bytes[length] !== 0) { length += 1; } - const fileID = decodeString(state.file, length); + const fileID = decodeUTF8(state.file, length); throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); } return func(...args); @@ -501,8 +503,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -510,8 +512,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -536,8 +538,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -584,9 +586,9 @@ export async function createInstantiator(options, swift) { promise[__bjs_promiseSettlers] = { resolve, reject }; return swift.memory.retain(promise); } - bjs["promise_resolve_TestModule_SS"] = function(promise, valueBytes, valueCount) { + bjs["promise_resolve_TestModule_SS"] = function(promise, valueWord0, valueWord1, valueWord2) { try { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(string); } catch (error) { setException(error); @@ -644,11 +646,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -733,10 +735,10 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModule10HttpStatusO_10HttpStatusO); } - bjs["invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO"] = function(callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModule5ThemeO_5ThemeO"] = function(callbackId, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); let ret = callback(string); tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; @@ -858,10 +860,10 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModule9DirectionO_9DirectionO); } - bjs["invoke_js_callback_TestModule_10TestModuleKSS_Sb"] = function(callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleKSS_Sb"] = function(callbackId, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); let ret = callback(string); return ret ? 1 : 0; } catch (error) { @@ -884,10 +886,10 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleKSS_Sb); } - bjs["invoke_js_callback_TestModule_10TestModuleKSS_Si"] = function(callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleKSS_Si"] = function(callbackId, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); let ret = callback(string); return ret; } catch (error) { @@ -910,10 +912,10 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleKSS_Si); } - bjs["invoke_js_callback_TestModule_10TestModuleSS_SS"] = function(callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleSS_SS"] = function(callbackId, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); let ret = callback(string); tmpRetBytes = textEncoder.encode(ret); return tmpRetBytes.length; @@ -1056,12 +1058,12 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSq10HttpStatusO_Sq10HttpStatusO); } - bjs["invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO"] = function(callbackId, param0IsSome, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleSq5ThemeO_Sq5ThemeO"] = function(callbackId, param0IsSome, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); let optResult; if (param0IsSome) { - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); optResult = string; } else { optResult = null; @@ -1228,12 +1230,12 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSq9DirectionO_Sq9DirectionO); } - bjs["invoke_js_callback_TestModule_10TestModuleSqSS_SqSS"] = function(callbackId, param0IsSome, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleSqSS_SqSS"] = function(callbackId, param0IsSome, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); let optResult; if (param0IsSome) { - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); optResult = string; } else { optResult = null; @@ -1375,11 +1377,11 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSqSi_SqSi); } - bjs["invoke_js_callback_TestModule_10TestModuleYaKSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleYaKSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Word0, param0Word1, param0Word2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); callback(string).then(resolve, reject); } bjs["make_swift_closure_TestModule_10TestModuleYaKSS_SS"] = function(boxPtr, file, line) { @@ -1420,11 +1422,11 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleYaKSb_9APIResultO); } - bjs["invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV"] = function(resolveRef, rejectRef, callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleYaSS_6AnimalV"] = function(resolveRef, rejectRef, callbackId, param0Word0, param0Word1, param0Word2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); callback(string).then(resolve, reject); } bjs["make_swift_closure_TestModule_10TestModuleYaSS_6AnimalV"] = function(boxPtr, file, line) { @@ -1438,11 +1440,11 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleYaSS_6AnimalV); } - bjs["invoke_js_callback_TestModule_10TestModuleYaSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleYaSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Word0, param0Word1, param0Word2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); callback(string).then(resolve, reject); } bjs["make_swift_closure_TestModule_10TestModuleYaSS_SS"] = function(boxPtr, file, line) { @@ -1522,10 +1524,10 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModules9APIResultO_y); } - bjs["invoke_js_callback_TestModule_10TestModulesSS_y"] = function(callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModulesSS_y"] = function(callbackId, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); callback(string); } catch (error) { setException(error); @@ -1559,10 +1561,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js index 96c78dd22..8768e4ee1 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftClosureImports.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -131,7 +133,7 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, state.file >>> 0); let length = 0; while (bytes[length] !== 0) { length += 1; } - const fileID = decodeString(state.file, length); + const fileID = decodeUTF8(state.file, length); throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); } return func(...args); @@ -154,8 +156,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -163,8 +165,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -189,8 +191,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -229,9 +231,9 @@ export async function createInstantiator(options, swift) { promise[__bjs_promiseSettlers] = { resolve, reject }; return swift.memory.retain(promise); } - bjs["promise_resolve_TestModule_SS"] = function(promise, valueBytes, valueCount) { + bjs["promise_resolve_TestModule_SS"] = function(promise, valueWord0, valueWord1, valueWord2) { try { - const string = decodeString(valueBytes, valueCount); + const string = decodeString(valueWord0, valueWord1, valueWord2); swift.memory.getObject(promise)[__bjs_promiseSettlers].resolve(string); } catch (error) { setException(error); @@ -273,11 +275,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -339,10 +341,10 @@ export async function createInstantiator(options, swift) { const func = swift.memory.getObject(funcRef); func.__unregister(); } - bjs["invoke_js_callback_TestModule_10TestModuleKSS_Sb"] = function(callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleKSS_Sb"] = function(callbackId, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); let ret = callback(string); return ret ? 1 : 0; } catch (error) { @@ -388,11 +390,11 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModuleSi_Si); } - bjs["invoke_js_callback_TestModule_10TestModuleYaKSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModuleYaKSS_SS"] = function(resolveRef, rejectRef, callbackId, param0Word0, param0Word1, param0Word2) { const resolve = swift.memory.getObject(resolveRef); const reject = swift.memory.getObject(rejectRef); const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); callback(string).then(resolve, reject); } bjs["make_swift_closure_TestModule_10TestModuleYaKSS_SS"] = function(boxPtr, file, line) { @@ -434,10 +436,10 @@ export async function createInstantiator(options, swift) { }; return makeClosure(boxPtr, file, line, lower_closure_TestModule_10TestModules7JSValueV_y); } - bjs["invoke_js_callback_TestModule_10TestModulesSS_y"] = function(callbackId, param0Bytes, param0Count) { + bjs["invoke_js_callback_TestModule_10TestModulesSS_y"] = function(callbackId, param0Word0, param0Word1, param0Word2) { try { const callback = swift.memory.getObject(callbackId); - const string = decodeString(param0Bytes, param0Count); + const string = decodeString(param0Word0, param0Word1, param0Word2); callback(string); } catch (error) { setException(error); @@ -481,10 +483,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js index 0bfd7c2e2..d04bc0fbb 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStruct.js @@ -14,6 +14,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -544,8 +546,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -553,8 +555,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -579,8 +581,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -704,11 +706,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -776,10 +778,180 @@ export async function createInstantiator(options, swift) { }; }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js index 134d7c28e..9ef137a8b 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftStructImports.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -385,8 +387,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -394,8 +396,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -420,8 +422,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -496,11 +498,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -579,10 +581,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.js index 500a005a3..1c8fcc597 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/SwiftTypedClosureAccess.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -42,7 +44,7 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, state.file >>> 0); let length = 0; while (bytes[length] !== 0) { length += 1; } - const fileID = decodeString(state.file, length); + const fileID = decodeUTF8(state.file, length); throw new Error(`Attempted to call a released JSTypedClosure created at ${fileID}:${state.line}`); } return func(...args); @@ -64,8 +66,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -73,8 +75,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -99,8 +101,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -167,11 +169,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -324,10 +326,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.js index 58ff6a85a..98a4ea18d 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/Throws.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -39,8 +41,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -48,8 +50,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -74,8 +76,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -142,11 +144,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -206,10 +208,180 @@ export async function createInstantiator(options, swift) { bjs["swift_js_closure_unregister"] = function(funcRef) {} }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.js index 416e5c281..50d4ba990 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/UnsafePointer.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -56,8 +58,8 @@ export async function createInstantiator(options, swift) { addImports: (importObject, importsContext) => { bjs = {}; importObject["bjs"] = bjs; - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -65,8 +67,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -91,8 +93,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -167,11 +169,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -231,10 +233,180 @@ export async function createInstantiator(options, swift) { bjs["swift_js_closure_unregister"] = function(funcRef) {} }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.js b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.js index 81b09eaf4..a5753441c 100644 --- a/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.js +++ b/Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/BridgeJSLinkTests/VoidParameterVoidReturn.js @@ -9,6 +9,8 @@ export async function createInstantiator(options, swift) { let memory; let setException; let decodeString; + let decodeUTF8; + const immortalStrings = new Map(); const textDecoder = new TextDecoder("utf-8"); const textEncoder = new TextEncoder("utf-8"); let tmpRetString; @@ -40,8 +42,8 @@ export async function createInstantiator(options, swift) { bjs = {}; importObject["bjs"] = bjs; const imports = options.getImports(importsContext); - bjs["swift_js_return_string"] = function(ptr, len) { - tmpRetString = decodeString(ptr, len); + bjs["swift_js_return_string"] = function(word0, word1, word2) { + tmpRetString = decodeString(word0, word1, word2); } bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) { const source = swift.memory.getObject(sourceId); @@ -49,8 +51,8 @@ export async function createInstantiator(options, swift) { const bytes = new Uint8Array(memory.buffer, bytesPtr >>> 0); bytes.set(source); } - bjs["swift_js_make_js_string"] = function(ptr, len) { - return swift.memory.retain(decodeString(ptr, len)); + bjs["swift_js_make_js_string"] = function(word0, word1, word2) { + return swift.memory.retain(decodeString(word0, word1, word2)); } bjs["swift_js_init_memory_with_result"] = function(ptr, len) { const target = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); @@ -75,8 +77,8 @@ export async function createInstantiator(options, swift) { bjs["swift_js_push_f64"] = function(v) { f64Stack.push(v); } - bjs["swift_js_push_string"] = function(ptr, len) { - const value = decodeString(ptr, len); + bjs["swift_js_push_string"] = function(word0, word1, word2) { + const value = decodeString(word0, word1, word2); strStack.push(value); } bjs["swift_js_pop_i32"] = function() { @@ -143,11 +145,11 @@ export async function createInstantiator(options, swift) { tmpRetOptionalDouble = value; } } - bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) { + bjs["swift_js_return_optional_string"] = function(isSome, word0, word1, word2) { if (isSome === 0) { tmpRetString = null; } else { - tmpRetString = decodeString(ptr, len); + tmpRetString = decodeString(word0, word1, word2); } } bjs["swift_js_return_optional_object"] = function(isSome, objectId) { @@ -215,10 +217,180 @@ export async function createInstantiator(options, swift) { } }, setInstance: (i) => { + if (memory !== i.exports.memory) immortalStrings.clear(); instance = i; memory = instance.exports.memory; - decodeString = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeUTF8 = (ptr, len) => { const bytes = new Uint8Array(memory.buffer, ptr >>> 0, len >>> 0); return textDecoder.decode(bytes); } + decodeString = (() => { + const byteAt = (word0, word1, word2, byteIndex) => + byteIndex < 4 + ? (word0 >>> (byteIndex * 8)) & 255 + : byteIndex < 8 + ? (word1 >>> ((byteIndex - 4) * 8)) & 255 + : byteIndex === 8 + ? word2 & 255 + : (word2 >>> 16) & 255; + // Valid small Swift strings need at most ten UTF-16 code units. + const decodeSmallUTF8 = (word0, word1, word2, count) => { + let byteIndex = 0, + unitCount = 0, + u0 = 0, + u1 = 0, + u2 = 0, + u3 = 0, + u4 = 0, + u5 = 0, + u6 = 0, + u7 = 0, + u8 = 0, + u9 = 0; + while (byteIndex < count) { + const start = byteIndex; + const b0 = byteAt(word0, word1, word2, byteIndex++); + let codePoint; + if (b0 < 128) codePoint = b0; + else if (b0 < 224) codePoint = ((b0 & 31) << 6) | (byteAt(word0, word1, word2, byteIndex++) & 63); + else if (b0 < 240) { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 15) << 12) | ((b1 & 63) << 6) | (b2 & 63); + } else { + const b1 = byteAt(word0, word1, word2, byteIndex++), + b2 = byteAt(word0, word1, word2, byteIndex++), + b3 = byteAt(word0, word1, word2, byteIndex++); + codePoint = ((b0 & 7) << 18) | ((b1 & 63) << 12) | ((b2 & 63) << 6) | (b3 & 63); + } + // Match TextDecoder: consume exactly one leading UTF-8 BOM. + if (start === 0 && codePoint === 0xfeff) continue; + const supplementary = codePoint > 0xffff; + const codeUnit = supplementary + ? 0xd800 + ((codePoint - 0x10000) >>> 10) + : codePoint; + switch (unitCount++) { + case 0: + u0 = codeUnit; + break; + case 1: + u1 = codeUnit; + break; + case 2: + u2 = codeUnit; + break; + case 3: + u3 = codeUnit; + break; + case 4: + u4 = codeUnit; + break; + case 5: + u5 = codeUnit; + break; + case 6: + u6 = codeUnit; + break; + case 7: + u7 = codeUnit; + break; + case 8: + u8 = codeUnit; + break; + case 9: + u9 = codeUnit; + break; + } + if (supplementary) { + const lowSurrogate = 0xdc00 + ((codePoint - 0x10000) & 1023); + switch (unitCount++) { + case 0: + u0 = lowSurrogate; + break; + case 1: + u1 = lowSurrogate; + break; + case 2: + u2 = lowSurrogate; + break; + case 3: + u3 = lowSurrogate; + break; + case 4: + u4 = lowSurrogate; + break; + case 5: + u5 = lowSurrogate; + break; + case 6: + u6 = lowSurrogate; + break; + case 7: + u7 = lowSurrogate; + break; + case 8: + u8 = lowSurrogate; + break; + case 9: + u9 = lowSurrogate; + break; + } + } + } + switch (unitCount) { + case 0: + return ""; + case 1: + return String.fromCharCode(u0); + case 2: + return String.fromCharCode(u0, u1); + case 3: + return String.fromCharCode(u0, u1, u2); + case 4: + return String.fromCharCode(u0, u1, u2, u3); + case 5: + return String.fromCharCode(u0, u1, u2, u3, u4); + case 6: + return String.fromCharCode(u0, u1, u2, u3, u4, u5); + case 7: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6); + case 8: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7); + case 9: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8); + case 10: + return String.fromCharCode(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9); + } + }; + const decodeSmall = (word0, word1, word2) => { + const count = (word2 >>> 8) & 0x0f; + if ((word2 & 0x4000 /* ASCII */) === 0) return decodeSmallUTF8(word0, word1, word2, count); + const char = String.fromCharCode; + switch (count) { + case 0: return ""; + case 1: return char(word0 & 0xff); + case 2: return char(word0 & 0xff, (word0 >>> 8) & 0xff); + case 3: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff); + case 4: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24); + case 5: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff); + case 6: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff); + case 7: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff); + case 8: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24); + case 9: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff); + case 10: return char(word0 & 0xff, (word0 >>> 8) & 0xff, (word0 >>> 16) & 0xff, word0 >>> 24, word1 & 0xff, (word1 >>> 8) & 0xff, (word1 >>> 16) & 0xff, word1 >>> 24, word2 & 0xff, (word2 >>> 16) & 0xff); + } + }; + return (word0, word1, word2) => { + if (word2 & 0x2000 /* small */) return decodeSmall(word0, word1, word2); + const ptr = (word1 + 20 /* nativeBias */) >>> 0; + const len = word0 >>> 0; + if ((word2 & 0x8000 /* immortal */) === 0 || len >= 0x200000 /* 2^21 Number key */) return decodeUTF8(ptr, len); + const key = ptr + len * 0x100000000; + const cached = immortalStrings.get(key); + if (cached !== undefined) return cached; + const value = decodeUTF8(ptr, len); + immortalStrings.set(key, value); + return value; + }; + })(); setException = (error) => { instance.exports._swift_js_exception.value = swift.memory.retain(error) diff --git a/Sources/JavaScriptKit/BridgeJSIntrinsics.swift b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift index 6946d6a59..dd5e1e970 100644 --- a/Sources/JavaScriptKit/BridgeJSIntrinsics.swift +++ b/Sources/JavaScriptKit/BridgeJSIntrinsics.swift @@ -452,7 +452,7 @@ extension _BridgedSwiftAlias where JSRepresentation: _BridgedSwiftAssociatedValu extension _BridgedSwiftAlias where JSRepresentation == String { // MARK: ImportTS - @_spi(BridgeJS) public consuming func bridgeJSWithLoweredParameter(_ body: (Int32, Int32) -> T) -> T { + @_spi(BridgeJS) public consuming func bridgeJSWithLoweredParameter(_ body: (Int32, Int32, Int32) -> T) -> T { bridgeToJS().bridgeJSWithLoweredParameter(body) } @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ bytesCount: Int32) -> Self { @@ -476,7 +476,7 @@ extension _BridgedSwiftAlias where JSRepresentation == String { extension _BridgedAsOptional where Wrapped: _BridgedSwiftAlias, Wrapped.JSRepresentation == String { @_spi(BridgeJS) public consuming func bridgeJSWithLoweredParameter( - _ body: (Int32, Int32, Int32) -> T + _ body: (Int32, Int32, Int32, Int32) -> T ) -> T { asOptional.map { $0.bridgeToJS() }.bridgeJSWithLoweredParameter(body) } @@ -798,12 +798,29 @@ extension Double: _BridgedSwiftTypeLoweredIntoSingleWasmCoreType, _BridgedSwiftS extension String: _BridgedSwiftStackType { public typealias StackLiftResult = String + /// Lends the three wasm32 `String` words to `body` for the duration of a synchronous JS call. + /// + /// The 12-byte layout is a Swift stdlib implementation detail. JS interprets small-string + /// flags in `word2` and, for large strings, UTF-8 at `word1 + 20` (`nativeBias` on 32-bit). + @inline(__always) + fileprivate consuming func withBridgeJSRawWords(_ body: (Int32, Int32, Int32) -> Result) -> Result { + #if arch(wasm32) + return withUnsafeBytes(of: self) { bytes in + precondition(bytes.count == 12, "BridgeJS expects the 12-byte wasm32 String layout") + let word0 = bytes.load(fromByteOffset: 0, as: Int32.self) + let word1 = bytes.load(fromByteOffset: 4, as: Int32.self) + let word2 = bytes.load(fromByteOffset: 8, as: Int32.self) + return body(word0, word1, word2) + } + #else + _onlyAvailableOnWasm() + #endif + } + // MARK: ImportTS - @_spi(BridgeJS) public consuming func bridgeJSWithLoweredParameter(_ body: (Int32, Int32) -> T) -> T { - return self.withUTF8 { b in - body(Int32(bitPattern: UInt32(UInt(bitPattern: b.baseAddress))), Int32(b.count)) - } + @_spi(BridgeJS) public consuming func bridgeJSWithLoweredParameter(_ body: (Int32, Int32, Int32) -> T) -> T { + withBridgeJSRawWords(body) } @_spi(BridgeJS) public static func bridgeJSLiftReturn(_ bytesCount: Int32) -> String { @@ -835,14 +852,14 @@ extension String: _BridgedSwiftStackType { } @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { - return self.withUTF8 { ptr in - _swift_js_return_string(ptr.baseAddress, Int32(ptr.count)) + withBridgeJSRawWords { word0, word1, word2 in + _swift_js_return_string(word0, word1, word2) } } @_spi(BridgeJS) public consuming func bridgeJSStackPush() { - self.withUTF8 { ptr in - _swift_js_push_string(ptr.baseAddress, Int32(ptr.count)) + withBridgeJSRawWords { word0, word1, word2 in + _swift_js_push_string(word0, word1, word2) } } } @@ -1290,7 +1307,7 @@ extension _BridgedSwiftStruct { extension _BridgedSwiftEnumNoPayload where Self: RawRepresentable, RawValue == String { // MARK: ImportTS - @_spi(BridgeJS) public consuming func bridgeJSWithLoweredParameter(_ body: (Int32, Int32) -> T) -> T { + @_spi(BridgeJS) public consuming func bridgeJSWithLoweredParameter(_ body: (Int32, Int32, Int32) -> T) -> T { rawValue.bridgeJSWithLoweredParameter(body) } @@ -1348,15 +1365,15 @@ private func _swift_js_init_memory_extern(_ sourceId: Int32, _ ptr: UnsafeMutabl #if arch(wasm32) @_extern(wasm, module: "bjs", name: "swift_js_push_string") -private func _swift_js_push_string_extern(_ ptr: UnsafePointer?, _ len: Int32) +private func _swift_js_push_string_extern(_ word0: Int32, _ word1: Int32, _ word2: Int32) #else -private func _swift_js_push_string_extern(_ ptr: UnsafePointer?, _ len: Int32) { +private func _swift_js_push_string_extern(_ word0: Int32, _ word1: Int32, _ word2: Int32) { _onlyAvailableOnWasm() } #endif -@_spi(BridgeJS) @inline(never) public func _swift_js_push_string(_ ptr: UnsafePointer?, _ len: Int32) { - _swift_js_push_string_extern(ptr, len) +@_spi(BridgeJS) @inline(never) public func _swift_js_push_string(_ word0: Int32, _ word1: Int32, _ word2: Int32) { + _swift_js_push_string_extern(word0, word1, word2) } #if arch(wasm32) @@ -1542,15 +1559,15 @@ extension UInt: _BridgedNumericArray { #if arch(wasm32) @_extern(wasm, module: "bjs", name: "swift_js_make_js_string") -private func _swift_js_make_js_string_extern(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 +private func _swift_js_make_js_string_extern(_ word0: Int32, _ word1: Int32, _ word2: Int32) -> Int32 #else -/// Creates a JavaScript string from UTF-8 data in WebAssembly memory -private func _swift_js_make_js_string_extern(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 { +/// Creates a JavaScript string from the wasm32 Swift `String` layout words +private func _swift_js_make_js_string_extern(_ word0: Int32, _ word1: Int32, _ word2: Int32) -> Int32 { _onlyAvailableOnWasm() } #endif -@inline(never) func _swift_js_make_js_string(_ ptr: UnsafePointer?, _ len: Int32) -> Int32 { - _swift_js_make_js_string_extern(ptr, len) +@inline(never) func _swift_js_make_js_string(_ word0: Int32, _ word1: Int32, _ word2: Int32) -> Int32 { + _swift_js_make_js_string_extern(word0, word1, word2) } #if arch(wasm32) @@ -1568,15 +1585,15 @@ private func _swift_js_init_memory_with_result_extern(_ ptr: UnsafePointer?, _ len: Int32) +private func _swift_js_return_string_extern(_ word0: Int32, _ word1: Int32, _ word2: Int32) #else /// Write a string to reserved string storage to be returned to JavaScript -private func _swift_js_return_string_extern(_ ptr: UnsafePointer?, _ len: Int32) { +private func _swift_js_return_string_extern(_ word0: Int32, _ word1: Int32, _ word2: Int32) { _onlyAvailableOnWasm() } #endif -@inline(never) func _swift_js_return_string(_ ptr: UnsafePointer?, _ len: Int32) { - _swift_js_return_string_extern(ptr, len) +@inline(never) func _swift_js_return_string(_ word0: Int32, _ word1: Int32, _ word2: Int32) { + _swift_js_return_string_extern(word0, word1, word2) } #if arch(wasm32) @@ -1657,15 +1674,15 @@ private func _swift_js_get_optional_string_extern() -> Int32 { #if arch(wasm32) @_extern(wasm, module: "bjs", name: "swift_js_return_optional_string") -private func _swift_js_return_optional_string_extern(_ isSome: Int32, _ ptr: UnsafePointer?, _ len: Int32) +private func _swift_js_return_optional_string_extern(_ isSome: Int32, _ word0: Int32, _ word1: Int32, _ word2: Int32) #else /// Write an optional string to reserved string storage to be returned to JavaScript -private func _swift_js_return_optional_string_extern(_ isSome: Int32, _ ptr: UnsafePointer?, _ len: Int32) { +private func _swift_js_return_optional_string_extern(_ isSome: Int32, _ word0: Int32, _ word1: Int32, _ word2: Int32) { _onlyAvailableOnWasm() } #endif -@inline(never) func _swift_js_return_optional_string(_ isSome: Int32, _ ptr: UnsafePointer?, _ len: Int32) { - _swift_js_return_optional_string_extern(isSome, ptr, len) +@inline(never) func _swift_js_return_optional_string(_ isSome: Int32, _ word0: Int32, _ word1: Int32, _ word2: Int32) { + _swift_js_return_optional_string_extern(isSome, word0, word1, word2) } #if arch(wasm32) @@ -2156,14 +2173,14 @@ extension _BridgedAsOptional where Wrapped == Bool { extension _BridgedAsOptional where Wrapped == String { @_spi(BridgeJS) @_transparent public consuming func bridgeJSWithLoweredParameter( - _ body: (Int32, Int32, Int32) -> T + _ body: (Int32, Int32, Int32, Int32) -> T ) -> T { switch asOptional { case .none: - return body(0, 0, 0) + return body(0, 0, 0, 0) case .some(let value): - return value.bridgeJSWithLoweredParameter { bytes, count in - return body(1, bytes, count) + return value.bridgeJSWithLoweredParameter { word0, word1, word2 in + return body(1, word0, word1, word2) } } } @@ -2194,10 +2211,10 @@ extension _BridgedAsOptional where Wrapped == String { @_spi(BridgeJS) public consuming func bridgeJSLowerReturn() -> Void { switch asOptional { case .none: - _swift_js_return_optional_string(0, nil, 0) - case .some(var value): - value.withUTF8 { ptr in - _swift_js_return_optional_string(1, ptr.baseAddress, Int32(ptr.count)) + _swift_js_return_optional_string(0, 0, 0, 0) + case .some(let value): + value.withBridgeJSRawWords { word0, word1, word2 in + _swift_js_return_optional_string(1, word0, word1, word2) } } } @@ -2358,14 +2375,14 @@ extension _BridgedAsOptional where Wrapped: _BridgedSwiftCaseEnum { extension _BridgedAsOptional where Wrapped: _BridgedSwiftEnumNoPayload, Wrapped: RawRepresentable, Wrapped.RawValue == String { @_spi(BridgeJS) @_transparent public consuming func bridgeJSWithLoweredParameter( - _ body: (Int32, Int32, Int32) -> T + _ body: (Int32, Int32, Int32, Int32) -> T ) -> T { switch asOptional { case .none: - return body(0, 0, 0) + return body(0, 0, 0, 0) case .some(let wrapped): - return wrapped.bridgeJSWithLoweredParameter { bytes, count in - return body(1, bytes, count) + return wrapped.bridgeJSWithLoweredParameter { word0, word1, word2 in + return body(1, word0, word1, word2) } } } diff --git a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift index acb3e23f1..52799fa7c 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift +++ b/Tests/BridgeJSRuntimeTests/Generated/BridgeJS.swift @@ -202,14 +202,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTests13D #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -229,9 +229,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTests5ThemeO_SS { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_SS(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -269,14 +269,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5Th #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -296,9 +296,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTests5ThemeO_Sb { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTests5ThemeO_Sb(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -846,14 +846,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsAl7 #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -873,9 +873,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsKSS_Sb { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] (param0: String) throws(JSException) -> Bool in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Sb(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -930,14 +930,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -957,9 +957,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsKSS_Si { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] (param0: String) throws(JSException) -> Int in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS_Si(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -1014,14 +1014,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsKSS #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> UnsafeMutableRawPointer +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> UnsafeMutableRawPointer #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> UnsafeMutableRawPointer { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> UnsafeMutableRawPointer { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> UnsafeMutableRawPointer { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> UnsafeMutableRawPointer { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -1041,9 +1041,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsSS_7GreeterC { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_7GreeterC(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -1081,14 +1081,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_ #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -1108,9 +1108,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsSS_SS { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS(callbackValue, param0Bytes, param0Length) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSS_SS(callbackValue, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -1726,14 +1726,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSd_ #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS_extern(_ callback: Int32, _ param0: Int32, _ param1Bytes: Int32, _ param1Length: Int32, _ param2: Float64) -> Int32 +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS_extern(_ callback: Int32, _ param0: Int32, _ param1Word0: Int32, _ param1Word1: Int32, _ param1Word2: Int32, _ param2: Float64) -> Int32 #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS_extern(_ callback: Int32, _ param0: Int32, _ param1Bytes: Int32, _ param1Length: Int32, _ param2: Float64) -> Int32 { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS_extern(_ callback: Int32, _ param0: Int32, _ param1Word0: Int32, _ param1Word1: Int32, _ param1Word2: Int32, _ param2: Float64) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS(_ callback: Int32, _ param0: Int32, _ param1Bytes: Int32, _ param1Length: Int32, _ param2: Float64) -> Int32 { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS_extern(callback, param0, param1Bytes, param1Length, param2) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS(_ callback: Int32, _ param0: Int32, _ param1Word0: Int32, _ param1Word1: Int32, _ param1Word2: Int32, _ param2: Float64) -> Int32 { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS_extern(callback, param0, param1Word0, param1Word1, param1Word2, param2) } #if arch(wasm32) @@ -1753,11 +1753,11 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsSiSSSd_SS { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] (param0, param1, param2) in #if arch(wasm32) - let ret0 = param1.bridgeJSWithLoweredParameter { (param1Bytes, param1Length) in + let ret0 = param1.bridgeJSWithLoweredParameter { (param1Word0, param1Word1, param1Word2) in let param2Value = param2.bridgeJSLowerParameter() let param0Value = param0.bridgeJSLowerParameter() let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS(callbackValue, param0Value, param1Bytes, param1Length, param2Value) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSiSSSd_SS(callbackValue, param0Value, param1Word0, param1Word1, param1Word2, param2Value) return ret } let ret = ret0 @@ -2126,14 +2126,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq1 #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS_extern(callback, param0IsSome, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS_extern(callback, param0IsSome, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -2153,9 +2153,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsSq5ThemeO_SS { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS(callbackValue, param0IsSome, param0Bytes, param0Length) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq5ThemeO_SS(callbackValue, param0IsSome, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -2449,14 +2449,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSq9 #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Int32 { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS_extern(callback, param0IsSome, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Int32 { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS_extern(callback, param0IsSome, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -2476,9 +2476,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsSqSS_SS { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - let ret0 = param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Bytes, param0Length) in + let ret0 = param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS(callbackValue, param0IsSome, param0Bytes, param0Length) + let ret = invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqSS_SS(callbackValue, param0IsSome, param0Word0, param0Word1, param0Word2) return ret } let ret = ret0 @@ -2580,14 +2580,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsSqS #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS_extern(resolveRef, rejectRef, callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS_extern(resolveRef, rejectRef, callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -2612,9 +2612,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsYaKSS_SS { }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS(resolveRef, rejectRef, callbackValue, param0Bytes, param0Length) + invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_SS(resolveRef, rejectRef, callbackValue, param0Word0, param0Word1, param0Word2) } } return resolved @@ -2652,14 +2652,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaK #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y_extern(resolveRef, rejectRef, callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y_extern(resolveRef, rejectRef, callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -2684,9 +2684,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsYaKSS_y { }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y(resolveRef, rejectRef, callbackValue, param0Bytes, param0Length) + invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaKSS_y(resolveRef, rejectRef, callbackValue, param0Word0, param0Word1, param0Word2) } } #else @@ -2794,14 +2794,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaK #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS_extern(resolveRef, rejectRef, callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS(_ resolveRef: Int32, _ rejectRef: Int32, _ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS_extern(resolveRef, rejectRef, callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -2826,9 +2826,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestsYaSS_SS { }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS(resolveRef, rejectRef, callbackValue, param0Bytes, param0Length) + invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaSS_SS(resolveRef, rejectRef, callbackValue, param0Word0, param0Word1, param0Word2) } } return resolved @@ -3008,14 +3008,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestsYaS #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -3035,9 +3035,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestss11FeatureFlagO_y { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y(callbackValue, param0Bytes, param0Length) + invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss11FeatureFlagO_y(callbackValue, param0Word0, param0Word1, param0Word2) } #else fatalError("Only available on WebAssembly") @@ -3381,14 +3381,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestss9D #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y_extern(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y_extern(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y(_ callback: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y_extern(callback, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y(_ callback: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y_extern(callback, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -3408,9 +3408,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestssSS_y { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - param0.bridgeJSWithLoweredParameter { (param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y(callbackValue, param0Bytes, param0Length) + invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSS_y(callbackValue, param0Word0, param0Word1, param0Word2) } #else fatalError("Only available on WebAssembly") @@ -3878,14 +3878,14 @@ public func _invoke_swift_closure_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSq #if arch(wasm32) @_extern(wasm, module: "bjs", name: "invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y") -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void #else -fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { +fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y_extern(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y(_ callback: Int32, _ param0IsSome: Int32, _ param0Bytes: Int32, _ param0Length: Int32) -> Void { - return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y_extern(callback, param0IsSome, param0Bytes, param0Length) +@inline(never) fileprivate func invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y(_ callback: Int32, _ param0IsSome: Int32, _ param0Word0: Int32, _ param0Word1: Int32, _ param0Word2: Int32) -> Void { + return invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y_extern(callback, param0IsSome, param0Word0, param0Word1, param0Word2) } #if arch(wasm32) @@ -3905,9 +3905,9 @@ private enum _BJS_Closure_20BridgeJSRuntimeTestssSqSS_y { let callback = JSObject.bridgeJSLiftParameter(callbackId) return { [callback] param0 in #if arch(wasm32) - param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Bytes, param0Length) in + param0.bridgeJSWithLoweredParameter { (param0IsSome, param0Word0, param0Word1, param0Word2) in let callbackValue = callback.bridgeJSLowerParameter() - invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y(callbackValue, param0IsSome, param0Bytes, param0Length) + invoke_js_callback_BridgeJSRuntimeTests_20BridgeJSRuntimeTestssSqSS_y(callbackValue, param0IsSome, param0Word0, param0Word1, param0Word2) } #else fatalError("Only available on WebAssembly") @@ -4308,10 +4308,10 @@ extension DataProcessor where Self: _BridgedSwiftProtocolWrapper { } func setLabelElements(_ labelPrefix: String, _ labelSuffix: String) -> Void { - labelPrefix.bridgeJSWithLoweredParameter { (labelPrefixBytes, labelPrefixLength) in - labelSuffix.bridgeJSWithLoweredParameter { (labelSuffixBytes, labelSuffixLength) in + labelPrefix.bridgeJSWithLoweredParameter { (labelPrefixWord0, labelPrefixWord1, labelPrefixWord2) in + labelSuffix.bridgeJSWithLoweredParameter { (labelSuffixWord0, labelSuffixWord1, labelSuffixWord2) in let jsObjectValue = jsObject.bridgeJSLowerParameter() - bjs_DataProcessor_setLabelElements(jsObjectValue, labelPrefixBytes, labelPrefixLength, labelSuffixBytes, labelSuffixLength) + bjs_DataProcessor_setLabelElements(jsObjectValue, labelPrefixWord0, labelPrefixWord1, labelPrefixWord2, labelSuffixWord0, labelSuffixWord1, labelSuffixWord2) } } } @@ -4394,9 +4394,9 @@ extension DataProcessor where Self: _BridgedSwiftProtocolWrapper { return Optional.bridgeJSLiftReturnFromSideChannel() } set { - newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueWord0, newValueWord1, newValueWord2) in let jsObjectValue = jsObject.bridgeJSLowerParameter() - bjs_DataProcessor_optionalTag_set(jsObjectValue, newValueIsSome, newValueBytes, newValueLength) + bjs_DataProcessor_optionalTag_set(jsObjectValue, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } } } @@ -4434,9 +4434,9 @@ extension DataProcessor where Self: _BridgedSwiftProtocolWrapper { return Optional.bridgeJSLiftReturnFromSideChannel() } set { - newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueIsSome, newValueWord0, newValueWord1, newValueWord2) in let jsObjectValue = jsObject.bridgeJSLowerParameter() - bjs_DataProcessor_optionalTheme_set(jsObjectValue, newValueIsSome, newValueBytes, newValueLength) + bjs_DataProcessor_optionalTheme_set(jsObjectValue, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } } } @@ -4528,14 +4528,14 @@ fileprivate func bjs_DataProcessor_getValue_extern(_ jsObject: Int32) -> Int32 { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_DataProcessor_setLabelElements") -fileprivate func bjs_DataProcessor_setLabelElements_extern(_ jsObject: Int32, _ labelPrefixBytes: Int32, _ labelPrefixLength: Int32, _ labelSuffixBytes: Int32, _ labelSuffixLength: Int32) -> Void +fileprivate func bjs_DataProcessor_setLabelElements_extern(_ jsObject: Int32, _ labelPrefixWord0: Int32, _ labelPrefixWord1: Int32, _ labelPrefixWord2: Int32, _ labelSuffixWord0: Int32, _ labelSuffixWord1: Int32, _ labelSuffixWord2: Int32) -> Void #else -fileprivate func bjs_DataProcessor_setLabelElements_extern(_ jsObject: Int32, _ labelPrefixBytes: Int32, _ labelPrefixLength: Int32, _ labelSuffixBytes: Int32, _ labelSuffixLength: Int32) -> Void { +fileprivate func bjs_DataProcessor_setLabelElements_extern(_ jsObject: Int32, _ labelPrefixWord0: Int32, _ labelPrefixWord1: Int32, _ labelPrefixWord2: Int32, _ labelSuffixWord0: Int32, _ labelSuffixWord1: Int32, _ labelSuffixWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_DataProcessor_setLabelElements(_ jsObject: Int32, _ labelPrefixBytes: Int32, _ labelPrefixLength: Int32, _ labelSuffixBytes: Int32, _ labelSuffixLength: Int32) -> Void { - return bjs_DataProcessor_setLabelElements_extern(jsObject, labelPrefixBytes, labelPrefixLength, labelSuffixBytes, labelSuffixLength) +@inline(never) fileprivate func bjs_DataProcessor_setLabelElements(_ jsObject: Int32, _ labelPrefixWord0: Int32, _ labelPrefixWord1: Int32, _ labelPrefixWord2: Int32, _ labelSuffixWord0: Int32, _ labelSuffixWord1: Int32, _ labelSuffixWord2: Int32) -> Void { + return bjs_DataProcessor_setLabelElements_extern(jsObject, labelPrefixWord0, labelPrefixWord1, labelPrefixWord2, labelSuffixWord0, labelSuffixWord1, labelSuffixWord2) } #if arch(wasm32) @@ -4684,14 +4684,14 @@ fileprivate func bjs_DataProcessor_optionalTag_get_extern(_ jsObject: Int32) -> #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_DataProcessor_optionalTag_set") -fileprivate func bjs_DataProcessor_optionalTag_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_DataProcessor_optionalTag_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_DataProcessor_optionalTag_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_DataProcessor_optionalTag_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_DataProcessor_optionalTag_set(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_DataProcessor_optionalTag_set_extern(jsObject, newValueIsSome, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_DataProcessor_optionalTag_set(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_DataProcessor_optionalTag_set_extern(jsObject, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -4756,14 +4756,14 @@ fileprivate func bjs_DataProcessor_optionalTheme_get_extern(_ jsObject: Int32) - #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_DataProcessor_optionalTheme_set") -fileprivate func bjs_DataProcessor_optionalTheme_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_DataProcessor_optionalTheme_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_DataProcessor_optionalTheme_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_DataProcessor_optionalTheme_set_extern(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_DataProcessor_optionalTheme_set(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_DataProcessor_optionalTheme_set_extern(jsObject, newValueIsSome, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_DataProcessor_optionalTheme_set(_ jsObject: Int32, _ newValueIsSome: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_DataProcessor_optionalTheme_set_extern(jsObject, newValueIsSome, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -7672,6 +7672,174 @@ extension APIOptionalResult: _BridgedSwiftAssociatedValueEnum { } } +@_expose(wasm, "bjs_StringABIExports_static_echo") +@_cdecl("bjs_StringABIExports_static_echo") +public func _bjs_StringABIExports_static_echo(_ valueBytes: Int32, _ valueLength: Int32) -> Void { + #if arch(wasm32) + let value = String.bridgeJSLiftParameter(valueBytes, valueLength) + let ret = StringABIExports.echo(_: value) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_echoOptional") +@_cdecl("bjs_StringABIExports_static_echoOptional") +public func _bjs_StringABIExports_static_echoOptional(_ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { + #if arch(wasm32) + let value = Optional.bridgeJSLiftParameter(valueIsSome, valueBytes, valueLength) + let ret = StringABIExports.echoOptional(_: value) + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_echoArray") +@_cdecl("bjs_StringABIExports_static_echoArray") +public func _bjs_StringABIExports_static_echoArray() -> Void { + #if arch(wasm32) + let value = [String].bridgeJSStackPop() + let ret = StringABIExports.echoArray(_: value) + ret.bridgeJSStackPush() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_smallEmpty") +@_cdecl("bjs_StringABIExports_static_smallEmpty") +public func _bjs_StringABIExports_static_smallEmpty() -> Void { + #if arch(wasm32) + let ret = StringABIExports.smallEmpty() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_smallASCII") +@_cdecl("bjs_StringABIExports_static_smallASCII") +public func _bjs_StringABIExports_static_smallASCII() -> Void { + #if arch(wasm32) + let ret = StringABIExports.smallASCII() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_smallEight") +@_cdecl("bjs_StringABIExports_static_smallEight") +public func _bjs_StringABIExports_static_smallEight() -> Void { + #if arch(wasm32) + let ret = StringABIExports.smallEight() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_smallNine") +@_cdecl("bjs_StringABIExports_static_smallNine") +public func _bjs_StringABIExports_static_smallNine() -> Void { + #if arch(wasm32) + let ret = StringABIExports.smallNine() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_smallTen") +@_cdecl("bjs_StringABIExports_static_smallTen") +public func _bjs_StringABIExports_static_smallTen() -> Void { + #if arch(wasm32) + let ret = StringABIExports.smallTen() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_smallUTF8") +@_cdecl("bjs_StringABIExports_static_smallUTF8") +public func _bjs_StringABIExports_static_smallUTF8() -> Void { + #if arch(wasm32) + let ret = StringABIExports.smallUTF8() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_largeImmortal") +@_cdecl("bjs_StringABIExports_static_largeImmortal") +public func _bjs_StringABIExports_static_largeImmortal() -> Void { + #if arch(wasm32) + let ret = StringABIExports.largeImmortal() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_largeImmortalAgain") +@_cdecl("bjs_StringABIExports_static_largeImmortalAgain") +public func _bjs_StringABIExports_static_largeImmortalAgain() -> Void { + #if arch(wasm32) + let ret = StringABIExports.largeImmortalAgain() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_largeDynamic") +@_cdecl("bjs_StringABIExports_static_largeDynamic") +public func _bjs_StringABIExports_static_largeDynamic() -> Void { + #if arch(wasm32) + let ret = StringABIExports.largeDynamic() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_immortalSubstring") +@_cdecl("bjs_StringABIExports_static_immortalSubstring") +public func _bjs_StringABIExports_static_immortalSubstring() -> Void { + #if arch(wasm32) + let ret = StringABIExports.immortalSubstring() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_optionalNone") +@_cdecl("bjs_StringABIExports_static_optionalNone") +public func _bjs_StringABIExports_static_optionalNone() -> Void { + #if arch(wasm32) + let ret = StringABIExports.optionalNone() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + +@_expose(wasm, "bjs_StringABIExports_static_optionalSome") +@_cdecl("bjs_StringABIExports_static_optionalSome") +public func _bjs_StringABIExports_static_optionalSome() -> Void { + #if arch(wasm32) + let ret = StringABIExports.optionalSome() + return ret.bridgeJSLowerReturn() + #else + fatalError("Only available on WebAssembly") + #endif +} + extension JSCoordinate: _BridgedSwiftStruct { @_spi(BridgeJS) @_transparent public static func bridgeJSStackPop() -> JSCoordinate { let longitude = Double.bridgeJSStackPop() @@ -16234,20 +16402,20 @@ func _$Promise_reject(_ promise: JSObject, _ value: JSValue) throws(JSException) #if arch(wasm32) @_extern(wasm, module: "bjs", name: "promise_resolve_BridgeJSRuntimeTests_SS") -fileprivate func promise_resolve_BridgeJSRuntimeTests_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func promise_resolve_BridgeJSRuntimeTests_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func promise_resolve_BridgeJSRuntimeTests_SS_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func promise_resolve_BridgeJSRuntimeTests_SS_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func promise_resolve_BridgeJSRuntimeTests_SS(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return promise_resolve_BridgeJSRuntimeTests_SS_extern(promise, valueBytes, valueLength) +@inline(never) fileprivate func promise_resolve_BridgeJSRuntimeTests_SS(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_BridgeJSRuntimeTests_SS_extern(promise, valueWord0, valueWord1, valueWord2) } func _$Promise_resolve_SS(_ promise: JSObject, _ value: String) throws(JSException) -> Void { - value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in let promiseValue = promise.bridgeJSLowerParameter() - promise_resolve_BridgeJSRuntimeTests_SS(promiseValue, valueBytes, valueLength) + promise_resolve_BridgeJSRuntimeTests_SS(promiseValue, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error } } @@ -16402,20 +16570,20 @@ func _$Promise_resolve_8JSObjectC(_ promise: JSObject, _ value: JSObject) throws #if arch(wasm32) @_extern(wasm, module: "bjs", name: "promise_resolve_BridgeJSRuntimeTests_5ThemeO") -fileprivate func promise_resolve_BridgeJSRuntimeTests_5ThemeO_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func promise_resolve_BridgeJSRuntimeTests_5ThemeO_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func promise_resolve_BridgeJSRuntimeTests_5ThemeO_extern(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func promise_resolve_BridgeJSRuntimeTests_5ThemeO_extern(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func promise_resolve_BridgeJSRuntimeTests_5ThemeO(_ promise: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return promise_resolve_BridgeJSRuntimeTests_5ThemeO_extern(promise, valueBytes, valueLength) +@inline(never) fileprivate func promise_resolve_BridgeJSRuntimeTests_5ThemeO(_ promise: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_BridgeJSRuntimeTests_5ThemeO_extern(promise, valueWord0, valueWord1, valueWord2) } func _$Promise_resolve_5ThemeO(_ promise: JSObject, _ value: Theme) throws(JSException) -> Void { - value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in let promiseValue = promise.bridgeJSLowerParameter() - promise_resolve_BridgeJSRuntimeTests_5ThemeO(promiseValue, valueBytes, valueLength) + promise_resolve_BridgeJSRuntimeTests_5ThemeO(promiseValue, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error } } @@ -16445,20 +16613,20 @@ func _$Promise_resolve_9DirectionO(_ promise: JSObject, _ value: Direction) thro #if arch(wasm32) @_extern(wasm, module: "bjs", name: "promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO") -fileprivate func promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO_extern(_ promise: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO(_ promise: Int32, _ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO_extern(promise, valueIsSome, valueBytes, valueLength) +@inline(never) fileprivate func promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO(_ promise: Int32, _ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO_extern(promise, valueIsSome, valueWord0, valueWord1, valueWord2) } func _$Promise_resolve_Sq5ThemeO(_ promise: JSObject, _ value: Optional) throws(JSException) -> Void { - value.bridgeJSWithLoweredParameter { (valueIsSome, valueBytes, valueLength) in + value.bridgeJSWithLoweredParameter { (valueIsSome, valueWord0, valueWord1, valueWord2) in let promiseValue = promise.bridgeJSLowerParameter() - promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO(promiseValue, valueIsSome, valueBytes, valueLength) + promise_resolve_BridgeJSRuntimeTests_Sq5ThemeO(promiseValue, valueIsSome, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error } } @@ -16802,14 +16970,14 @@ extension Boxed: _BridgedSwiftAlias, _BridgedSwiftStackType {} #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Surface_init") -fileprivate func bjs_Surface_init_extern(_ labelBytes: Int32, _ labelLength: Int32) -> Int32 +fileprivate func bjs_Surface_init_extern(_ labelWord0: Int32, _ labelWord1: Int32, _ labelWord2: Int32) -> Int32 #else -fileprivate func bjs_Surface_init_extern(_ labelBytes: Int32, _ labelLength: Int32) -> Int32 { +fileprivate func bjs_Surface_init_extern(_ labelWord0: Int32, _ labelWord1: Int32, _ labelWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_Surface_init(_ labelBytes: Int32, _ labelLength: Int32) -> Int32 { - return bjs_Surface_init_extern(labelBytes, labelLength) +@inline(never) fileprivate func bjs_Surface_init(_ labelWord0: Int32, _ labelWord1: Int32, _ labelWord2: Int32) -> Int32 { + return bjs_Surface_init_extern(labelWord0, labelWord1, labelWord2) } #if arch(wasm32) @@ -16825,8 +16993,8 @@ fileprivate func bjs_Surface_label_get_extern(_ self: Int32) -> Int32 { } func _$Surface_init(_ label: String) throws(JSException) -> JSObject { - let ret0 = label.bridgeJSWithLoweredParameter { (labelBytes, labelLength) in - let ret = bjs_Surface_init(labelBytes, labelLength) + let ret0 = label.bridgeJSWithLoweredParameter { (labelWord0, labelWord1, labelWord2) in + let ret = bjs_Surface_init(labelWord0, labelWord1, labelWord2) return ret } let ret = ret0 @@ -16847,38 +17015,38 @@ func _$Surface_label_get(_ self: JSObject) throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_AliasImports_jsRoundTripTagged_static") -fileprivate func bjs_AliasImports_jsRoundTripTagged_static_extern(_ valueBytes: Int32, _ valueLength: Int32) -> Int32 +fileprivate func bjs_AliasImports_jsRoundTripTagged_static_extern(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 #else -fileprivate func bjs_AliasImports_jsRoundTripTagged_static_extern(_ valueBytes: Int32, _ valueLength: Int32) -> Int32 { +fileprivate func bjs_AliasImports_jsRoundTripTagged_static_extern(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_AliasImports_jsRoundTripTagged_static(_ valueBytes: Int32, _ valueLength: Int32) -> Int32 { - return bjs_AliasImports_jsRoundTripTagged_static_extern(valueBytes, valueLength) +@inline(never) fileprivate func bjs_AliasImports_jsRoundTripTagged_static(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 { + return bjs_AliasImports_jsRoundTripTagged_static_extern(valueWord0, valueWord1, valueWord2) } #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_AliasImports_jsRoundTripOptionalTagged_static") -fileprivate func bjs_AliasImports_jsRoundTripOptionalTagged_static_extern(_ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void +fileprivate func bjs_AliasImports_jsRoundTripOptionalTagged_static_extern(_ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void #else -fileprivate func bjs_AliasImports_jsRoundTripOptionalTagged_static_extern(_ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { +fileprivate func bjs_AliasImports_jsRoundTripOptionalTagged_static_extern(_ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_AliasImports_jsRoundTripOptionalTagged_static(_ valueIsSome: Int32, _ valueBytes: Int32, _ valueLength: Int32) -> Void { - return bjs_AliasImports_jsRoundTripOptionalTagged_static_extern(valueIsSome, valueBytes, valueLength) +@inline(never) fileprivate func bjs_AliasImports_jsRoundTripOptionalTagged_static(_ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return bjs_AliasImports_jsRoundTripOptionalTagged_static_extern(valueIsSome, valueWord0, valueWord1, valueWord2) } #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_AliasImports_jsProduceOptionalCanvas_static") -fileprivate func bjs_AliasImports_jsProduceOptionalCanvas_static_extern(_ labelIsSome: Int32, _ labelBytes: Int32, _ labelLength: Int32) -> Void +fileprivate func bjs_AliasImports_jsProduceOptionalCanvas_static_extern(_ labelIsSome: Int32, _ labelWord0: Int32, _ labelWord1: Int32, _ labelWord2: Int32) -> Void #else -fileprivate func bjs_AliasImports_jsProduceOptionalCanvas_static_extern(_ labelIsSome: Int32, _ labelBytes: Int32, _ labelLength: Int32) -> Void { +fileprivate func bjs_AliasImports_jsProduceOptionalCanvas_static_extern(_ labelIsSome: Int32, _ labelWord0: Int32, _ labelWord1: Int32, _ labelWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_AliasImports_jsProduceOptionalCanvas_static(_ labelIsSome: Int32, _ labelBytes: Int32, _ labelLength: Int32) -> Void { - return bjs_AliasImports_jsProduceOptionalCanvas_static_extern(labelIsSome, labelBytes, labelLength) +@inline(never) fileprivate func bjs_AliasImports_jsProduceOptionalCanvas_static(_ labelIsSome: Int32, _ labelWord0: Int32, _ labelWord1: Int32, _ labelWord2: Int32) -> Void { + return bjs_AliasImports_jsProduceOptionalCanvas_static_extern(labelIsSome, labelWord0, labelWord1, labelWord2) } #if arch(wasm32) @@ -16942,8 +17110,8 @@ fileprivate func bjs_AliasImports_jsRoundTripOptionalUserId_static_extern(_ valu } func _$AliasImports_jsRoundTripTagged(_ value: Tagged) throws(JSException) -> Tagged { - let ret0 = value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in - let ret = bjs_AliasImports_jsRoundTripTagged_static(valueBytes, valueLength) + let ret0 = value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in + let ret = bjs_AliasImports_jsRoundTripTagged_static(valueWord0, valueWord1, valueWord2) return ret } let ret = ret0 @@ -16954,8 +17122,8 @@ func _$AliasImports_jsRoundTripTagged(_ value: Tagged) throws(JSException) -> Ta } func _$AliasImports_jsRoundTripOptionalTagged(_ value: Optional) throws(JSException) -> Optional { - value.bridgeJSWithLoweredParameter { (valueIsSome, valueBytes, valueLength) in - bjs_AliasImports_jsRoundTripOptionalTagged_static(valueIsSome, valueBytes, valueLength) + value.bridgeJSWithLoweredParameter { (valueIsSome, valueWord0, valueWord1, valueWord2) in + bjs_AliasImports_jsRoundTripOptionalTagged_static(valueIsSome, valueWord0, valueWord1, valueWord2) } if let error = _swift_js_take_exception() { throw error @@ -16964,8 +17132,8 @@ func _$AliasImports_jsRoundTripOptionalTagged(_ value: Optional) throws( } func _$AliasImports_jsProduceOptionalCanvas(_ label: Optional) throws(JSException) -> Optional { - label.bridgeJSWithLoweredParameter { (labelIsSome, labelBytes, labelLength) in - bjs_AliasImports_jsProduceOptionalCanvas_static(labelIsSome, labelBytes, labelLength) + label.bridgeJSWithLoweredParameter { (labelIsSome, labelWord0, labelWord1, labelWord2) in + bjs_AliasImports_jsProduceOptionalCanvas_static(labelIsSome, labelWord0, labelWord1, labelWord2) } if let error = _swift_js_take_exception() { throw error @@ -17020,14 +17188,14 @@ func _$AliasImports_jsRoundTripOptionalUserId(_ value: Optional) throws( #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ArrayElementObject_init") -fileprivate func bjs_ArrayElementObject_init_extern(_ idBytes: Int32, _ idLength: Int32) -> Int32 +fileprivate func bjs_ArrayElementObject_init_extern(_ idWord0: Int32, _ idWord1: Int32, _ idWord2: Int32) -> Int32 #else -fileprivate func bjs_ArrayElementObject_init_extern(_ idBytes: Int32, _ idLength: Int32) -> Int32 { +fileprivate func bjs_ArrayElementObject_init_extern(_ idWord0: Int32, _ idWord1: Int32, _ idWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_ArrayElementObject_init(_ idBytes: Int32, _ idLength: Int32) -> Int32 { - return bjs_ArrayElementObject_init_extern(idBytes, idLength) +@inline(never) fileprivate func bjs_ArrayElementObject_init(_ idWord0: Int32, _ idWord1: Int32, _ idWord2: Int32) -> Int32 { + return bjs_ArrayElementObject_init_extern(idWord0, idWord1, idWord2) } #if arch(wasm32) @@ -17043,8 +17211,8 @@ fileprivate func bjs_ArrayElementObject_id_get_extern(_ self: Int32) -> Int32 { } func _$ArrayElementObject_init(_ id: String) throws(JSException) -> JSObject { - let ret0 = id.bridgeJSWithLoweredParameter { (idBytes, idLength) in - let ret = bjs_ArrayElementObject_init(idBytes, idLength) + let ret0 = id.bridgeJSWithLoweredParameter { (idWord0, idWord1, idWord2) in + let ret = bjs_ArrayElementObject_init(idWord0, idWord1, idWord2) return ret } let ret = ret0 @@ -17455,26 +17623,26 @@ fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripBool_static_extern(_ res #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_AsyncImportImports_jsAsyncRoundTripString_static") -fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripString_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void +fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripString_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void #else -fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripString_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void { +fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripString_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripString_static(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void { - return bjs_AsyncImportImports_jsAsyncRoundTripString_static_extern(resolveRef, rejectRef, vBytes, vLength) +@inline(never) fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripString_static(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void { + return bjs_AsyncImportImports_jsAsyncRoundTripString_static_extern(resolveRef, rejectRef, vWord0, vWord1, vWord2) } #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static") -fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vIsSome: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void +fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vIsSome: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void #else -fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vIsSome: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void { +fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vIsSome: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static(_ resolveRef: Int32, _ rejectRef: Int32, _ vIsSome: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void { - return bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static_extern(resolveRef, rejectRef, vIsSome, vBytes, vLength) +@inline(never) fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static(_ resolveRef: Int32, _ rejectRef: Int32, _ vIsSome: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void { + return bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static_extern(resolveRef, rejectRef, vIsSome, vWord0, vWord1, vWord2) } #if arch(wasm32) @@ -17527,14 +17695,14 @@ fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripStringArray_static_exter #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static") -fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void +fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void #else -fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void { +fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static(_ resolveRef: Int32, _ rejectRef: Int32, _ vBytes: Int32, _ vLength: Int32) -> Void { - return bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static_extern(resolveRef, rejectRef, vBytes, vLength) +@inline(never) fileprivate func bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static(_ resolveRef: Int32, _ rejectRef: Int32, _ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Void { + return bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static_extern(resolveRef, rejectRef, vWord0, vWord1, vWord2) } #if arch(wasm32) @@ -17601,8 +17769,8 @@ func _$AsyncImportImports_jsAsyncRoundTripString(_ v: String) async throws(JSExc }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - v.bridgeJSWithLoweredParameter { (vBytes, vLength) in - bjs_AsyncImportImports_jsAsyncRoundTripString_static(resolveRef, rejectRef, vBytes, vLength) + v.bridgeJSWithLoweredParameter { (vWord0, vWord1, vWord2) in + bjs_AsyncImportImports_jsAsyncRoundTripString_static(resolveRef, rejectRef, vWord0, vWord1, vWord2) } } return resolved @@ -17614,8 +17782,8 @@ func _$AsyncImportImports_jsAsyncRoundTripOptionalString(_ v: Optional) }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - v.bridgeJSWithLoweredParameter { (vIsSome, vBytes, vLength) in - bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static(resolveRef, rejectRef, vIsSome, vBytes, vLength) + v.bridgeJSWithLoweredParameter { (vIsSome, vWord0, vWord1, vWord2) in + bjs_AsyncImportImports_jsAsyncRoundTripOptionalString_static(resolveRef, rejectRef, vIsSome, vWord0, vWord1, vWord2) } } return resolved @@ -17675,8 +17843,8 @@ func _$AsyncImportImports_jsAsyncRoundTripFeatureFlag(_ v: FeatureFlag) async th }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - v.bridgeJSWithLoweredParameter { (vBytes, vLength) in - bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static(resolveRef, rejectRef, vBytes, vLength) + v.bridgeJSWithLoweredParameter { (vWord0, vWord1, vWord2) in + bjs_AsyncImportImports_jsAsyncRoundTripFeatureFlag_static(resolveRef, rejectRef, vWord0, vWord1, vWord2) } } return resolved @@ -17778,14 +17946,14 @@ fileprivate func bjs_ClosureSupportImports_jsApplyDouble_static_extern(_ value: #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ClosureSupportImports_jsApplyString_static") -fileprivate func bjs_ClosureSupportImports_jsApplyString_static_extern(_ valueBytes: Int32, _ valueLength: Int32, _ transform: Int32) -> Int32 +fileprivate func bjs_ClosureSupportImports_jsApplyString_static_extern(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32, _ transform: Int32) -> Int32 #else -fileprivate func bjs_ClosureSupportImports_jsApplyString_static_extern(_ valueBytes: Int32, _ valueLength: Int32, _ transform: Int32) -> Int32 { +fileprivate func bjs_ClosureSupportImports_jsApplyString_static_extern(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32, _ transform: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_ClosureSupportImports_jsApplyString_static(_ valueBytes: Int32, _ valueLength: Int32, _ transform: Int32) -> Int32 { - return bjs_ClosureSupportImports_jsApplyString_static_extern(valueBytes, valueLength, transform) +@inline(never) fileprivate func bjs_ClosureSupportImports_jsApplyString_static(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32, _ transform: Int32) -> Int32 { + return bjs_ClosureSupportImports_jsApplyString_static_extern(valueWord0, valueWord1, valueWord2, transform) } #if arch(wasm32) @@ -17886,14 +18054,14 @@ fileprivate func bjs_ClosureSupportImports_jsMakeDoubleToDouble_static_extern(_ #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_ClosureSupportImports_jsMakeStringToString_static") -fileprivate func bjs_ClosureSupportImports_jsMakeStringToString_static_extern(_ prefixBytes: Int32, _ prefixLength: Int32) -> Int32 +fileprivate func bjs_ClosureSupportImports_jsMakeStringToString_static_extern(_ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32) -> Int32 #else -fileprivate func bjs_ClosureSupportImports_jsMakeStringToString_static_extern(_ prefixBytes: Int32, _ prefixLength: Int32) -> Int32 { +fileprivate func bjs_ClosureSupportImports_jsMakeStringToString_static_extern(_ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_ClosureSupportImports_jsMakeStringToString_static(_ prefixBytes: Int32, _ prefixLength: Int32) -> Int32 { - return bjs_ClosureSupportImports_jsMakeStringToString_static_extern(prefixBytes, prefixLength) +@inline(never) fileprivate func bjs_ClosureSupportImports_jsMakeStringToString_static(_ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32) -> Int32 { + return bjs_ClosureSupportImports_jsMakeStringToString_static_extern(prefixWord0, prefixWord1, prefixWord2) } #if arch(wasm32) @@ -18042,9 +18210,9 @@ func _$ClosureSupportImports_jsApplyDouble(_ value: Double, _ transform: JSTyped } func _$ClosureSupportImports_jsApplyString(_ value: String, _ transform: JSTypedClosure<(String) -> String>) throws(JSException) -> String { - let ret0 = value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in + let ret0 = value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in let transformFuncRef = transform.bridgeJSLowerParameter() - let ret = bjs_ClosureSupportImports_jsApplyString_static(valueBytes, valueLength, transformFuncRef) + let ret = bjs_ClosureSupportImports_jsApplyString_static(valueWord0, valueWord1, valueWord2, transformFuncRef) return ret } let ret = ret0 @@ -18133,8 +18301,8 @@ func _$ClosureSupportImports_jsMakeDoubleToDouble(_ base: Double) throws(JSExcep } func _$ClosureSupportImports_jsMakeStringToString(_ prefix: String) throws(JSException) -> (String) -> String { - let ret0 = prefix.bridgeJSWithLoweredParameter { (prefixBytes, prefixLength) in - let ret = bjs_ClosureSupportImports_jsMakeStringToString_static(prefixBytes, prefixLength) + let ret0 = prefix.bridgeJSWithLoweredParameter { (prefixWord0, prefixWord1, prefixWord2) in + let ret = bjs_ClosureSupportImports_jsMakeStringToString_static(prefixWord0, prefixWord1, prefixWord2) return ret } let ret = ret0 @@ -18386,14 +18554,14 @@ func _$DictionarySupportImports_jsRoundTripDictionaryDoubleArray(_ values: [Stri #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Foo_init") -fileprivate func bjs_Foo_init_extern(_ valueBytes: Int32, _ valueLength: Int32) -> Int32 +fileprivate func bjs_Foo_init_extern(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 #else -fileprivate func bjs_Foo_init_extern(_ valueBytes: Int32, _ valueLength: Int32) -> Int32 { +fileprivate func bjs_Foo_init_extern(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_Foo_init(_ valueBytes: Int32, _ valueLength: Int32) -> Int32 { - return bjs_Foo_init_extern(valueBytes, valueLength) +@inline(never) fileprivate func bjs_Foo_init(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 { + return bjs_Foo_init_extern(valueWord0, valueWord1, valueWord2) } #if arch(wasm32) @@ -18409,8 +18577,8 @@ fileprivate func bjs_Foo_value_get_extern(_ self: Int32) -> Int32 { } func _$Foo_init(_ value: String) throws(JSException) -> JSObject { - let ret0 = value.bridgeJSWithLoweredParameter { (valueBytes, valueLength) in - let ret = bjs_Foo_init(valueBytes, valueLength) + let ret0 = value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in + let ret = bjs_Foo_init(valueWord0, valueWord1, valueWord2) return ret } let ret = ret0 @@ -18512,19 +18680,19 @@ func _$jsRoundTripBool(_ v: Bool) throws(JSException) -> Bool { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripString") -fileprivate func bjs_jsRoundTripString_extern(_ vBytes: Int32, _ vLength: Int32) -> Int32 +fileprivate func bjs_jsRoundTripString_extern(_ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Int32 #else -fileprivate func bjs_jsRoundTripString_extern(_ vBytes: Int32, _ vLength: Int32) -> Int32 { +fileprivate func bjs_jsRoundTripString_extern(_ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_jsRoundTripString(_ vBytes: Int32, _ vLength: Int32) -> Int32 { - return bjs_jsRoundTripString_extern(vBytes, vLength) +@inline(never) fileprivate func bjs_jsRoundTripString(_ vWord0: Int32, _ vWord1: Int32, _ vWord2: Int32) -> Int32 { + return bjs_jsRoundTripString_extern(vWord0, vWord1, vWord2) } func _$jsRoundTripString(_ v: String) throws(JSException) -> String { - let ret0 = v.bridgeJSWithLoweredParameter { (vBytes, vLength) in - let ret = bjs_jsRoundTripString(vBytes, vLength) + let ret0 = v.bridgeJSWithLoweredParameter { (vWord0, vWord1, vWord2) in + let ret = bjs_jsRoundTripString(vWord0, vWord1, vWord2) return ret } let ret = ret0 @@ -18640,19 +18808,19 @@ func _$jsThrowOrString(_ shouldThrow: Bool) throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsRoundTripFeatureFlag") -fileprivate func bjs_jsRoundTripFeatureFlag_extern(_ flagBytes: Int32, _ flagLength: Int32) -> Int32 +fileprivate func bjs_jsRoundTripFeatureFlag_extern(_ flagWord0: Int32, _ flagWord1: Int32, _ flagWord2: Int32) -> Int32 #else -fileprivate func bjs_jsRoundTripFeatureFlag_extern(_ flagBytes: Int32, _ flagLength: Int32) -> Int32 { +fileprivate func bjs_jsRoundTripFeatureFlag_extern(_ flagWord0: Int32, _ flagWord1: Int32, _ flagWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_jsRoundTripFeatureFlag(_ flagBytes: Int32, _ flagLength: Int32) -> Int32 { - return bjs_jsRoundTripFeatureFlag_extern(flagBytes, flagLength) +@inline(never) fileprivate func bjs_jsRoundTripFeatureFlag(_ flagWord0: Int32, _ flagWord1: Int32, _ flagWord2: Int32) -> Int32 { + return bjs_jsRoundTripFeatureFlag_extern(flagWord0, flagWord1, flagWord2) } func _$jsRoundTripFeatureFlag(_ flag: FeatureFlag) throws(JSException) -> FeatureFlag { - let ret0 = flag.bridgeJSWithLoweredParameter { (flagBytes, flagLength) in - let ret = bjs_jsRoundTripFeatureFlag(flagBytes, flagLength) + let ret0 = flag.bridgeJSWithLoweredParameter { (flagWord0, flagWord1, flagWord2) in + let ret = bjs_jsRoundTripFeatureFlag(flagWord0, flagWord1, flagWord2) return ret } let ret = ret0 @@ -18686,14 +18854,14 @@ func _$runAsyncWorks() async throws(JSException) -> Void { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_fetchWeatherData") -fileprivate func bjs_fetchWeatherData_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ cityBytes: Int32, _ cityLength: Int32) -> Void +fileprivate func bjs_fetchWeatherData_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ cityWord0: Int32, _ cityWord1: Int32, _ cityWord2: Int32) -> Void #else -fileprivate func bjs_fetchWeatherData_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ cityBytes: Int32, _ cityLength: Int32) -> Void { +fileprivate func bjs_fetchWeatherData_extern(_ resolveRef: Int32, _ rejectRef: Int32, _ cityWord0: Int32, _ cityWord1: Int32, _ cityWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_fetchWeatherData(_ resolveRef: Int32, _ rejectRef: Int32, _ cityBytes: Int32, _ cityLength: Int32) -> Void { - return bjs_fetchWeatherData_extern(resolveRef, rejectRef, cityBytes, cityLength) +@inline(never) fileprivate func bjs_fetchWeatherData(_ resolveRef: Int32, _ rejectRef: Int32, _ cityWord0: Int32, _ cityWord1: Int32, _ cityWord2: Int32) -> Void { + return bjs_fetchWeatherData_extern(resolveRef, rejectRef, cityWord0, cityWord1, cityWord2) } func _$fetchWeatherData(_ city: String) async throws(JSException) -> WeatherData { @@ -18702,8 +18870,8 @@ func _$fetchWeatherData(_ city: String) async throws(JSException) -> WeatherData }, makeRejectClosure: { JSTypedClosure<(sending JSValue) -> Void>.sending($0) }) { resolveRef, rejectRef in - city.bridgeJSWithLoweredParameter { (cityBytes, cityLength) in - bjs_fetchWeatherData(resolveRef, rejectRef, cityBytes, cityLength) + city.bridgeJSWithLoweredParameter { (cityWord0, cityWord1, cityWord2) in + bjs_fetchWeatherData(resolveRef, rejectRef, cityWord0, cityWord1, cityWord2) } } return resolved @@ -18731,19 +18899,19 @@ func _$_jsWeirdFunction() throws(JSException) -> Double { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_parseInt") -fileprivate func bjs_parseInt_extern(_ stringBytes: Int32, _ stringLength: Int32) -> Float64 +fileprivate func bjs_parseInt_extern(_ stringWord0: Int32, _ stringWord1: Int32, _ stringWord2: Int32) -> Float64 #else -fileprivate func bjs_parseInt_extern(_ stringBytes: Int32, _ stringLength: Int32) -> Float64 { +fileprivate func bjs_parseInt_extern(_ stringWord0: Int32, _ stringWord1: Int32, _ stringWord2: Int32) -> Float64 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_parseInt(_ stringBytes: Int32, _ stringLength: Int32) -> Float64 { - return bjs_parseInt_extern(stringBytes, stringLength) +@inline(never) fileprivate func bjs_parseInt(_ stringWord0: Int32, _ stringWord1: Int32, _ stringWord2: Int32) -> Float64 { + return bjs_parseInt_extern(stringWord0, stringWord1, stringWord2) } func _$parseInt(_ string: String) throws(JSException) -> Double { - let ret0 = string.bridgeJSWithLoweredParameter { (stringBytes, stringLength) in - let ret = bjs_parseInt(stringBytes, stringLength) + let ret0 = string.bridgeJSWithLoweredParameter { (stringWord0, stringWord1, stringWord2) in + let ret = bjs_parseInt(stringWord0, stringWord1, stringWord2) return ret } let ret = ret0 @@ -18755,14 +18923,14 @@ func _$parseInt(_ string: String) throws(JSException) -> Double { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_init") -fileprivate func bjs_JsGreeter_init_extern(_ nameBytes: Int32, _ nameLength: Int32, _ prefixBytes: Int32, _ prefixLength: Int32) -> Int32 +fileprivate func bjs_JsGreeter_init_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32) -> Int32 #else -fileprivate func bjs_JsGreeter_init_extern(_ nameBytes: Int32, _ nameLength: Int32, _ prefixBytes: Int32, _ prefixLength: Int32) -> Int32 { +fileprivate func bjs_JsGreeter_init_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_JsGreeter_init(_ nameBytes: Int32, _ nameLength: Int32, _ prefixBytes: Int32, _ prefixLength: Int32) -> Int32 { - return bjs_JsGreeter_init_extern(nameBytes, nameLength, prefixBytes, prefixLength) +@inline(never) fileprivate func bjs_JsGreeter_init(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ prefixWord0: Int32, _ prefixWord1: Int32, _ prefixWord2: Int32) -> Int32 { + return bjs_JsGreeter_init_extern(nameWord0, nameWord1, nameWord2, prefixWord0, prefixWord1, prefixWord2) } #if arch(wasm32) @@ -18791,14 +18959,14 @@ fileprivate func bjs_JsGreeter_prefix_get_extern(_ self: Int32) -> Int32 { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_name_set") -fileprivate func bjs_JsGreeter_name_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_JsGreeter_name_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_JsGreeter_name_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_JsGreeter_name_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_JsGreeter_name_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_JsGreeter_name_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_JsGreeter_name_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_JsGreeter_name_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -18815,20 +18983,20 @@ fileprivate func bjs_JsGreeter_greet_extern(_ self: Int32) -> Int32 { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_JsGreeter_changeName") -fileprivate func bjs_JsGreeter_changeName_extern(_ self: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void +fileprivate func bjs_JsGreeter_changeName_extern(_ self: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void #else -fileprivate func bjs_JsGreeter_changeName_extern(_ self: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { +fileprivate func bjs_JsGreeter_changeName_extern(_ self: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_JsGreeter_changeName(_ self: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { - return bjs_JsGreeter_changeName_extern(self, nameBytes, nameLength) +@inline(never) fileprivate func bjs_JsGreeter_changeName(_ self: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void { + return bjs_JsGreeter_changeName_extern(self, nameWord0, nameWord1, nameWord2) } func _$JsGreeter_init(_ name: String, _ prefix: String) throws(JSException) -> JSObject { - let ret0 = name.bridgeJSWithLoweredParameter { (nameBytes, nameLength) in - let ret1 = prefix.bridgeJSWithLoweredParameter { (prefixBytes, prefixLength) in - let ret = bjs_JsGreeter_init(nameBytes, nameLength, prefixBytes, prefixLength) + let ret0 = name.bridgeJSWithLoweredParameter { (nameWord0, nameWord1, nameWord2) in + let ret1 = prefix.bridgeJSWithLoweredParameter { (prefixWord0, prefixWord1, prefixWord2) in + let ret = bjs_JsGreeter_init(nameWord0, nameWord1, nameWord2, prefixWord0, prefixWord1, prefixWord2) return ret } return ret1 @@ -18859,9 +19027,9 @@ func _$JsGreeter_prefix_get(_ self: JSObject) throws(JSException) -> String { } func _$JsGreeter_name_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_JsGreeter_name_set(selfValue, newValueBytes, newValueLength) + bjs_JsGreeter_name_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -18878,9 +19046,9 @@ func _$JsGreeter_greet(_ self: JSObject) throws(JSException) -> String { } func _$JsGreeter_changeName(_ self: JSObject, _ name: String) throws(JSException) -> Void { - name.bridgeJSWithLoweredParameter { (nameBytes, nameLength) in + name.bridgeJSWithLoweredParameter { (nameWord0, nameWord1, nameWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_JsGreeter_changeName(selfValue, nameBytes, nameLength) + bjs_JsGreeter_changeName(selfValue, nameWord0, nameWord1, nameWord2) } if let error = _swift_js_take_exception() { throw error @@ -18937,14 +19105,14 @@ fileprivate func bjs_WeatherData_temperature_set_extern(_ self: Int32, _ newValu #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_WeatherData_description_set") -fileprivate func bjs_WeatherData_description_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_WeatherData_description_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_WeatherData_description_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_WeatherData_description_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_WeatherData_description_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_WeatherData_description_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_WeatherData_description_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_WeatherData_description_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -18996,9 +19164,9 @@ func _$WeatherData_temperature_set(_ self: JSObject, _ newValue: Double) throws( } func _$WeatherData_description_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_WeatherData_description_set(selfValue, newValueBytes, newValueLength) + bjs_WeatherData_description_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -19180,14 +19348,14 @@ func _$StaticBox_value(_ self: JSObject) throws(JSException) -> Double { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Animal_init") -fileprivate func bjs_Animal_init_extern(_ nameBytes: Int32, _ nameLength: Int32, _ age: Float64, _ isCat: Int32) -> Int32 +fileprivate func bjs_Animal_init_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ age: Float64, _ isCat: Int32) -> Int32 #else -fileprivate func bjs_Animal_init_extern(_ nameBytes: Int32, _ nameLength: Int32, _ age: Float64, _ isCat: Int32) -> Int32 { +fileprivate func bjs_Animal_init_extern(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ age: Float64, _ isCat: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_Animal_init(_ nameBytes: Int32, _ nameLength: Int32, _ age: Float64, _ isCat: Int32) -> Int32 { - return bjs_Animal_init_extern(nameBytes, nameLength, age, isCat) +@inline(never) fileprivate func bjs_Animal_init(_ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32, _ age: Float64, _ isCat: Int32) -> Int32 { + return bjs_Animal_init_extern(nameWord0, nameWord1, nameWord2, age, isCat) } #if arch(wasm32) @@ -19228,14 +19396,14 @@ fileprivate func bjs_Animal_isCat_get_extern(_ self: Int32) -> Int32 { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_Animal_name_set") -fileprivate func bjs_Animal_name_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void +fileprivate func bjs_Animal_name_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void #else -fileprivate func bjs_Animal_name_set_extern(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { +fileprivate func bjs_Animal_name_set_extern(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_Animal_name_set(_ self: Int32, _ newValueBytes: Int32, _ newValueLength: Int32) -> Void { - return bjs_Animal_name_set_extern(self, newValueBytes, newValueLength) +@inline(never) fileprivate func bjs_Animal_name_set(_ self: Int32, _ newValueWord0: Int32, _ newValueWord1: Int32, _ newValueWord2: Int32) -> Void { + return bjs_Animal_name_set_extern(self, newValueWord0, newValueWord1, newValueWord2) } #if arch(wasm32) @@ -19287,10 +19455,10 @@ fileprivate func bjs_Animal_getIsCat_extern(_ self: Int32) -> Int32 { } func _$Animal_init(_ name: String, _ age: Double, _ isCat: Bool) throws(JSException) -> JSObject { - let ret0 = name.bridgeJSWithLoweredParameter { (nameBytes, nameLength) in + let ret0 = name.bridgeJSWithLoweredParameter { (nameWord0, nameWord1, nameWord2) in let isCatValue = isCat.bridgeJSLowerParameter() let ageValue = age.bridgeJSLowerParameter() - let ret = bjs_Animal_init(nameBytes, nameLength, ageValue, isCatValue) + let ret = bjs_Animal_init(nameWord0, nameWord1, nameWord2, ageValue, isCatValue) return ret } let ret = ret0 @@ -19328,9 +19496,9 @@ func _$Animal_isCat_get(_ self: JSObject) throws(JSException) -> Bool { } func _$Animal_name_set(_ self: JSObject, _ newValue: String) throws(JSException) -> Void { - newValue.bridgeJSWithLoweredParameter { (newValueBytes, newValueLength) in + newValue.bridgeJSWithLoweredParameter { (newValueWord0, newValueWord1, newValueWord2) in let selfValue = self.bridgeJSLowerParameter() - bjs_Animal_name_set(selfValue, newValueBytes, newValueLength) + bjs_Animal_name_set(selfValue, newValueWord0, newValueWord1, newValueWord2) } if let error = _swift_js_take_exception() { throw error @@ -19504,21 +19672,21 @@ func _$jsJoinEnumThenArray(_ a: ImportedPayloadSignal, _ b: [Int]) throws(JSExce #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsJoinStringThenStackParams") -fileprivate func bjs_jsJoinStringThenStackParams_extern(_ sBytes: Int32, _ sLength: Int32) -> Int32 +fileprivate func bjs_jsJoinStringThenStackParams_extern(_ sWord0: Int32, _ sWord1: Int32, _ sWord2: Int32) -> Int32 #else -fileprivate func bjs_jsJoinStringThenStackParams_extern(_ sBytes: Int32, _ sLength: Int32) -> Int32 { +fileprivate func bjs_jsJoinStringThenStackParams_extern(_ sWord0: Int32, _ sWord1: Int32, _ sWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_jsJoinStringThenStackParams(_ sBytes: Int32, _ sLength: Int32) -> Int32 { - return bjs_jsJoinStringThenStackParams_extern(sBytes, sLength) +@inline(never) fileprivate func bjs_jsJoinStringThenStackParams(_ sWord0: Int32, _ sWord1: Int32, _ sWord2: Int32) -> Int32 { + return bjs_jsJoinStringThenStackParams_extern(sWord0, sWord1, sWord2) } func _$jsJoinStringThenStackParams(_ s: String, _ a: Optional<[Int]>, _ b: [Int]) throws(JSException) -> String { - let ret0 = s.bridgeJSWithLoweredParameter { (sBytes, sLength) in + let ret0 = s.bridgeJSWithLoweredParameter { (sWord0, sWord1, sWord2) in let _ = b.bridgeJSLowerParameter() let _ = a.bridgeJSLowerParameter() - let ret = bjs_jsJoinStringThenStackParams(sBytes, sLength) + let ret = bjs_jsJoinStringThenStackParams(sWord0, sWord1, sWord2) return ret } let ret = ret0 @@ -19572,19 +19740,19 @@ func _$jsGenericRoundTripClass(_ value: T) thr #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_jsGenericParsePoint") -fileprivate func bjs_jsGenericParsePoint_extern(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void +fileprivate func bjs_jsGenericParsePoint_extern(_ jsonWord0: Int32, _ jsonWord1: Int32, _ jsonWord2: Int32, _ _generic0TypeId: Int32) -> Void #else -fileprivate func bjs_jsGenericParsePoint_extern(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void { +fileprivate func bjs_jsGenericParsePoint_extern(_ jsonWord0: Int32, _ jsonWord1: Int32, _ jsonWord2: Int32, _ _generic0TypeId: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_jsGenericParsePoint(_ jsonBytes: Int32, _ jsonLength: Int32, _ _generic0TypeId: Int32) -> Void { - return bjs_jsGenericParsePoint_extern(jsonBytes, jsonLength, _generic0TypeId) +@inline(never) fileprivate func bjs_jsGenericParsePoint(_ jsonWord0: Int32, _ jsonWord1: Int32, _ jsonWord2: Int32, _ _generic0TypeId: Int32) -> Void { + return bjs_jsGenericParsePoint_extern(jsonWord0, jsonWord1, jsonWord2, _generic0TypeId) } func _$jsGenericParsePoint(_ json: String) throws(JSException) -> T { - json.bridgeJSWithLoweredParameter { (jsonBytes, jsonLength) in - bjs_jsGenericParsePoint(jsonBytes, jsonLength, T.bridgeJSTypeID) + json.bridgeJSWithLoweredParameter { (jsonWord0, jsonWord1, jsonWord2) in + bjs_jsGenericParsePoint(jsonWord0, jsonWord1, jsonWord2, T.bridgeJSTypeID) } if let error = _swift_js_take_exception() { throw error @@ -20398,19 +20566,19 @@ func _$defaultExport_get() throws(JSException) -> JSObject { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_nodeBasename") -fileprivate func bjs_nodeBasename_extern(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 +fileprivate func bjs_nodeBasename_extern(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 #else -fileprivate func bjs_nodeBasename_extern(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 { +fileprivate func bjs_nodeBasename_extern(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_nodeBasename(_ pathBytes: Int32, _ pathLength: Int32) -> Int32 { - return bjs_nodeBasename_extern(pathBytes, pathLength) +@inline(never) fileprivate func bjs_nodeBasename(_ pathWord0: Int32, _ pathWord1: Int32, _ pathWord2: Int32) -> Int32 { + return bjs_nodeBasename_extern(pathWord0, pathWord1, pathWord2) } func _$nodeBasename(_ path: String) throws(JSException) -> String { - let ret0 = path.bridgeJSWithLoweredParameter { (pathBytes, pathLength) in - let ret = bjs_nodeBasename(pathBytes, pathLength) + let ret0 = path.bridgeJSWithLoweredParameter { (pathWord0, pathWord1, pathWord2) in + let ret = bjs_nodeBasename(pathWord0, pathWord1, pathWord2) return ret } let ret = ret0 @@ -20422,20 +20590,20 @@ func _$nodeBasename(_ path: String) throws(JSException) -> String { #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_nodeJoin") -fileprivate func bjs_nodeJoin_extern(_ lhsBytes: Int32, _ lhsLength: Int32, _ rhsBytes: Int32, _ rhsLength: Int32) -> Int32 +fileprivate func bjs_nodeJoin_extern(_ lhsWord0: Int32, _ lhsWord1: Int32, _ lhsWord2: Int32, _ rhsWord0: Int32, _ rhsWord1: Int32, _ rhsWord2: Int32) -> Int32 #else -fileprivate func bjs_nodeJoin_extern(_ lhsBytes: Int32, _ lhsLength: Int32, _ rhsBytes: Int32, _ rhsLength: Int32) -> Int32 { +fileprivate func bjs_nodeJoin_extern(_ lhsWord0: Int32, _ lhsWord1: Int32, _ lhsWord2: Int32, _ rhsWord0: Int32, _ rhsWord1: Int32, _ rhsWord2: Int32) -> Int32 { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_nodeJoin(_ lhsBytes: Int32, _ lhsLength: Int32, _ rhsBytes: Int32, _ rhsLength: Int32) -> Int32 { - return bjs_nodeJoin_extern(lhsBytes, lhsLength, rhsBytes, rhsLength) +@inline(never) fileprivate func bjs_nodeJoin(_ lhsWord0: Int32, _ lhsWord1: Int32, _ lhsWord2: Int32, _ rhsWord0: Int32, _ rhsWord1: Int32, _ rhsWord2: Int32) -> Int32 { + return bjs_nodeJoin_extern(lhsWord0, lhsWord1, lhsWord2, rhsWord0, rhsWord1, rhsWord2) } func _$nodeJoin(_ lhs: String, _ rhs: String) throws(JSException) -> String { - let ret0 = lhs.bridgeJSWithLoweredParameter { (lhsBytes, lhsLength) in - let ret1 = rhs.bridgeJSWithLoweredParameter { (rhsBytes, rhsLength) in - let ret = bjs_nodeJoin(lhsBytes, lhsLength, rhsBytes, rhsLength) + let ret0 = lhs.bridgeJSWithLoweredParameter { (lhsWord0, lhsWord1, lhsWord2) in + let ret1 = rhs.bridgeJSWithLoweredParameter { (rhsWord0, rhsWord1, rhsWord2) in + let ret = bjs_nodeJoin(lhsWord0, lhsWord1, lhsWord2, rhsWord0, rhsWord1, rhsWord2) return ret } return ret1 @@ -20979,26 +21147,26 @@ fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalNumberUndefined_s #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static") -fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static_extern(_ nameIsSome: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void +fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static_extern(_ nameIsSome: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void #else -fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static_extern(_ nameIsSome: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { +fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static_extern(_ nameIsSome: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static(_ nameIsSome: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { - return bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static_extern(nameIsSome, nameBytes, nameLength) +@inline(never) fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static(_ nameIsSome: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void { + return bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static_extern(nameIsSome, nameWord0, nameWord1, nameWord2) } #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static") -fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static_extern(_ nameIsSome: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void +fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static_extern(_ nameIsSome: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void #else -fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static_extern(_ nameIsSome: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { +fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static_extern(_ nameIsSome: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void { fatalError("Only available on WebAssembly") } #endif -@inline(never) fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static(_ nameIsSome: Int32, _ nameBytes: Int32, _ nameLength: Int32) -> Void { - return bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static_extern(nameIsSome, nameBytes, nameLength) +@inline(never) fileprivate func bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static(_ nameIsSome: Int32, _ nameWord0: Int32, _ nameWord1: Int32, _ nameWord2: Int32) -> Void { + return bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static_extern(nameIsSome, nameWord0, nameWord1, nameWord2) } #if arch(wasm32) @@ -21092,8 +21260,8 @@ func _$OptionalSupportImports_jsRoundTripOptionalNumberUndefined(_ value: JSUnde } func _$OptionalSupportImports_jsRoundTripOptionalStringNull(_ name: Optional) throws(JSException) -> Optional { - name.bridgeJSWithLoweredParameter { (nameIsSome, nameBytes, nameLength) in - bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static(nameIsSome, nameBytes, nameLength) + name.bridgeJSWithLoweredParameter { (nameIsSome, nameWord0, nameWord1, nameWord2) in + bjs_OptionalSupportImports_jsRoundTripOptionalStringNull_static(nameIsSome, nameWord0, nameWord1, nameWord2) } if let error = _swift_js_take_exception() { throw error @@ -21102,8 +21270,8 @@ func _$OptionalSupportImports_jsRoundTripOptionalStringNull(_ name: Optional) throws(JSException) -> JSUndefinedOr { - name.bridgeJSWithLoweredParameter { (nameIsSome, nameBytes, nameLength) in - bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static(nameIsSome, nameBytes, nameLength) + name.bridgeJSWithLoweredParameter { (nameIsSome, nameWord0, nameWord1, nameWord2) in + bjs_OptionalSupportImports_jsRoundTripOptionalStringUndefined_static(nameIsSome, nameWord0, nameWord1, nameWord2) } if let error = _swift_js_take_exception() { throw error @@ -21163,6 +21331,92 @@ func _$OptionalSupportImports_runJsOptionalSupportTests() throws(JSException) -> } } +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StringABIImports_jsEcho_static") +fileprivate func bjs_StringABIImports_jsEcho_static_extern(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 +#else +fileprivate func bjs_StringABIImports_jsEcho_static_extern(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_StringABIImports_jsEcho_static(_ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Int32 { + return bjs_StringABIImports_jsEcho_static_extern(valueWord0, valueWord1, valueWord2) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StringABIImports_jsEchoOptional_static") +fileprivate func bjs_StringABIImports_jsEchoOptional_static_extern(_ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void +#else +fileprivate func bjs_StringABIImports_jsEchoOptional_static_extern(_ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_StringABIImports_jsEchoOptional_static(_ valueIsSome: Int32, _ valueWord0: Int32, _ valueWord1: Int32, _ valueWord2: Int32) -> Void { + return bjs_StringABIImports_jsEchoOptional_static_extern(valueIsSome, valueWord0, valueWord1, valueWord2) +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StringABIImports_jsEchoArray_static") +fileprivate func bjs_StringABIImports_jsEchoArray_static_extern() -> Void +#else +fileprivate func bjs_StringABIImports_jsEchoArray_static_extern() -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_StringABIImports_jsEchoArray_static() -> Void { + return bjs_StringABIImports_jsEchoArray_static_extern() +} + +#if arch(wasm32) +@_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_StringABIImports_runJsStringABITests_static") +fileprivate func bjs_StringABIImports_runJsStringABITests_static_extern() -> Void +#else +fileprivate func bjs_StringABIImports_runJsStringABITests_static_extern() -> Void { + fatalError("Only available on WebAssembly") +} +#endif +@inline(never) fileprivate func bjs_StringABIImports_runJsStringABITests_static() -> Void { + return bjs_StringABIImports_runJsStringABITests_static_extern() +} + +func _$StringABIImports_jsEcho(_ value: String) throws(JSException) -> String { + let ret0 = value.bridgeJSWithLoweredParameter { (valueWord0, valueWord1, valueWord2) in + let ret = bjs_StringABIImports_jsEcho_static(valueWord0, valueWord1, valueWord2) + return ret + } + let ret = ret0 + if let error = _swift_js_take_exception() { + throw error + } + return String.bridgeJSLiftReturn(ret) +} + +func _$StringABIImports_jsEchoOptional(_ value: Optional) throws(JSException) -> Optional { + value.bridgeJSWithLoweredParameter { (valueIsSome, valueWord0, valueWord1, valueWord2) in + bjs_StringABIImports_jsEchoOptional_static(valueIsSome, valueWord0, valueWord1, valueWord2) + } + if let error = _swift_js_take_exception() { + throw error + } + return Optional.bridgeJSLiftReturnFromSideChannel() +} + +func _$StringABIImports_jsEchoArray(_ value: [String]) throws(JSException) -> [String] { + let _ = value.bridgeJSLowerParameter() + bjs_StringABIImports_jsEchoArray_static() + if let error = _swift_js_take_exception() { + throw error + } + return [String].bridgeJSLiftReturn() +} + +func _$StringABIImports_runJsStringABITests() throws(JSException) -> Void { + bjs_StringABIImports_runJsStringABITests_static() + if let error = _swift_js_take_exception() { + throw error + } +} + #if arch(wasm32) @_extern(wasm, module: "BridgeJSRuntimeTests", name: "bjs_gc") fileprivate func bjs_gc_extern() -> Void diff --git a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json index 4749c6eb3..d649d4e76 100644 --- a/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json +++ b/Tests/BridgeJSRuntimeTests/Generated/JavaScript/BridgeJS.json @@ -13085,6 +13085,447 @@ ], "swiftCallName" : "APIOptionalResult", "tsFullPath" : "APIOptionalResult" + }, + { + "cases" : [ + + ], + "emitStyle" : "const", + "name" : "StringABIExports", + "staticMethods" : [ + { + "abiName" : "bjs_StringABIExports_static_echo", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "echo", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_echoOptional", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "echoOptional", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "nullable" : { + "_0" : { + "string" : { + + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "string" : { + + } + }, + "_1" : "null" + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_echoArray", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "echoArray", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + { + "label" : "_", + "name" : "value", + "type" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_smallEmpty", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "smallEmpty", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_smallASCII", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "smallASCII", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_smallEight", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "smallEight", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_smallNine", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "smallNine", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_smallTen", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "smallTen", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_smallUTF8", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "smallUTF8", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_largeImmortal", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "largeImmortal", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_largeImmortalAgain", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "largeImmortalAgain", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_largeDynamic", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "largeDynamic", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_immortalSubstring", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "immortalSubstring", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "string" : { + + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_optionalNone", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "optionalNone", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "nullable" : { + "_0" : { + "string" : { + + } + }, + "_1" : "null" + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + }, + { + "abiName" : "bjs_StringABIExports_static_optionalSome", + "effects" : { + "isAsync" : false, + "isStatic" : true, + "isThrows" : false + }, + "name" : "optionalSome", + "namespace" : [ + "StringABIExports" + ], + "parameters" : [ + + ], + "returnType" : { + "nullable" : { + "_0" : { + "string" : { + + } + }, + "_1" : "null" + } + }, + "staticContext" : { + "namespaceEnum" : { + "_0" : "StringABIExports" + } + } + } + ], + "staticProperties" : [ + + ], + "swiftCallName" : "StringABIExports", + "tsFullPath" : "StringABIExports" } ], "exposeToGlobal" : false, @@ -27823,6 +28264,135 @@ } ] }, + { + "functions" : [ + + ], + "types" : [ + { + "accessLevel" : "internal", + "getters" : [ + + ], + "methods" : [ + + ], + "name" : "StringABIImports", + "setters" : [ + + ], + "staticMethods" : [ + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "jsEcho", + "parameters" : [ + { + "name" : "value", + "type" : { + "string" : { + + } + } + } + ], + "returnType" : { + "string" : { + + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "jsEchoOptional", + "parameters" : [ + { + "name" : "value", + "type" : { + "nullable" : { + "_0" : { + "string" : { + + } + }, + "_1" : "null" + } + } + } + ], + "returnType" : { + "nullable" : { + "_0" : { + "string" : { + + } + }, + "_1" : "null" + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "jsEchoArray", + "parameters" : [ + { + "name" : "value", + "type" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + } + ], + "returnType" : { + "array" : { + "_0" : { + "string" : { + + } + } + } + } + }, + { + "accessLevel" : "internal", + "effects" : { + "isAsync" : false, + "isStatic" : false, + "isThrows" : true + }, + "name" : "runJsStringABITests", + "parameters" : [ + + ], + "returnType" : { + "void" : { + + } + } + } + ] + } + ] + }, { "functions" : [ { diff --git a/Tests/BridgeJSRuntimeTests/JavaScript/StringABITests.mjs b/Tests/BridgeJSRuntimeTests/JavaScript/StringABITests.mjs new file mode 100644 index 000000000..8468e1faf --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/JavaScript/StringABITests.mjs @@ -0,0 +1,53 @@ +// @ts-check + +import assert from 'node:assert'; + +/** + * @returns {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Imports["StringABIImports"]} + */ +export function getImports(importsContext) { + return { + jsEcho: (value) => value, + jsEchoArray: (value) => value, + jsEchoOptional: (value) => value ?? null, + runJsStringABITests: () => { + const exports = importsContext.getExports(); + if (!exports) { throw new Error("No exports!?"); } + runJsStringABITests(exports); + }, + }; +} + +/** + * Swift→JS string layout coverage: small, immortal, dynamic, substring, optional. + * @param {import('../../../.build/plugins/PackageToJS/outputs/PackageTests/bridge-js.d.ts').Exports} rootExports + */ +export function runJsStringABITests(rootExports) { + const exports = rootExports.StringABIExports; + const immortal = "this is a long immortal literal string"; + + assert.equal(exports.smallEmpty(), ""); + assert.equal(exports.smallASCII(), "div"); + assert.equal(exports.smallEight(), "abcdefgh"); + assert.equal(exports.smallNine(), "abcdefghi"); + assert.equal(exports.smallTen(), "abcdefghij"); + assert.equal(exports.smallUTF8(), "é"); + assert.equal(exports.largeImmortal(), immortal); + assert.equal(exports.largeImmortalAgain(), immortal); + assert.equal(exports.largeDynamic(), immortal); + assert.equal(exports.immortalSubstring(), immortal.slice(10)); + assert.equal(exports.optionalNone(), null); + assert.equal(exports.optionalSome(), immortal); + const expected = samples.map((s) => s.startsWith("\ufeff") ? s.slice(1) : s); + for (let i = 0; i < samples.length; i++) { + assert.equal(exports.echo(samples[i]), expected[i]); + assert.equal(exports.echoOptional(samples[i]), expected[i]); + } + assert.deepEqual(exports.echoArray(samples), expected); +} + +const samples = [ + "abcdefé", "abcdefgé", "abcdefghé", "abcdefghié", "abc€", "abcdefg€", + "abc😄", "abcdef😄", "abcdefg😄", "a\0b", "\ufeffx", "x\ufeff", + "\ufeff\ufeffx", "\ufeffabcdefghijk", "abc\ufeffdefghijk", "\ufeff\ufeffabcdefghijk", +]; diff --git a/Tests/BridgeJSRuntimeTests/StringABITests.swift b/Tests/BridgeJSRuntimeTests/StringABITests.swift new file mode 100644 index 000000000..c3f424011 --- /dev/null +++ b/Tests/BridgeJSRuntimeTests/StringABITests.swift @@ -0,0 +1,81 @@ +import XCTest +import JavaScriptKit +import JavaScriptEventLoop + +private let largeImmortalLiteral = "this is a long immortal literal string" + +@inline(never) +private func runtimeCopy(_ string: String) -> String { + String(decoding: Array(string.utf8), as: UTF8.self) +} + +@JSClass struct StringABIImports { + @JSFunction static func jsEcho(_ value: String) throws(JSException) -> String + @JSFunction static func jsEchoOptional(_ value: String?) throws(JSException) -> String? + @JSFunction static func jsEchoArray(_ value: [String]) throws(JSException) -> [String] + @JSFunction static func runJsStringABITests() throws(JSException) +} + +@JS enum StringABIExports { + @JS static func echo(_ value: String) -> String { value } + @JS static func echoOptional(_ value: String?) -> String? { value } + @JS static func echoArray(_ value: [String]) -> [String] { value } + @JS static func smallEmpty() -> String { "" } + @JS static func smallASCII() -> String { "div" } + @JS static func smallEight() -> String { "abcdefgh" } + @JS static func smallNine() -> String { "abcdefghi" } + @JS static func smallTen() -> String { "abcdefghij" } + @JS static func smallUTF8() -> String { "é" } + @JS static func largeImmortal() -> String { largeImmortalLiteral } + @JS static func largeImmortalAgain() -> String { largeImmortalLiteral } + @JS static func largeDynamic() -> String { runtimeCopy(largeImmortalLiteral) } + @JS static func immortalSubstring() -> String { String(largeImmortalLiteral.dropFirst(10)) } + @JS static func optionalNone() -> String? { nil } + @JS static func optionalSome() -> String? { largeImmortalLiteral } +} + +final class StringABITests: XCTestCase { + func testRunJsStringABITests() throws { + try StringABIImports.runJsStringABITests() + } + + func testUnicodeAndBOMParametersAndStacks() throws { + let samples = [ + "abcdefé", "abcdefgé", "abcdefghé", "abcdefghié", + "abc€", "abcdefg€", "abc😄", "abcdef😄", "abcdefg😄", "a\0b", + "\u{FEFF}x", "x\u{FEFF}", "\u{FEFF}\u{FEFF}x", + "\u{FEFF}abcdefghijk", "abc\u{FEFF}defghijk", "\u{FEFF}\u{FEFF}abcdefghijk", + ] + let expected = samples.map { $0.first == "\u{FEFF}" ? String($0.dropFirst()) : $0 } + for (input, output) in zip(samples, expected) { + try XCTAssertEqual(StringABIImports.jsEcho(input), output) + try XCTAssertEqual(StringABIImports.jsEchoOptional(input), output) + } + try XCTAssertEqual(StringABIImports.jsEchoArray(samples), expected) + } + + func testImportEchoesSmallStrings() throws { + try XCTAssertEqual(StringABIImports.jsEcho(""), "") + try XCTAssertEqual(StringABIImports.jsEcho("div"), "div") + try XCTAssertEqual(StringABIImports.jsEcho("abcdefgh"), "abcdefgh") + try XCTAssertEqual(StringABIImports.jsEcho("abcdefghi"), "abcdefghi") + try XCTAssertEqual(StringABIImports.jsEcho("abcdefghij"), "abcdefghij") + try XCTAssertEqual(StringABIImports.jsEcho("é"), "é") + } + + func testImportEchoesLargeImmortalAndDynamicStrings() throws { + try XCTAssertEqual(StringABIImports.jsEcho(largeImmortalLiteral), largeImmortalLiteral) + try XCTAssertEqual(StringABIImports.jsEcho(largeImmortalLiteral), largeImmortalLiteral) + try XCTAssertEqual(StringABIImports.jsEcho(runtimeCopy(largeImmortalLiteral)), largeImmortalLiteral) + try XCTAssertEqual( + StringABIImports.jsEcho(String(largeImmortalLiteral.dropFirst(10))), + String(largeImmortalLiteral.dropFirst(10)) + ) + } + + func testImportEchoesOptionalStrings() throws { + try XCTAssertNil(StringABIImports.jsEchoOptional(nil)) + try XCTAssertEqual(StringABIImports.jsEchoOptional("div"), "div") + try XCTAssertEqual(StringABIImports.jsEchoOptional(largeImmortalLiteral), largeImmortalLiteral) + } +} diff --git a/Tests/prelude.mjs b/Tests/prelude.mjs index 5287df791..8a109bcb5 100644 --- a/Tests/prelude.mjs +++ b/Tests/prelude.mjs @@ -11,6 +11,7 @@ import { getImports as getClosureThrowsImports } from './BridgeJSRuntimeTests/Ja import { getImports as getClosureAsyncImports } from './BridgeJSRuntimeTests/JavaScript/ClosureAsyncTests.mjs'; import { getImports as getSwiftClassSupportImports } from './BridgeJSRuntimeTests/JavaScript/SwiftClassSupportTests.mjs'; import { getImports as getOptionalSupportImports } from './BridgeJSRuntimeTests/JavaScript/OptionalSupportTests.mjs'; +import { getImports as getStringABIImports } from './BridgeJSRuntimeTests/JavaScript/StringABITests.mjs'; import { getImports as getArraySupportImports, ArrayElementObject } from './BridgeJSRuntimeTests/JavaScript/ArraySupportTests.mjs'; import { getImports as getDictionarySupportImports } from './BridgeJSRuntimeTests/JavaScript/DictionarySupportTests.mjs'; import { getImports as getDefaultArgumentImports } from './BridgeJSRuntimeTests/JavaScript/DefaultArgumentTests.mjs'; @@ -212,6 +213,7 @@ export async function setupOptions(options, context) { ClosureAsyncImports: getClosureAsyncImports(importsContext), SwiftClassSupportImports: getSwiftClassSupportImports(importsContext), OptionalSupportImports: getOptionalSupportImports(importsContext), + StringABIImports: getStringABIImports(importsContext), ArraySupportImports: getArraySupportImports(importsContext), DictionarySupportImports: getDictionarySupportImports(importsContext), DefaultArgumentImports: getDefaultArgumentImports(importsContext), @@ -289,6 +291,12 @@ function BridgeJSRuntimeTests_runJsWorks(instance, exports) { assert.equal(exports.roundTripBool(v), v); } for (const v of [ + "", + "div", + "abcdefgh", + "abcdefghi", + "abcdefghij", + "é", "Hello, world!", "😄", "こんにちは",