Skip to content

Commit 731032d

Browse files
Enabled import-side arrays. Updated Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift to allow array lowering/lifting (stack-based, no ABI params) and to handle stack-only parameters in CallJSEmission. Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift now lifts array parameters and lowers array returns for imported JS functions using existing array fragments. Sources/JavaScriptKit/BridgeJSIntrinsics.swift gives Array stack bridging conformance plus import helpers (bridgeJSLowerParameter, bridgeJSLiftReturn, bridgeJSLowerStackReturn), covering nested arrays. Added Plugins/BridgeJS/Tests/BridgeJSToolTests/Inputs/MacroSwift/ImportArray.swift with corresponding snapshots for JSFunction array imports.
Tests: `UPDATE_SNAPSHOTS=1 swift test --package-path ./Plugins/BridgeJS --filter BridgeJSCodegenTests` followed by `swift test --package-path ./Plugins/BridgeJS --filter BridgeJSCodegenTests` (pass). Next steps: 1) Run the full BridgeJS test suite (`swift test --package-path ./Plugins/BridgeJS`) to ensure wider coverage. 2) Consider runtime-side coverage (e.g., `make unittest` with `JAVASCRIPTKIT_EXPERIMENTAL_BRIDGEJS=1`) if you want to exercise the new import path end-to-end.
1 parent 89ed56e commit 731032d

File tree

15 files changed

+1427
-63
lines changed

15 files changed

+1427
-63
lines changed

Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ public struct ImportTS {
8383
var abiReturnType: WasmCoreType?
8484
// Track destructured variable names for multiple lowered parameters
8585
var destructuredVarNames: [String] = []
86+
// Stack-lowered parameters should be evaluated in reverse order to match LIFO stacks
87+
var stackLoweringStmts: [CodeBlockItemSyntax] = []
8688

8789
init(moduleName: String, abiName: String, context: BridgeContext = .importTS) {
8890
self.moduleName = moduleName
@@ -93,11 +95,6 @@ public struct ImportTS {
9395
func lowerParameter(param: Parameter) throws {
9496
let loweringInfo = try param.type.loweringParameterInfo(context: context)
9597

96-
// Generate destructured variable names for all lowered parameters
97-
let destructuredNames = loweringInfo.loweredParameters.map {
98-
"\(param.name)\($0.name.capitalizedFirstLetter)"
99-
}
100-
10198
let initializerExpr: ExprSyntax
10299
switch param.type {
103100
case .closure(let signature):
@@ -108,6 +105,34 @@ public struct ImportTS {
108105
initializerExpr = ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()")
109106
}
110107

108+
if loweringInfo.loweredParameters.isEmpty {
109+
let stmt = CodeBlockItemSyntax(
110+
item: .decl(
111+
DeclSyntax(
112+
VariableDeclSyntax(
113+
bindingSpecifier: .keyword(.let),
114+
bindings: PatternBindingListSyntax {
115+
PatternBindingSyntax(
116+
pattern: PatternSyntax(
117+
IdentifierPatternSyntax(identifier: .wildcardToken())
118+
),
119+
initializer: InitializerClauseSyntax(value: initializerExpr)
120+
)
121+
}
122+
)
123+
)
124+
)
125+
)
126+
// Insert at the front so the last parameter ends up on the stack first
127+
stackLoweringStmts.insert(stmt, at: 0)
128+
return
129+
}
130+
131+
// Generate destructured variable names for all lowered parameters
132+
let destructuredNames = loweringInfo.loweredParameters.map {
133+
"\(param.name)\($0.name.capitalizedFirstLetter)"
134+
}
135+
111136
// Always add destructuring statement to body (unified for single and multiple)
112137
let pattern: PatternSyntax
113138
if destructuredNames.count == 1 {
@@ -166,6 +191,9 @@ public struct ImportTS {
166191
}
167192

168193
func call(returnType: BridgeType) throws {
194+
let liftingInfo = try returnType.liftingReturnInfo(context: context)
195+
// Stack-based lowerings must run in reverse parameter order (already reversed)
196+
body.append(contentsOf: stackLoweringStmts)
169197
// Build function call expression
170198
let callExpr = FunctionCallExprSyntax(
171199
calledExpression: ExprSyntax("\(raw: abiName)"),
@@ -183,6 +211,9 @@ public struct ImportTS {
183211
} else if returnType.usesSideChannelForOptionalReturn() {
184212
// Side channel returns don't need "let ret ="
185213
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
214+
} else if liftingInfo.valueToLift == nil {
215+
// Value is returned via stacks, no direct ABI return
216+
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
186217
} else {
187218
body.append("let ret = \(raw: callExpr)")
188219
}
@@ -900,13 +931,9 @@ extension BridgeType {
900931
return LoweringParameterInfo(loweredParameters: [("value", .i32)])
901932
}
902933
case .rawValueEnum(_, let rawType):
903-
switch context {
904-
case .importTS:
905-
return LoweringParameterInfo(loweredParameters: [("value", rawType.wasmCoreType ?? .i32)])
906-
case .exportSwift:
907-
// For protocol export we return .i32 for String raw value type instead of nil
908-
return LoweringParameterInfo(loweredParameters: [("value", rawType.wasmCoreType ?? .i32)])
909-
}
934+
// Both importTS and exportSwift lower raw-value enums the same way
935+
let wasmType = rawType.wasmCoreType ?? .i32
936+
return LoweringParameterInfo(loweredParameters: [("value", wasmType)])
910937
case .associatedValueEnum:
911938
switch context {
912939
case .importTS:
@@ -935,12 +962,7 @@ extension BridgeType {
935962
return LoweringParameterInfo(loweredParameters: params)
936963
}
937964
case .array:
938-
switch context {
939-
case .importTS:
940-
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
941-
case .exportSwift:
942-
return LoweringParameterInfo(loweredParameters: [])
943-
}
965+
return LoweringParameterInfo(loweredParameters: [])
944966
}
945967
}
946968

@@ -994,13 +1016,9 @@ extension BridgeType {
9941016
return LiftingReturnInfo(valueToLift: .i32)
9951017
}
9961018
case .rawValueEnum(_, let rawType):
997-
switch context {
998-
case .importTS:
999-
return LiftingReturnInfo(valueToLift: rawType.wasmCoreType ?? .i32)
1000-
case .exportSwift:
1001-
// For protocol export we return .i32 for String raw value type instead of nil
1002-
return LiftingReturnInfo(valueToLift: rawType.wasmCoreType ?? .i32)
1003-
}
1019+
// Both importTS and exportSwift lift raw-value enums identically
1020+
let wasmType = rawType.wasmCoreType ?? .i32
1021+
return LiftingReturnInfo(valueToLift: wasmType)
10041022
case .associatedValueEnum:
10051023
switch context {
10061024
case .importTS:
@@ -1027,12 +1045,7 @@ extension BridgeType {
10271045
return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift)
10281046
}
10291047
case .array:
1030-
switch context {
1031-
case .importTS:
1032-
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
1033-
case .exportSwift:
1034-
return LiftingReturnInfo(valueToLift: nil)
1035-
}
1048+
return LiftingReturnInfo(valueToLift: nil)
10361049
}
10371050
}
10381051
}

Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2146,12 +2146,10 @@ extension BridgeJSLink {
21462146

21472147
func liftParameter(param: Parameter) throws {
21482148
let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type, context: context)
2149-
assert(
2150-
liftingFragment.parameters.count >= 1,
2151-
"Lifting fragment should have at least one parameter to lift"
2152-
)
21532149
let valuesToLift: [String]
2154-
if liftingFragment.parameters.count == 1 {
2150+
if liftingFragment.parameters.isEmpty {
2151+
valuesToLift = []
2152+
} else if liftingFragment.parameters.count == 1 {
21552153
parameterNames.append(param.name)
21562154
valuesToLift = [scope.variable(param.name)]
21572155
} else {

Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,14 +1553,7 @@ struct IntrinsicJSFragment: Sendable {
15531553
"Namespace enums are not supported to be passed as parameters to imported JS functions: \(string)"
15541554
)
15551555
case .array(let elementType):
1556-
switch context {
1557-
case .importTS:
1558-
throw BridgeJSLinkError(
1559-
message: "Arrays are not yet supported to be passed as parameters to imported JS functions"
1560-
)
1561-
case .exportSwift:
1562-
return try arrayLift(elementType: elementType)
1563-
}
1556+
return try arrayLift(elementType: elementType)
15641557
}
15651558
}
15661559

@@ -1635,14 +1628,7 @@ struct IntrinsicJSFragment: Sendable {
16351628
message: "Namespace enums are not supported to be returned from imported JS functions: \(string)"
16361629
)
16371630
case .array(let elementType):
1638-
switch context {
1639-
case .importTS:
1640-
throw BridgeJSLinkError(
1641-
message: "Arrays are not yet supported to be returned from imported JS functions"
1642-
)
1643-
case .exportSwift:
1644-
return try arrayLower(elementType: elementType)
1645-
}
1631+
return try arrayLower(elementType: elementType)
16461632
}
16471633
}
16481634

@@ -2740,11 +2726,10 @@ struct IntrinsicJSFragment: Sendable {
27402726
// Lift return value if needed
27412727
if method.returnType != .void {
27422728
let liftFragment = try! IntrinsicJSFragment.liftReturn(type: method.returnType)
2743-
if !liftFragment.parameters.isEmpty {
2744-
let lifted = liftFragment.printCode(["ret"], methodScope, printer, methodCleanup)
2745-
if let liftedValue = lifted.first {
2746-
printer.write("return \(liftedValue);")
2747-
}
2729+
let liftArgs = liftFragment.parameters.isEmpty ? [] : ["ret"]
2730+
let lifted = liftFragment.printCode(liftArgs, methodScope, printer, methodCleanup)
2731+
if let liftedValue = lifted.first {
2732+
printer.write("return \(liftedValue);")
27482733
}
27492734
}
27502735
}
@@ -3074,7 +3059,7 @@ struct IntrinsicJSFragment: Sendable {
30743059
printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);")
30753060
cleanup.write("if (\(enumCleanupVar)) { \(enumCleanupVar)(); }")
30763061
default:
3077-
// For other types (nested structs, etc.), original logic applies
3062+
// For other types (including arrays), generate lowering under the isSome guard
30783063
let wrappedFragment = structFieldLowerFragment(
30793064
field: ExportedProperty(
30803065
name: field.name,
@@ -3084,9 +3069,37 @@ struct IntrinsicJSFragment: Sendable {
30843069
),
30853070
allStructs: allStructs
30863071
)
3072+
let guardedPrinter = CodeFragmentPrinter()
3073+
let guardedCleanup = CodeFragmentPrinter()
3074+
_ = wrappedFragment.printCode([value], scope, guardedPrinter, guardedCleanup)
3075+
var loweredLines = guardedPrinter.lines
3076+
var hoistedCleanupVar: String?
3077+
if let first = loweredLines.first {
3078+
let trimmed = first.trimmingCharacters(in: .whitespaces)
3079+
if trimmed.hasPrefix("const "),
3080+
let namePart = trimmed.split(separator: " ").dropFirst().first,
3081+
trimmed.contains("= []")
3082+
{
3083+
hoistedCleanupVar = String(namePart)
3084+
loweredLines[0] = "\(hoistedCleanupVar!) = [];"
3085+
}
3086+
}
3087+
if let hoistedName = hoistedCleanupVar {
3088+
printer.write("let \(hoistedName);")
3089+
}
30873090
printer.write("if (\(isSomeVar)) {")
30883091
printer.indent {
3089-
_ = wrappedFragment.printCode([value], scope, printer, cleanup)
3092+
for line in loweredLines {
3093+
printer.write(line)
3094+
}
3095+
// Cleanup is conditional on isSome as well
3096+
if !guardedCleanup.lines.isEmpty {
3097+
cleanup.write("if (\(isSomeVar)) {")
3098+
cleanup.indent {
3099+
cleanup.write(contentsOf: guardedCleanup)
3100+
}
3101+
cleanup.write("}")
3102+
}
30903103
}
30913104
printer.write("}")
30923105
printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@JSFunction func roundtrip(_ items: [Int]) throws(JSException) -> [Int]
2+
@JSFunction func logStrings(_ items: [String]) throws(JSException)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
{
2+
"exported" : {
3+
"classes" : [
4+
5+
],
6+
"enums" : [
7+
8+
],
9+
"exposeToGlobal" : false,
10+
"functions" : [
11+
12+
],
13+
"protocols" : [
14+
15+
],
16+
"structs" : [
17+
18+
]
19+
},
20+
"imported" : {
21+
"children" : [
22+
{
23+
"functions" : [
24+
{
25+
"name" : "roundtrip",
26+
"parameters" : [
27+
{
28+
"name" : "items",
29+
"type" : {
30+
"array" : {
31+
"_0" : {
32+
"int" : {
33+
34+
}
35+
}
36+
}
37+
}
38+
}
39+
],
40+
"returnType" : {
41+
"array" : {
42+
"_0" : {
43+
"int" : {
44+
45+
}
46+
}
47+
}
48+
}
49+
},
50+
{
51+
"name" : "logStrings",
52+
"parameters" : [
53+
{
54+
"name" : "items",
55+
"type" : {
56+
"array" : {
57+
"_0" : {
58+
"string" : {
59+
60+
}
61+
}
62+
}
63+
}
64+
}
65+
],
66+
"returnType" : {
67+
"void" : {
68+
69+
}
70+
}
71+
}
72+
],
73+
"types" : [
74+
75+
]
76+
}
77+
]
78+
},
79+
"moduleName" : "TestModule"
80+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#if arch(wasm32)
2+
@_extern(wasm, module: "TestModule", name: "bjs_roundtrip")
3+
fileprivate func bjs_roundtrip() -> Void
4+
#else
5+
fileprivate func bjs_roundtrip() -> Void {
6+
fatalError("Only available on WebAssembly")
7+
}
8+
#endif
9+
10+
func _$roundtrip(_ items: [Int]) throws(JSException) -> [Int] {
11+
let _ = items.bridgeJSLowerParameter()
12+
bjs_roundtrip()
13+
if let error = _swift_js_take_exception() {
14+
throw error
15+
}
16+
return [Int].bridgeJSLiftReturn()
17+
}
18+
19+
#if arch(wasm32)
20+
@_extern(wasm, module: "TestModule", name: "bjs_logStrings")
21+
fileprivate func bjs_logStrings() -> Void
22+
#else
23+
fileprivate func bjs_logStrings() -> Void {
24+
fatalError("Only available on WebAssembly")
25+
}
26+
#endif
27+
28+
func _$logStrings(_ items: [String]) throws(JSException) -> Void {
29+
let _ = items.bridgeJSLowerParameter()
30+
bjs_logStrings()
31+
if let error = _swift_js_take_exception() {
32+
throw error
33+
}
34+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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 type Exports = {
8+
}
9+
export type Imports = {
10+
roundtrip(items: number[]): number[];
11+
logStrings(items: string[]): void;
12+
}
13+
export function createInstantiator(options: {
14+
imports: Imports;
15+
}, swift: any): Promise<{
16+
addImports: (importObject: WebAssembly.Imports) => void;
17+
setInstance: (instance: WebAssembly.Instance) => void;
18+
createExports: (instance: WebAssembly.Instance) => Exports;
19+
}>;

0 commit comments

Comments
 (0)