Skip to content

[JExtract] Lazy Cdecl lowering and Java translation #252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 6, 2025
Merged
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
24 changes: 6 additions & 18 deletions Sources/JExtractSwift/ImportedDecls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,17 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {

public var swiftDecl: any DeclSyntaxProtocol

var translatedSignature: TranslatedFunctionSignature

package var apiKind: SwiftAPIKind

var functionSignature: SwiftFunctionSignature

public var signatureString: String {
self.swiftDecl.signatureString
}

var loweredSignature: LoweredFunctionSignature {
translatedSignature.loweredSignature
}

var swiftSignature: SwiftFunctionSignature {
loweredSignature.original
}

package func cFunctionDecl(cName: String) -> CFunction {
// 'try!' because we know 'loweredSignature' can be described with C.
try! loweredSignature.cFunctionDecl(cName: cName)
}

var parentType: SwiftType? {
guard let selfParameter = swiftSignature.selfParameter else {
guard let selfParameter = functionSignature.selfParameter else {
return nil
}
switch selfParameter {
Expand All @@ -91,7 +79,7 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
/// this will contain that declaration's imported name.
///
/// This is necessary when rendering accessor Java code we need the type that "self" is expecting to have.
public var hasParent: Bool { translatedSignature.selfParameter != nil }
public var hasParent: Bool { functionSignature.selfParameter != nil }

/// A display name to use to refer to the Swift declaration with its
/// enclosing type, if there is one.
Expand All @@ -116,13 +104,13 @@ public final class ImportedFunc: ImportedDecl, CustomStringConvertible {
swiftDecl: any DeclSyntaxProtocol,
name: String,
apiKind: SwiftAPIKind,
translatedSignature: TranslatedFunctionSignature
functionSignature: SwiftFunctionSignature
) {
self.module = module
self.name = name
self.swiftDecl = swiftDecl
self.apiKind = apiKind
self.translatedSignature = translatedSignature
self.functionSignature = functionSignature
}

public var description: String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ extension Swift2JavaTranslator {
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
guard let _ = translatedSignature(for: decl) else {
// Failed to translate. Skip.
return
}

printer.printSeparator(decl.displayName)

printJavaBindingDescriptorClass(&printer, decl)
Expand All @@ -33,7 +38,9 @@ extension Swift2JavaTranslator {
_ decl: ImportedFunc
) {
let thunkName = thunkNameRegistry.functionThunkName(decl: decl)
let cFunc = decl.cFunctionDecl(cName: thunkName)
let translatedSignature = self.translatedSignature(for: decl)!
// 'try!' because we know 'loweredSignature' can be described with C.
let cFunc = try! translatedSignature.loweredSignature.cFunctionDecl(cName: thunkName)

printer.printBraceBlock(
"""
Expand Down Expand Up @@ -129,19 +136,20 @@ extension Swift2JavaTranslator {
}

var modifiers = "public"
switch decl.swiftSignature.selfParameter {
switch decl.functionSignature.selfParameter {
case .staticMethod, .initializer, nil:
modifiers.append(" static")
default:
break
}

let returnTy = decl.translatedSignature.result.javaResultType
let translatedSignature = self.translatedSignature(for: decl)!
let returnTy = translatedSignature.result.javaResultType

var paramDecls = decl.translatedSignature.parameters
var paramDecls = translatedSignature.parameters
.flatMap(\.javaParameters)
.map { "\($0.type) \($0.name)" }
if decl.translatedSignature.requiresSwiftArena {
if translatedSignature.requiresSwiftArena {
paramDecls.append("SwiftArena swiftArena$")
}

Expand All @@ -157,7 +165,7 @@ extension Swift2JavaTranslator {
\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", ")))
"""
) { printer in
if case .instance(_) = decl.swiftSignature.selfParameter {
if case .instance(_) = decl.functionSignature.selfParameter {
// Make sure the object has not been destroyed.
printer.print("$ensureAlive();")
}
Expand All @@ -174,7 +182,9 @@ extension Swift2JavaTranslator {
_ decl: ImportedFunc
) {
//=== Part 1: prepare temporary arena if needed.
if decl.translatedSignature.requiresTemporaryArena {
let translatedSignature = self.translatedSignature(for: decl)!

if translatedSignature.requiresTemporaryArena {
printer.print("try(var arena$ = Arena.ofConfined()) {")
printer.indent();
}
Expand All @@ -183,21 +193,21 @@ extension Swift2JavaTranslator {
var downCallArguments: [String] = []

// Regular parameters.
for (i, parameter) in decl.translatedSignature.parameters.enumerated() {
let original = decl.swiftSignature.parameters[i]
for (i, parameter) in translatedSignature.parameters.enumerated() {
let original = decl.functionSignature.parameters[i]
let parameterName = original.parameterName ?? "_\(i)"
let lowered = parameter.conversion.render(&printer, parameterName)
downCallArguments.append(lowered)
}

// 'self' parameter.
if let selfParameter = decl.translatedSignature.selfParameter {
if let selfParameter = translatedSignature.selfParameter {
let lowered = selfParameter.conversion.render(&printer, "this")
downCallArguments.append(lowered)
}

// Indirect return receivers.
for outParameter in decl.translatedSignature.result.outParameters {
for outParameter in translatedSignature.result.outParameters {
let memoryLayout = renderMemoryLayoutValue(for: outParameter.type)

let arena = if let className = outParameter.type.className,
Expand All @@ -222,27 +232,27 @@ extension Swift2JavaTranslator {
let downCall = "\(thunkName).call(\(downCallArguments.joined(separator: ", ")))"

//=== Part 4: Convert the return value.
if decl.translatedSignature.result.javaResultType == .void {
if translatedSignature.result.javaResultType == .void {
printer.print("\(downCall);")
} else {
let placeholder: String
if decl.translatedSignature.result.outParameters.isEmpty {
if translatedSignature.result.outParameters.isEmpty {
placeholder = downCall
} else {
// FIXME: Support cdecl thunk returning a value while populating the out parameters.
printer.print("\(downCall);")
placeholder = "_result"
}
let result = decl.translatedSignature.result.conversion.render(&printer, placeholder)
let result = translatedSignature.result.conversion.render(&printer, placeholder)

if decl.translatedSignature.result.javaResultType != .void {
if translatedSignature.result.javaResultType != .void {
printer.print("return \(result);")
} else {
printer.print("\(result);")
}
}

if decl.translatedSignature.requiresTemporaryArena {
if translatedSignature.requiresTemporaryArena {
printer.outdent()
printer.print("}")
}
Expand Down
25 changes: 18 additions & 7 deletions Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,26 @@
import JavaTypes

extension Swift2JavaTranslator {
func translate(
swiftSignature: SwiftFunctionSignature
) throws -> TranslatedFunctionSignature {
let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes)
let loweredSignature = try lowering.lowerFunctionSignature(swiftSignature)
func translatedSignature(
for decl: ImportedFunc
) -> TranslatedFunctionSignature? {
if let cached = translatedSignatures[decl] {
return cached
}

let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes)
let translated = try translation.translate(loweredFunctionSignature: loweredSignature)
let translated: TranslatedFunctionSignature?
do {
let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes)
let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature)

let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes)
translated = try translation.translate(loweredFunctionSignature: loweredSignature)
} catch {
self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)")
translated = nil
}

translatedSignatures[decl] = translated
return translated
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,13 @@ struct SwiftThunkTranslator {

func render(forFunc decl: ImportedFunc) -> [DeclSyntax] {
st.log.trace("Rendering thunks for: \(decl.displayName)")

let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl)
let thunkFunc = decl.loweredSignature.cdeclThunk(
guard let translatedSignatures = st.translatedSignature(for: decl) else {
return []
}

let thunkFunc = translatedSignatures.loweredSignature.cdeclThunk(
cName: thunkName,
swiftAPIName: decl.name,
as: decl.apiKind,
Expand Down
3 changes: 3 additions & 0 deletions Sources/JExtractSwift/Swift2JavaTranslator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public final class Swift2JavaTranslator {

package var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()

/// Cached Java translation result. 'nil' indicates failed translation.
var translatedSignatures: [ImportedFunc: TranslatedFunctionSignature?] = [:]

/// The name of the Swift module being translated.
var swiftModuleName: String {
symbolTable.moduleName
Expand Down
41 changes: 16 additions & 25 deletions Sources/JExtractSwift/Swift2JavaVisitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,15 @@ final class Swift2JavaVisitor: SyntaxVisitor {

self.log.debug("Import function: '\(node.qualifiedNameForDebug)'")

let translatedSignature: TranslatedFunctionSignature
let signature: SwiftFunctionSignature
do {
let swiftSignature = try SwiftFunctionSignature(
signature = try SwiftFunctionSignature(
node,
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)
translatedSignature = try translator.translate(swiftSignature: swiftSignature)
} catch {
self.log.debug("Failed to translate: '\(node.qualifiedNameForDebug)'; \(error)")
self.log.debug("Failed to import: '\(node.qualifiedNameForDebug)'; \(error)")
return .skipChildren
}

Expand All @@ -139,7 +138,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
swiftDecl: node,
name: node.name.text,
apiKind: .function,
translatedSignature: translatedSignature
functionSignature: signature
)

log.debug("Record imported method \(node.qualifiedNameForDebug)")
Expand All @@ -166,26 +165,19 @@ final class Swift2JavaVisitor: SyntaxVisitor {
self.log.debug("Import variable: \(node.kind) '\(node.qualifiedNameForDebug)'")

func importAccessor(kind: SwiftAPIKind) throws {
let translatedSignature: TranslatedFunctionSignature
do {
let swiftSignature = try SwiftFunctionSignature(
node,
isSet: kind == .setter,
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)
translatedSignature = try translator.translate(swiftSignature: swiftSignature)
} catch {
self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
throw error
}
let signature = try SwiftFunctionSignature(
node,
isSet: kind == .setter,
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)

let imported = ImportedFunc(
module: translator.swiftModuleName,
swiftDecl: node,
name: varName,
apiKind: kind,
translatedSignature: translatedSignature
functionSignature: signature
)

log.debug("Record imported variable accessor \(kind == .getter ? "getter" : "setter"):\(node.qualifiedNameForDebug)")
Expand All @@ -205,7 +197,7 @@ final class Swift2JavaVisitor: SyntaxVisitor {
try importAccessor(kind: .setter)
}
} catch {
self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)")
return .skipChildren
}

Expand All @@ -222,24 +214,23 @@ final class Swift2JavaVisitor: SyntaxVisitor {

self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'")

let translatedSignature: TranslatedFunctionSignature
let signature: SwiftFunctionSignature
do {
let swiftSignature = try SwiftFunctionSignature(
signature = try SwiftFunctionSignature(
node,
enclosingType: self.currentSwiftType,
symbolTable: self.translator.symbolTable
)
translatedSignature = try translator.translate(swiftSignature: swiftSignature)
} catch {
self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)")
self.log.debug("Failed to import: \(node.qualifiedNameForDebug); \(error)")
return .skipChildren
}
let imported = ImportedFunc(
module: translator.swiftModuleName,
swiftDecl: node,
name: "init",
apiKind: .initializer,
translatedSignature: translatedSignature
functionSignature: signature
)

currentType.initializers.append(imported)
Expand Down
2 changes: 1 addition & 1 deletion Sources/JExtractSwift/ThunkNameRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ package struct ThunkNameRegistry {
case .setter:
suffix = "$set"
default:
suffix = decl.swiftSignature.parameters
suffix = decl.functionSignature.parameters
.map { "_" + ($0.argumentLabel ?? "_") }
.joined()
}
Expand Down