Skip to content

Commit 7ca5dac

Browse files
committed
BridgeJS: Use descriptor-driven handling in JSGlueGen to eliminate per-type switches
1 parent 963bb20 commit 7ca5dac

File tree

12 files changed

+587
-740
lines changed

12 files changed

+587
-740
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1681,7 +1681,6 @@ extension BridgeType {
16811681
struct LiftingIntrinsicInfo: Sendable {
16821682
let parameters: [(name: String, type: WasmCoreType)]
16831683

1684-
16851684
}
16861685

16871686
func liftParameterInfo() throws -> LiftingIntrinsicInfo {

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 458 additions & 626 deletions
Large diffs are not rendered by default.

Plugins/BridgeJS/Sources/BridgeJSSkeleton/BridgeJSSkeleton.swift

Lines changed: 19 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,13 @@ public enum BridgeType: Codable, Equatable, Hashable, Sendable {
165165

166166
public enum WasmCoreType: String, Codable, Sendable {
167167
case i32, i64, f32, f64, pointer
168+
169+
public var jsZeroLiteral: String {
170+
switch self {
171+
case .f32, .f64: return "0.0"
172+
case .i32, .i64, .pointer: return "0"
173+
}
174+
}
168175
}
169176

170177
// MARK: - ABI Descriptor
@@ -182,67 +189,21 @@ public struct BridgeTypeDescriptor: Sendable {
182189
public let optionalUsesStackABI: Bool
183190
public let accessorTransform: AccessorTransform
184191
public let lowerMethod: LowerMethod
185-
public let jsGlue: JSGlue?
186-
187-
/// JS-specific coercion info for types with simple single-value ABI.
188-
///
189-
/// Types that have a `JSGlue` can be handled generically by JSGlueGen
190-
/// without per-type switch arms. Complex types (string, jsValue, closure,
191-
/// containers, jsObject, swiftHeapObject, etc.) return `nil`.
192-
///
193-
/// Coercion strings use `$0` as a placeholder for the value expression.
194-
public struct JSGlue: Sendable {
195-
/// Transform from WASM value to JS value (e.g. `"$0 !== 0"` for bool, `"$0 >>> 0"` for uint).
196-
/// `nil` means identity (no transform needed).
197-
/// Used for liftReturn, liftParameter, and stack pop in arrays/structs.
198-
public let liftCoerce: String?
199-
200-
/// Transform from JS value to WASM value for direct WASM returns (e.g. `"$0 ? 1 : 0"` for bool).
201-
/// `nil` means identity. Used for lowerReturn direction.
202-
public let lowerCoerce: String?
203-
204-
/// Transform from JS value when pushing onto a JS stack array.
205-
/// Defaults to `lowerCoerce` if nil. Types like int need `"($0 | 0)"` for stack
206-
/// push but identity for direct WASM return.
207-
public let stackLowerCoerce: String?
208-
209-
/// Variable name hint for generated code (e.g. "bool", "int", "f64").
210-
public let varHint: String
211-
212-
public init(
213-
liftCoerce: String? = nil,
214-
lowerCoerce: String? = nil,
215-
stackLowerCoerce: String? = nil,
216-
varHint: String
217-
) {
218-
self.liftCoerce = liftCoerce
219-
self.lowerCoerce = lowerCoerce
220-
self.stackLowerCoerce = stackLowerCoerce
221-
self.varHint = varHint
222-
}
223-
224-
/// The coercion to use when pushing a value onto a stack array.
225-
public var effectiveStackLowerCoerce: String? {
226-
stackLowerCoerce ?? lowerCoerce
227-
}
228-
}
229192

230193
public init(
231194
wasmParams: [(name: String, type: WasmCoreType)],
232195
wasmReturnType: WasmCoreType?,
233196
importReturnType: WasmCoreType?? = nil,
234197
optionalUsesStackABI: Bool,
235198
accessorTransform: AccessorTransform,
236-
lowerMethod: LowerMethod,
237-
jsGlue: JSGlue? = nil
199+
lowerMethod: LowerMethod
238200
) {
239201
self.wasmParams = wasmParams
240202
self.wasmReturnType = wasmReturnType
241203
self.importReturnType = importReturnType ?? wasmReturnType
242204
self.optionalUsesStackABI = optionalUsesStackABI
243205
self.accessorTransform = accessorTransform
244206
self.lowerMethod = lowerMethod
245-
self.jsGlue = jsGlue
246207
}
247208
}
248209

@@ -305,44 +266,39 @@ extension BridgeType {
305266
wasmReturnType: .i32,
306267
optionalUsesStackABI: false,
307268
accessorTransform: .identity,
308-
lowerMethod: .stackReturn,
309-
jsGlue: .init(liftCoerce: "$0 !== 0", lowerCoerce: "$0 ? 1 : 0", varHint: "bool")
269+
lowerMethod: .stackReturn
310270
)
311271
case .int:
312272
return BridgeTypeDescriptor(
313273
wasmParams: [("value", .i32)],
314274
wasmReturnType: .i32,
315275
optionalUsesStackABI: false,
316276
accessorTransform: .identity,
317-
lowerMethod: .stackReturn,
318-
jsGlue: .init(stackLowerCoerce: "($0 | 0)", varHint: "int")
277+
lowerMethod: .stackReturn
319278
)
320279
case .uint:
321280
return BridgeTypeDescriptor(
322281
wasmParams: [("value", .i32)],
323282
wasmReturnType: .i32,
324283
optionalUsesStackABI: false,
325284
accessorTransform: .identity,
326-
lowerMethod: .stackReturn,
327-
jsGlue: .init(liftCoerce: "$0 >>> 0", stackLowerCoerce: "($0 | 0)", varHint: "int")
285+
lowerMethod: .stackReturn
328286
)
329287
case .float:
330288
return BridgeTypeDescriptor(
331289
wasmParams: [("value", .f32)],
332290
wasmReturnType: .f32,
333291
optionalUsesStackABI: false,
334292
accessorTransform: .identity,
335-
lowerMethod: .stackReturn,
336-
jsGlue: .init(stackLowerCoerce: "Math.fround($0)", varHint: "f32")
293+
lowerMethod: .stackReturn
337294
)
338295
case .double:
339296
return BridgeTypeDescriptor(
340297
wasmParams: [("value", .f64)],
341298
wasmReturnType: .f64,
342299
optionalUsesStackABI: false,
343300
accessorTransform: .identity,
344-
lowerMethod: .stackReturn,
345-
jsGlue: .init(varHint: "f64")
301+
lowerMethod: .stackReturn
346302
)
347303
case .string:
348304
return BridgeTypeDescriptor(
@@ -385,8 +341,7 @@ extension BridgeType {
385341
wasmReturnType: .pointer,
386342
optionalUsesStackABI: false,
387343
accessorTransform: .identity,
388-
lowerMethod: .stackReturn,
389-
jsGlue: .init(stackLowerCoerce: "($0 | 0)", varHint: "pointer")
344+
lowerMethod: .stackReturn
390345
)
391346
case .swiftProtocol(let protocolName):
392347
return BridgeTypeDescriptor(
@@ -402,8 +357,7 @@ extension BridgeType {
402357
wasmReturnType: .i32,
403358
optionalUsesStackABI: false,
404359
accessorTransform: .identity,
405-
lowerMethod: .stackReturn,
406-
jsGlue: .init(stackLowerCoerce: "($0 | 0)", varHint: "caseId")
360+
lowerMethod: .stackReturn
407361
)
408362
case .rawValueEnum(_, let rawType):
409363
switch rawType {
@@ -422,40 +376,31 @@ extension BridgeType {
422376
wasmReturnType: .f32,
423377
optionalUsesStackABI: false,
424378
accessorTransform: .identity,
425-
lowerMethod: .stackReturn,
426-
jsGlue: .init(stackLowerCoerce: "Math.fround($0)", varHint: "rawValue")
379+
lowerMethod: .stackReturn
427380
)
428381
case .double:
429382
return BridgeTypeDescriptor(
430383
wasmParams: [("value", .f64)],
431384
wasmReturnType: .f64,
432385
optionalUsesStackABI: false,
433386
accessorTransform: .identity,
434-
lowerMethod: .stackReturn,
435-
jsGlue: .init(varHint: "rawValue")
387+
lowerMethod: .stackReturn
436388
)
437389
case .bool:
438390
return BridgeTypeDescriptor(
439391
wasmParams: [("value", .i32)],
440392
wasmReturnType: .i32,
441393
optionalUsesStackABI: false,
442394
accessorTransform: .identity,
443-
lowerMethod: .stackReturn,
444-
jsGlue: .init(
445-
liftCoerce: "$0 !== 0",
446-
lowerCoerce: "$0 ? 1 : 0",
447-
stackLowerCoerce: "$0 ? 1 : 0",
448-
varHint: "rawValue"
449-
)
395+
lowerMethod: .stackReturn
450396
)
451397
case .int, .int32, .int64, .uint, .uint32, .uint64:
452398
return BridgeTypeDescriptor(
453399
wasmParams: [("value", .i32)],
454400
wasmReturnType: .i32,
455401
optionalUsesStackABI: false,
456402
accessorTransform: .identity,
457-
lowerMethod: .stackReturn,
458-
jsGlue: .init(stackLowerCoerce: "($0 | 0)", varHint: "rawValue")
403+
lowerMethod: .stackReturn
459404
)
460405
}
461406
case .associatedValueEnum:

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,6 @@ export async function createInstantiator(options, swift) {
695695
if (isSome) {
696696
const { cleanup: structCleanup } = structHelpers.Point.lower(elem);
697697
arrayCleanups.push(() => { if (structCleanup) { structCleanup(); } });
698-
} else {
699698
}
700699
i32Stack.push(isSome);
701700
}

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

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -432,21 +432,22 @@ export async function createInstantiator(options, swift) {
432432
switch (enumTag) {
433433
case APIOptionalResultValues.Tag.Success: {
434434
const isSome = value.param0 != null;
435-
let id;
435+
let innerCleanup;
436436
if (isSome) {
437-
let bytes = textEncoder.encode(value.param0);
438-
id = swift.memory.retain(bytes);
437+
const bytes = textEncoder.encode(value.param0);
438+
const id = swift.memory.retain(bytes);
439439
i32Stack.push(bytes.length);
440440
i32Stack.push(id);
441+
innerCleanup = () => {
442+
swift.memory.release(id);
443+
};
441444
} else {
442445
i32Stack.push(0);
443446
i32Stack.push(0);
444447
}
445448
i32Stack.push(isSome ? 1 : 0);
446449
const cleanup = () => {
447-
if(id) {
448-
swift.memory.release(id);
449-
}
450+
if (innerCleanup) { innerCleanup(); }
450451
};
451452
return { caseId: APIOptionalResultValues.Tag.Success, cleanup };
452453
}
@@ -462,12 +463,15 @@ export async function createInstantiator(options, swift) {
462463
}
463464
case APIOptionalResultValues.Tag.Status: {
464465
const isSome = value.param2 != null;
465-
let id;
466+
let innerCleanup;
466467
if (isSome) {
467-
let bytes = textEncoder.encode(value.param2);
468-
id = swift.memory.retain(bytes);
468+
const bytes = textEncoder.encode(value.param2);
469+
const id = swift.memory.retain(bytes);
469470
i32Stack.push(bytes.length);
470471
i32Stack.push(id);
472+
innerCleanup = () => {
473+
swift.memory.release(id);
474+
};
471475
} else {
472476
i32Stack.push(0);
473477
i32Stack.push(0);
@@ -480,9 +484,7 @@ export async function createInstantiator(options, swift) {
480484
i32Stack.push(isSome2 ? (value.param0 ? 1 : 0) : 0);
481485
i32Stack.push(isSome2 ? 1 : 0);
482486
const cleanup = () => {
483-
if(id) {
484-
swift.memory.release(id);
485-
}
487+
if (innerCleanup) { innerCleanup(); }
486488
};
487489
return { caseId: APIOptionalResultValues.Tag.Status, cleanup };
488490
}
@@ -723,75 +725,82 @@ export async function createInstantiator(options, swift) {
723725
switch (enumTag) {
724726
case OptionalAllTypesResultValues.Tag.OptStruct: {
725727
const isSome = value.param0 != null;
726-
let nestedCleanup;
728+
let innerCleanup;
727729
if (isSome) {
728-
const structResult = structHelpers.Point.lower(value.param0);
729-
nestedCleanup = structResult.cleanup;
730+
const { cleanup: structCleanup } = structHelpers.Point.lower(value.param0);
731+
innerCleanup = () => {
732+
if (structCleanup) { structCleanup(); }
733+
};
730734
}
731735
i32Stack.push(isSome ? 1 : 0);
732736
const cleanup = () => {
733-
if (nestedCleanup) { nestedCleanup(); }
737+
if (innerCleanup) { innerCleanup(); }
734738
};
735739
return { caseId: OptionalAllTypesResultValues.Tag.OptStruct, cleanup };
736740
}
737741
case OptionalAllTypesResultValues.Tag.OptClass: {
738742
const isSome = value.param0 != null;
743+
let innerCleanup;
739744
if (isSome) {
740745
ptrStack.push(value.param0.pointer);
741746
} else {
742747
ptrStack.push(0);
743748
}
744749
i32Stack.push(isSome ? 1 : 0);
745-
const cleanup = undefined;
750+
const cleanup = () => {
751+
if (innerCleanup) { innerCleanup(); }
752+
};
746753
return { caseId: OptionalAllTypesResultValues.Tag.OptClass, cleanup };
747754
}
748755
case OptionalAllTypesResultValues.Tag.OptJSObject: {
749756
const isSome = value.param0 != null;
750-
let id;
757+
let innerCleanup;
751758
if (isSome) {
752-
id = swift.memory.retain(value.param0);
753-
i32Stack.push(id);
759+
const objId = swift.memory.retain(value.param0);
760+
i32Stack.push(objId);
754761
} else {
755-
id = undefined;
756762
i32Stack.push(0);
757763
}
758764
i32Stack.push(isSome ? 1 : 0);
759-
const cleanup = undefined;
765+
const cleanup = () => {
766+
if (innerCleanup) { innerCleanup(); }
767+
};
760768
return { caseId: OptionalAllTypesResultValues.Tag.OptJSObject, cleanup };
761769
}
762770
case OptionalAllTypesResultValues.Tag.OptNestedEnum: {
763771
const isSome = value.param0 != null;
764-
let enumCaseId, enumCleanup;
772+
let innerCleanup;
765773
if (isSome) {
766-
const enumResult = enumHelpers.APIResult.lower(value.param0);
767-
enumCaseId = enumResult.caseId;
768-
enumCleanup = enumResult.cleanup;
769-
i32Stack.push(enumCaseId);
774+
const { caseId: caseId, cleanup: enumCleanup } = enumHelpers.APIResult.lower(value.param0);
775+
i32Stack.push(caseId);
776+
innerCleanup = () => {
777+
if (enumCleanup) { enumCleanup(); }
778+
};
770779
} else {
771780
i32Stack.push(0);
772781
}
773782
i32Stack.push(isSome ? 1 : 0);
774783
const cleanup = () => {
775-
if (enumCleanup) { enumCleanup(); }
784+
if (innerCleanup) { innerCleanup(); }
776785
};
777786
return { caseId: OptionalAllTypesResultValues.Tag.OptNestedEnum, cleanup };
778787
}
779788
case OptionalAllTypesResultValues.Tag.OptArray: {
780789
const isSome = value.param0 != null;
781-
let arrCleanup;
790+
let innerCleanup;
782791
if (isSome) {
783792
const arrayCleanups = [];
784793
for (const elem of value.param0) {
785794
i32Stack.push((elem | 0));
786795
}
787796
i32Stack.push(value.param0.length);
788-
arrCleanup = () => {
797+
innerCleanup = () => {
789798
for (const cleanup of arrayCleanups) { cleanup(); }
790799
};
791800
}
792801
i32Stack.push(isSome ? 1 : 0);
793802
const cleanup = () => {
794-
if (arrCleanup) { arrCleanup(); }
803+
if (innerCleanup) { innerCleanup(); }
795804
};
796805
return { caseId: OptionalAllTypesResultValues.Tag.OptArray, cleanup };
797806
}

0 commit comments

Comments
 (0)