Skip to content

Commit 5931e7c

Browse files
BridgeJS: Support @JS Swift struct types in imported JS signatures
They are cloned via stack ABI
1 parent 61659cb commit 5931e7c

File tree

16 files changed

+1222
-404
lines changed

16 files changed

+1222
-404
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ struct StackCodegen {
881881
case .double:
882882
return ["_swift_js_push_f64(\(raw: accessor))"]
883883
case .jsObject:
884-
return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerParameter())"]
884+
return ["_swift_js_push_int(\(raw: accessor).bridgeJSLowerReturn())"]
885885
case .swiftHeapObject:
886886
return ["_swift_js_push_pointer(\(raw: accessor).bridgeJSLowerReturn())"]
887887
case .unsafePointer:
@@ -984,7 +984,7 @@ struct StackCodegen {
984984
case .associatedValueEnum:
985985
return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"]
986986
case .jsObject:
987-
return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerParameter())"]
987+
return ["_swift_js_push_int(\(raw: unwrappedVar).bridgeJSLowerReturn())"]
988988
default:
989989
return ["preconditionFailure(\"BridgeJS: unsupported optional wrapped type\")"]
990990
}

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,8 @@ extension BridgeType {
916916
case .swiftStruct:
917917
switch context {
918918
case .importTS:
919-
throw BridgeJSCoreError("Swift structs are not yet supported in TypeScript imports")
919+
// Swift structs are bridged as JS objects (object IDs) in imported signatures.
920+
return LoweringParameterInfo(loweredParameters: [("objectId", .i32)])
920921
case .exportSwift:
921922
return LoweringParameterInfo(loweredParameters: [])
922923
}
@@ -1002,7 +1003,8 @@ extension BridgeType {
10021003
case .swiftStruct:
10031004
switch context {
10041005
case .importTS:
1005-
throw BridgeJSCoreError("Swift structs are not yet supported in TypeScript imports")
1006+
// Swift structs are bridged as JS objects (object IDs) in imported signatures.
1007+
return LiftingReturnInfo(valueToLift: .i32)
10061008
case .exportSwift:
10071009
return LiftingReturnInfo(valueToLift: nil)
10081010
}

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,18 @@ struct IntrinsicJSFragment: Sendable {
204204
return [resultLabel]
205205
}
206206
)
207+
static let jsObjectLiftRetainedObjectId = IntrinsicJSFragment(
208+
parameters: ["objectId"],
209+
printCode: { arguments, scope, printer, cleanupCode in
210+
let resultLabel = scope.variable("value")
211+
let objectId = arguments[0]
212+
printer.write(
213+
"const \(resultLabel) = \(JSGlueVariableScope.reservedSwift).memory.getObject(\(objectId));"
214+
)
215+
printer.write("\(JSGlueVariableScope.reservedSwift).memory.release(\(objectId));")
216+
return [resultLabel]
217+
}
218+
)
207219
static let jsObjectLiftParameter = IntrinsicJSFragment(
208220
parameters: ["objectId"],
209221
printCode: { arguments, scope, printer, cleanupCode in
@@ -1446,10 +1458,7 @@ struct IntrinsicJSFragment: Sendable {
14461458
case .swiftStruct(let fullName):
14471459
switch context {
14481460
case .importTS:
1449-
throw BridgeJSLinkError(
1450-
message:
1451-
"Swift structs are not supported to be passed as parameters to imported JS functions: \(fullName)"
1452-
)
1461+
return .jsObjectLiftRetainedObjectId
14531462
case .exportSwift:
14541463
let base = fullName.components(separatedBy: ".").last ?? fullName
14551464
return IntrinsicJSFragment(
@@ -1527,10 +1536,8 @@ struct IntrinsicJSFragment: Sendable {
15271536
case .swiftStruct(let fullName):
15281537
switch context {
15291538
case .importTS:
1530-
throw BridgeJSLinkError(
1531-
message:
1532-
"Swift structs are not supported to be returned from imported JS functions: \(fullName)"
1533-
)
1539+
// ImportTS expects Swift structs to come back as a retained JS object ID.
1540+
return .jsObjectLowerReturn
15341541
case .exportSwift:
15351542
return swiftStructLowerReturn(fullName: fullName)
15361543
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@JS
2+
struct Point {
3+
var x: Int
4+
var y: Int
5+
}
6+
7+
@JSFunction func translate(_ point: Point, dx: Int, dy: Int) throws(JSException) -> Point
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
export interface Point {
8+
x: number;
9+
y: number;
10+
}
11+
export type Exports = {
12+
}
13+
export type Imports = {
14+
translate(point: Point, dx: number, dy: number): Point;
15+
}
16+
export function createInstantiator(options: {
17+
imports: Imports;
18+
}, swift: any): Promise<{
19+
addImports: (importObject: WebAssembly.Imports) => void;
20+
setInstance: (instance: WebAssembly.Instance) => void;
21+
createExports: (instance: WebAssembly.Instance) => Exports;
22+
}>;
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
2+
// DO NOT EDIT.
3+
//
4+
// To update this file, just rebuild your project or run
5+
// `swift package bridge-js`.
6+
7+
export async function createInstantiator(options, swift) {
8+
let instance;
9+
let memory;
10+
let setException;
11+
const textDecoder = new TextDecoder("utf-8");
12+
const textEncoder = new TextEncoder("utf-8");
13+
let tmpRetString;
14+
let tmpRetBytes;
15+
let tmpRetException;
16+
let tmpRetOptionalBool;
17+
let tmpRetOptionalInt;
18+
let tmpRetOptionalFloat;
19+
let tmpRetOptionalDouble;
20+
let tmpRetOptionalHeapObject;
21+
let tmpRetTag;
22+
let tmpRetStrings = [];
23+
let tmpRetInts = [];
24+
let tmpRetF32s = [];
25+
let tmpRetF64s = [];
26+
let tmpParamInts = [];
27+
let tmpParamF32s = [];
28+
let tmpParamF64s = [];
29+
let tmpRetPointers = [];
30+
let tmpParamPointers = [];
31+
let tmpStructCleanups = [];
32+
const enumHelpers = {};
33+
const structHelpers = {};
34+
35+
let _exports = null;
36+
let bjs = null;
37+
const __bjs_createPointHelpers = () => {
38+
return (tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers) => ({
39+
lower: (value) => {
40+
tmpParamInts.push((value.x | 0));
41+
tmpParamInts.push((value.y | 0));
42+
return { cleanup: undefined };
43+
},
44+
raise: (tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers) => {
45+
const int = tmpRetInts.pop();
46+
const int1 = tmpRetInts.pop();
47+
return { x: int1, y: int };
48+
}
49+
});
50+
};
51+
52+
return {
53+
/**
54+
* @param {WebAssembly.Imports} importObject
55+
*/
56+
addImports: (importObject, importsContext) => {
57+
bjs = {};
58+
importObject["bjs"] = bjs;
59+
const imports = options.getImports(importsContext);
60+
bjs["swift_js_return_string"] = function(ptr, len) {
61+
const bytes = new Uint8Array(memory.buffer, ptr, len);
62+
tmpRetString = textDecoder.decode(bytes);
63+
}
64+
bjs["swift_js_init_memory"] = function(sourceId, bytesPtr) {
65+
const source = swift.memory.getObject(sourceId);
66+
const bytes = new Uint8Array(memory.buffer, bytesPtr);
67+
bytes.set(source);
68+
}
69+
bjs["swift_js_make_js_string"] = function(ptr, len) {
70+
const bytes = new Uint8Array(memory.buffer, ptr, len);
71+
return swift.memory.retain(textDecoder.decode(bytes));
72+
}
73+
bjs["swift_js_init_memory_with_result"] = function(ptr, len) {
74+
const target = new Uint8Array(memory.buffer, ptr, len);
75+
target.set(tmpRetBytes);
76+
tmpRetBytes = undefined;
77+
}
78+
bjs["swift_js_throw"] = function(id) {
79+
tmpRetException = swift.memory.retainByRef(id);
80+
}
81+
bjs["swift_js_retain"] = function(id) {
82+
return swift.memory.retainByRef(id);
83+
}
84+
bjs["swift_js_release"] = function(id) {
85+
swift.memory.release(id);
86+
}
87+
bjs["swift_js_push_tag"] = function(tag) {
88+
tmpRetTag = tag;
89+
}
90+
bjs["swift_js_push_int"] = function(v) {
91+
tmpRetInts.push(v | 0);
92+
}
93+
bjs["swift_js_push_f32"] = function(v) {
94+
tmpRetF32s.push(Math.fround(v));
95+
}
96+
bjs["swift_js_push_f64"] = function(v) {
97+
tmpRetF64s.push(v);
98+
}
99+
bjs["swift_js_push_string"] = function(ptr, len) {
100+
const bytes = new Uint8Array(memory.buffer, ptr, len);
101+
const value = textDecoder.decode(bytes);
102+
tmpRetStrings.push(value);
103+
}
104+
bjs["swift_js_pop_param_int32"] = function() {
105+
return tmpParamInts.pop();
106+
}
107+
bjs["swift_js_pop_param_f32"] = function() {
108+
return tmpParamF32s.pop();
109+
}
110+
bjs["swift_js_pop_param_f64"] = function() {
111+
return tmpParamF64s.pop();
112+
}
113+
bjs["swift_js_push_pointer"] = function(pointer) {
114+
tmpRetPointers.push(pointer);
115+
}
116+
bjs["swift_js_pop_param_pointer"] = function() {
117+
return tmpParamPointers.pop();
118+
}
119+
bjs["swift_js_struct_cleanup"] = function(cleanupId) {
120+
if (cleanupId === 0) { return; }
121+
const index = (cleanupId | 0) - 1;
122+
const cleanup = tmpStructCleanups[index];
123+
tmpStructCleanups[index] = null;
124+
if (cleanup) { cleanup(); }
125+
while (tmpStructCleanups.length > 0 && tmpStructCleanups[tmpStructCleanups.length - 1] == null) {
126+
tmpStructCleanups.pop();
127+
}
128+
}
129+
bjs["swift_js_struct_lower_Point"] = function(objectId) {
130+
const { cleanup: cleanup } = structHelpers.Point.lower(swift.memory.getObject(objectId));
131+
if (cleanup) {
132+
return tmpStructCleanups.push(cleanup);
133+
}
134+
return 0;
135+
}
136+
bjs["swift_js_struct_raise_Point"] = function() {
137+
const value = structHelpers.Point.raise(tmpRetStrings, tmpRetInts, tmpRetF32s, tmpRetF64s, tmpRetPointers);
138+
return swift.memory.retain(value);
139+
}
140+
bjs["swift_js_return_optional_bool"] = function(isSome, value) {
141+
if (isSome === 0) {
142+
tmpRetOptionalBool = null;
143+
} else {
144+
tmpRetOptionalBool = value !== 0;
145+
}
146+
}
147+
bjs["swift_js_return_optional_int"] = function(isSome, value) {
148+
if (isSome === 0) {
149+
tmpRetOptionalInt = null;
150+
} else {
151+
tmpRetOptionalInt = value | 0;
152+
}
153+
}
154+
bjs["swift_js_return_optional_float"] = function(isSome, value) {
155+
if (isSome === 0) {
156+
tmpRetOptionalFloat = null;
157+
} else {
158+
tmpRetOptionalFloat = Math.fround(value);
159+
}
160+
}
161+
bjs["swift_js_return_optional_double"] = function(isSome, value) {
162+
if (isSome === 0) {
163+
tmpRetOptionalDouble = null;
164+
} else {
165+
tmpRetOptionalDouble = value;
166+
}
167+
}
168+
bjs["swift_js_return_optional_string"] = function(isSome, ptr, len) {
169+
if (isSome === 0) {
170+
tmpRetString = null;
171+
} else {
172+
const bytes = new Uint8Array(memory.buffer, ptr, len);
173+
tmpRetString = textDecoder.decode(bytes);
174+
}
175+
}
176+
bjs["swift_js_return_optional_object"] = function(isSome, objectId) {
177+
if (isSome === 0) {
178+
tmpRetString = null;
179+
} else {
180+
tmpRetString = swift.memory.getObject(objectId);
181+
}
182+
}
183+
bjs["swift_js_return_optional_heap_object"] = function(isSome, pointer) {
184+
if (isSome === 0) {
185+
tmpRetOptionalHeapObject = null;
186+
} else {
187+
tmpRetOptionalHeapObject = pointer;
188+
}
189+
}
190+
bjs["swift_js_get_optional_int_presence"] = function() {
191+
return tmpRetOptionalInt != null ? 1 : 0;
192+
}
193+
bjs["swift_js_get_optional_int_value"] = function() {
194+
const value = tmpRetOptionalInt;
195+
tmpRetOptionalInt = undefined;
196+
return value;
197+
}
198+
bjs["swift_js_get_optional_string"] = function() {
199+
const str = tmpRetString;
200+
tmpRetString = undefined;
201+
if (str == null) {
202+
return -1;
203+
} else {
204+
const bytes = textEncoder.encode(str);
205+
tmpRetBytes = bytes;
206+
return bytes.length;
207+
}
208+
}
209+
bjs["swift_js_get_optional_float_presence"] = function() {
210+
return tmpRetOptionalFloat != null ? 1 : 0;
211+
}
212+
bjs["swift_js_get_optional_float_value"] = function() {
213+
const value = tmpRetOptionalFloat;
214+
tmpRetOptionalFloat = undefined;
215+
return value;
216+
}
217+
bjs["swift_js_get_optional_double_presence"] = function() {
218+
return tmpRetOptionalDouble != null ? 1 : 0;
219+
}
220+
bjs["swift_js_get_optional_double_value"] = function() {
221+
const value = tmpRetOptionalDouble;
222+
tmpRetOptionalDouble = undefined;
223+
return value;
224+
}
225+
bjs["swift_js_get_optional_heap_object_pointer"] = function() {
226+
const pointer = tmpRetOptionalHeapObject;
227+
tmpRetOptionalHeapObject = undefined;
228+
return pointer || 0;
229+
}
230+
const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
231+
TestModule["bjs_translate"] = function bjs_translate(point, dx, dy) {
232+
try {
233+
const value = swift.memory.getObject(point);
234+
swift.memory.release(point);
235+
let ret = imports.translate(value, dx, dy);
236+
return swift.memory.retain(ret);
237+
} catch (error) {
238+
setException(error);
239+
}
240+
}
241+
},
242+
setInstance: (i) => {
243+
instance = i;
244+
memory = instance.exports.memory;
245+
246+
setException = (error) => {
247+
instance.exports._swift_js_exception.value = swift.memory.retain(error)
248+
}
249+
},
250+
/** @param {WebAssembly.Instance} instance */
251+
createExports: (instance) => {
252+
const js = swift.memory.heap;
253+
const PointHelpers = __bjs_createPointHelpers()(tmpParamInts, tmpParamF32s, tmpParamF64s, tmpParamPointers, tmpRetPointers, textEncoder, swift, enumHelpers);
254+
structHelpers.Point = PointHelpers;
255+
256+
const exports = {
257+
};
258+
_exports = exports;
259+
return exports;
260+
},
261+
}
262+
}

Plugins/BridgeJS/Tests/BridgeJSToolTests/__Snapshots__/ExportSwiftTests/SwiftStruct.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ extension Measurement: _BridgedSwiftStruct {
262262
}
263263

264264
func toJSObject() -> JSObject {
265-
var __bjs_self = self
265+
let __bjs_self = self
266266
__bjs_self.bridgeJSLowerReturn()
267267
return JSObject(id: UInt32(bitPattern: _bjs_struct_raise_Measurement()))
268268
}

0 commit comments

Comments
 (0)