Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 44 additions & 31 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ImportTS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public struct ImportTS {
var abiReturnType: WasmCoreType?
// Track destructured variable names for multiple lowered parameters
var destructuredVarNames: [String] = []
// Stack-lowered parameters should be evaluated in reverse order to match LIFO stacks
var stackLoweringStmts: [CodeBlockItemSyntax] = []

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

// Generate destructured variable names for all lowered parameters
let destructuredNames = loweringInfo.loweredParameters.map {
"\(param.name)\($0.name.capitalizedFirstLetter)"
}

let initializerExpr: ExprSyntax
switch param.type {
case .closure(let signature):
Expand All @@ -108,6 +105,34 @@ public struct ImportTS {
initializerExpr = ExprSyntax("\(raw: param.name).bridgeJSLowerParameter()")
}

if loweringInfo.loweredParameters.isEmpty {
let stmt = CodeBlockItemSyntax(
item: .decl(
DeclSyntax(
VariableDeclSyntax(
bindingSpecifier: .keyword(.let),
bindings: PatternBindingListSyntax {
PatternBindingSyntax(
pattern: PatternSyntax(
IdentifierPatternSyntax(identifier: .wildcardToken())
),
initializer: InitializerClauseSyntax(value: initializerExpr)
)
}
)
)
)
)
// Insert at the front so the last parameter ends up on the stack first
stackLoweringStmts.insert(stmt, at: 0)
return
}

// Generate destructured variable names for all lowered parameters
let destructuredNames = loweringInfo.loweredParameters.map {
"\(param.name)\($0.name.capitalizedFirstLetter)"
}

// Always add destructuring statement to body (unified for single and multiple)
let pattern: PatternSyntax
if destructuredNames.count == 1 {
Expand Down Expand Up @@ -166,6 +191,9 @@ public struct ImportTS {
}

func call(returnType: BridgeType) throws {
let liftingInfo = try returnType.liftingReturnInfo(context: context)
// Stack-based lowerings must run in reverse parameter order (already reversed)
body.append(contentsOf: stackLoweringStmts)
// Build function call expression
let callExpr = FunctionCallExprSyntax(
calledExpression: ExprSyntax("\(raw: abiName)"),
Expand All @@ -183,6 +211,9 @@ public struct ImportTS {
} else if returnType.usesSideChannelForOptionalReturn() {
// Side channel returns don't need "let ret ="
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
} else if liftingInfo.valueToLift == nil {
// Value is returned via stacks, no direct ABI return
body.append(CodeBlockItemSyntax(item: .stmt(StmtSyntax(ExpressionStmtSyntax(expression: callExpr)))))
} else {
body.append("let ret = \(raw: callExpr)")
}
Expand Down Expand Up @@ -922,13 +953,9 @@ extension BridgeType {
return LoweringParameterInfo(loweredParameters: [("value", .i32)])
}
case .rawValueEnum(_, let rawType):
switch context {
case .importTS:
return LoweringParameterInfo(loweredParameters: [("value", rawType.wasmCoreType ?? .i32)])
case .exportSwift:
// For protocol export we return .i32 for String raw value type instead of nil
return LoweringParameterInfo(loweredParameters: [("value", rawType.wasmCoreType ?? .i32)])
}
// Both importTS and exportSwift lower raw-value enums the same way
let wasmType = rawType.wasmCoreType ?? .i32
return LoweringParameterInfo(loweredParameters: [("value", wasmType)])
case .associatedValueEnum:
switch context {
case .importTS:
Expand All @@ -952,12 +979,7 @@ extension BridgeType {
params.append(contentsOf: wrappedInfo.loweredParameters)
return LoweringParameterInfo(loweredParameters: params)
case .array:
switch context {
case .importTS:
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
case .exportSwift:
return LoweringParameterInfo(loweredParameters: [])
}
return LoweringParameterInfo(loweredParameters: [])
}
}

Expand Down Expand Up @@ -1011,13 +1033,9 @@ extension BridgeType {
return LiftingReturnInfo(valueToLift: .i32)
}
case .rawValueEnum(_, let rawType):
switch context {
case .importTS:
return LiftingReturnInfo(valueToLift: rawType.wasmCoreType ?? .i32)
case .exportSwift:
// For protocol export we return .i32 for String raw value type instead of nil
return LiftingReturnInfo(valueToLift: rawType.wasmCoreType ?? .i32)
}
// Both importTS and exportSwift lift raw-value enums identically
let wasmType = rawType.wasmCoreType ?? .i32
return LiftingReturnInfo(valueToLift: wasmType)
case .associatedValueEnum:
switch context {
case .importTS:
Expand All @@ -1039,12 +1057,7 @@ extension BridgeType {
let wrappedInfo = try wrappedType.liftingReturnInfo(context: context)
return LiftingReturnInfo(valueToLift: wrappedInfo.valueToLift)
case .array:
switch context {
case .importTS:
throw BridgeJSCoreError("Array types are not yet supported in TypeScript imports")
case .exportSwift:
return LiftingReturnInfo(valueToLift: nil)
}
return LiftingReturnInfo(valueToLift: nil)
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2147,12 +2147,10 @@ extension BridgeJSLink {

func liftParameter(param: Parameter) throws {
let liftingFragment = try IntrinsicJSFragment.liftParameter(type: param.type, context: context)
assert(
liftingFragment.parameters.count >= 1,
"Lifting fragment should have at least one parameter to lift"
)
let valuesToLift: [String]
if liftingFragment.parameters.count == 1 {
if liftingFragment.parameters.isEmpty {
valuesToLift = []
} else if liftingFragment.parameters.count == 1 {
parameterNames.append(param.name)
valuesToLift = [scope.variable(param.name)]
} else {
Expand Down
59 changes: 36 additions & 23 deletions Plugins/BridgeJS/Sources/BridgeJSLink/JSGlueGen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1565,14 +1565,7 @@ struct IntrinsicJSFragment: Sendable {
"Namespace enums are not supported to be passed as parameters to imported JS functions: \(string)"
)
case .array(let elementType):
switch context {
case .importTS:
throw BridgeJSLinkError(
message: "Arrays are not yet supported to be passed as parameters to imported JS functions"
)
case .exportSwift:
return try arrayLift(elementType: elementType)
}
return try arrayLift(elementType: elementType)
}
}

Expand Down Expand Up @@ -1640,14 +1633,7 @@ struct IntrinsicJSFragment: Sendable {
message: "Namespace enums are not supported to be returned from imported JS functions: \(string)"
)
case .array(let elementType):
switch context {
case .importTS:
throw BridgeJSLinkError(
message: "Arrays are not yet supported to be returned from imported JS functions"
)
case .exportSwift:
return try arrayLower(elementType: elementType)
}
return try arrayLower(elementType: elementType)
}
}

Expand Down Expand Up @@ -2756,11 +2742,10 @@ struct IntrinsicJSFragment: Sendable {
// Lift return value if needed
if method.returnType != .void {
let liftFragment = try! IntrinsicJSFragment.liftReturn(type: method.returnType)
if !liftFragment.parameters.isEmpty {
let lifted = liftFragment.printCode(["ret"], methodScope, printer, methodCleanup)
if let liftedValue = lifted.first {
printer.write("return \(liftedValue);")
}
let liftArgs = liftFragment.parameters.isEmpty ? [] : ["ret"]
let lifted = liftFragment.printCode(liftArgs, methodScope, printer, methodCleanup)
if let liftedValue = lifted.first {
printer.write("return \(liftedValue);")
}
}
}
Expand Down Expand Up @@ -3090,7 +3075,7 @@ struct IntrinsicJSFragment: Sendable {
printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);")
cleanup.write("if (\(enumCleanupVar)) { \(enumCleanupVar)(); }")
default:
// For other types (nested structs, etc.), original logic applies
// For other types (including arrays), generate lowering under the isSome guard
let wrappedFragment = structFieldLowerFragment(
field: ExportedProperty(
name: field.name,
Expand All @@ -3100,9 +3085,37 @@ struct IntrinsicJSFragment: Sendable {
),
allStructs: allStructs
)
let guardedPrinter = CodeFragmentPrinter()
let guardedCleanup = CodeFragmentPrinter()
_ = wrappedFragment.printCode([value], scope, guardedPrinter, guardedCleanup)
var loweredLines = guardedPrinter.lines
var hoistedCleanupVar: String?
if let first = loweredLines.first {
let trimmed = first.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("const "),
let namePart = trimmed.split(separator: " ").dropFirst().first,
trimmed.contains("= []")
{
hoistedCleanupVar = String(namePart)
loweredLines[0] = "\(hoistedCleanupVar!) = [];"
}
}
if let hoistedName = hoistedCleanupVar {
printer.write("let \(hoistedName);")
}
printer.write("if (\(isSomeVar)) {")
printer.indent {
_ = wrappedFragment.printCode([value], scope, printer, cleanup)
for line in loweredLines {
printer.write(line)
}
// Cleanup is conditional on isSome as well
if !guardedCleanup.lines.isEmpty {
cleanup.write("if (\(isSomeVar)) {")
cleanup.indent {
cleanup.write(contentsOf: guardedCleanup)
}
cleanup.write("}")
}
}
printer.write("}")
printer.write("\(JSGlueVariableScope.reservedTmpParamInts).push(\(isSomeVar) ? 1 : 0);")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@JSFunction func roundtrip(_ items: [Int]) throws(JSException) -> [Int]
@JSFunction func logStrings(_ items: [String]) throws(JSException)
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"exported" : {
"classes" : [

],
"enums" : [

],
"exposeToGlobal" : false,
"functions" : [

],
"protocols" : [

],
"structs" : [

]
},
"imported" : {
"children" : [
{
"functions" : [
{
"name" : "roundtrip",
"parameters" : [
{
"name" : "items",
"type" : {
"array" : {
"_0" : {
"int" : {

}
}
}
}
}
],
"returnType" : {
"array" : {
"_0" : {
"int" : {

}
}
}
}
},
{
"name" : "logStrings",
"parameters" : [
{
"name" : "items",
"type" : {
"array" : {
"_0" : {
"string" : {

}
}
}
}
}
],
"returnType" : {
"void" : {

}
}
}
],
"types" : [

]
}
]
},
"moduleName" : "TestModule"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#if arch(wasm32)
@_extern(wasm, module: "TestModule", name: "bjs_roundtrip")
fileprivate func bjs_roundtrip() -> Void
#else
fileprivate func bjs_roundtrip() -> Void {
fatalError("Only available on WebAssembly")
}
#endif

func _$roundtrip(_ items: [Int]) throws(JSException) -> [Int] {
let _ = items.bridgeJSLowerParameter()
bjs_roundtrip()
if let error = _swift_js_take_exception() {
throw error
}
return [Int].bridgeJSLiftReturn()
}

#if arch(wasm32)
@_extern(wasm, module: "TestModule", name: "bjs_logStrings")
fileprivate func bjs_logStrings() -> Void
#else
fileprivate func bjs_logStrings() -> Void {
fatalError("Only available on WebAssembly")
}
#endif

func _$logStrings(_ items: [String]) throws(JSException) -> Void {
let _ = items.bridgeJSLowerParameter()
bjs_logStrings()
if let error = _swift_js_take_exception() {
throw error
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// NOTICE: This is auto-generated code by BridgeJS from JavaScriptKit,
// DO NOT EDIT.
//
// To update this file, just rebuild your project or run
// `swift package bridge-js`.

export type Exports = {
}
export type Imports = {
roundtrip(items: number[]): number[];
logStrings(items: string[]): void;
}
export function createInstantiator(options: {
imports: Imports;
}, swift: any): Promise<{
addImports: (importObject: WebAssembly.Imports) => void;
setInstance: (instance: WebAssembly.Instance) => void;
createExports: (instance: WebAssembly.Instance) => Exports;
}>;
Loading
Loading