From 7860cb89a66e95604686bce3fb6456ad00c4d65f Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 6 Jun 2025 16:13:02 -0700 Subject: [PATCH] [JExtract] Import any C compatible closure Generalized closure parameter import, part 1. Start with C-compatible closures which don't need any conversions. --- .../com/example/swift/MySwiftLibraryTest.java | 4 +- .../MySwiftLibrary/MySwiftStruct.swift | 4 + .../com/example/swift/HelloJava2Swift.java | 3 + .../com/example/swift/MySwiftLibraryTest.java | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 52 ++++- ...t2JavaGenerator+JavaBindingsPrinting.swift | 202 +++++++++++++++--- ...MSwift2JavaGenerator+JavaTranslation.swift | 168 ++++++++++++--- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 4 +- .../FFM/FFMSwift2JavaGenerator.swift | 15 +- .../java/org/swift/swiftkit/SwiftKit.java | 33 +-- .../FuncCallbackImportTests.swift | 178 ++++++++++++++- .../FunctionDescriptorImportTests.swift | 30 +-- .../StringPassingTests.swift | 6 +- .../VariableImportTests.swift | 14 +- 14 files changed, 591 insertions(+), 126 deletions(-) diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java index 82472418..74df5da8 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -43,9 +43,9 @@ void call_globalTakeInt() { void call_globalCallMeRunnable() { CountDownLatch countDownLatch = new CountDownLatch(3); - MySwiftLibrary.globalCallMeRunnable(new Runnable() { + MySwiftLibrary.globalCallMeRunnable(new MySwiftLibrary.globalCallMeRunnable.run() { @Override - public void run() { + public void apply() { countDownLatch.countDown(); } }); diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift index 8feb3d2b..363e0683 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -48,6 +48,10 @@ public struct MySwiftStruct { self.len } + public func withCapLen(_ body: (Int, Int) -> Void) { + body(cap, len) + } + public mutating func increaseCap(by value: Int) -> Int { precondition(value > 0) self.cap += value diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index e20ac378..69dfbdb3 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -67,6 +67,9 @@ static void examples() { MySwiftStruct swiftValue = MySwiftStruct.init(2222, 1111, arena); SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity()); + swiftValue.withCapLen((cap, len) -> { + SwiftKit.trace("withCapLenCallback: cap=" + cap + ", len=" + len); + }); } System.out.println("DONE."); diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java index 41d83305..2df53843 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -64,9 +64,9 @@ void call_writeString_jni() { void call_globalCallMeRunnable() { CountDownLatch countDownLatch = new CountDownLatch(3); - MySwiftLibrary.globalCallMeRunnable(new Runnable() { + MySwiftLibrary.globalCallMeRunnable(new MySwiftLibrary.globalCallMeRunnable.run() { @Override - public void run() { + public void apply() { countDownLatch.countDown(); } }); diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index cd5ea0a2..02501dc5 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -116,7 +116,7 @@ struct CdeclLowering { ) } - /// Lower a Swift function parameter type to cdecl parameters. + /// Lower a Swift function parameter to cdecl parameters. /// /// For example, Swift parameter `arg value: inout Int` can be lowered with /// `lowerParameter(intTy, .inout, "value")`. @@ -276,25 +276,63 @@ struct CdeclLowering { } return LoweredParameter(cdeclParameters: parameters, conversion: .tuplify(conversions)) - case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid: + case .function(let fn): + let (loweredTy, conversion) = try lowerFunctionType(fn) return LoweredParameter( cdeclParameters: [ SwiftParameter( convention: .byValue, parameterName: parameterName, - type: .function(SwiftFunctionType(convention: .c, parameters: [], resultType: fn.resultType)) + type: loweredTy ) ], - // '@convention(c) () -> ()' is compatible with '() -> Void'. - conversion: .placeholder + conversion: conversion ) - case .function, .optional: - // FIXME: Support other function types than '() -> Void'. + case .optional: throw LoweringError.unhandledType(type) } } + /// Lower a Swift function type (i.e. closure) to cdecl function type. + /// + /// - Parameters: + /// - fn: the Swift function type to lower. + func lowerFunctionType( + _ fn: SwiftFunctionType + ) throws -> (type: SwiftType, conversion: ConversionStep) { + var parameters: [SwiftParameter] = [] + var parameterConversions: [ConversionStep] = [] + + for parameter in fn.parameters { + if let _ = try? CType(cdeclType: parameter.type) { + parameters.append(SwiftParameter(convention: .byValue, type: parameter.type)) + parameterConversions.append(.placeholder) + } else { + // Non-trivial types are not yet supported. + throw LoweringError.unhandledType(.function(fn)) + } + } + + let resultType: SwiftType + let resultConversion: ConversionStep + if let _ = try? CType(cdeclType: fn.resultType) { + resultType = fn.resultType + resultConversion = .placeholder + } else { + // Non-trivial types are not yet supported. + throw LoweringError.unhandledType(.function(fn)) + } + + // Ignore the conversions for now, since we don't support non-trivial types yet. + _ = (parameterConversions, resultConversion) + + return ( + type: .function(SwiftFunctionType(convention: .c, parameters: parameters, resultType: resultType)), + conversion: .placeholder + ) + } + /// Lower a Swift result type to cdecl out parameters and return type. /// /// - Parameters: diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 77ab9a81..c7c6631a 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -15,11 +15,11 @@ import JavaTypes extension FFMSwift2JavaGenerator { - func printFunctionDowncallMethods( + package func printFunctionDowncallMethods( _ printer: inout CodePrinter, _ decl: ImportedFunc ) { - guard let _ = translatedSignature(for: decl) else { + guard let _ = translatedDecl(for: decl) else { // Failed to translate. Skip. return } @@ -28,6 +28,8 @@ extension FFMSwift2JavaGenerator { printJavaBindingDescriptorClass(&printer, decl) + printJavaBindingWrapperHelperClass(&printer, decl) + // Render the "make the downcall" functions. printJavaBindingWrapperMethod(&printer, decl) } @@ -38,9 +40,9 @@ extension FFMSwift2JavaGenerator { _ decl: ImportedFunc ) { let thunkName = thunkNameRegistry.functionThunkName(decl: decl) - let translatedSignature = self.translatedSignature(for: decl)! + let translated = self.translatedDecl(for: decl)! // 'try!' because we know 'loweredSignature' can be described with C. - let cFunc = try! translatedSignature.loweredSignature.cFunctionDecl(cName: thunkName) + let cFunc = try! translated.loweredSignature.cFunctionDecl(cName: thunkName) printer.printBraceBlock( """ @@ -52,37 +54,39 @@ extension FFMSwift2JavaGenerator { private static class \(cFunc.name) """ ) { printer in - printFunctionDescriptorValue(&printer, cFunc) + printFunctionDescriptorDefinition(&printer, cFunc.resultType, cFunc.parameters) printer.print( """ - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = \(self.swiftModuleName).findOrThrow("\(cFunc.name)"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); """ ) printJavaBindingDowncallMethod(&printer, cFunc) + printParameterDescriptorClasses(&printer, cFunc) } } /// Print the 'FunctionDescriptor' of the lowered cdecl thunk. - func printFunctionDescriptorValue( + func printFunctionDescriptorDefinition( _ printer: inout CodePrinter, - _ cFunc: CFunction + _ resultType: CType, + _ parameters: [CParameter] ) { - printer.start("public static final FunctionDescriptor DESC = ") + printer.start("private static final FunctionDescriptor DESC = ") - let isEmptyParam = cFunc.parameters.isEmpty - if cFunc.resultType.isVoid { + let isEmptyParam = parameters.isEmpty + if resultType.isVoid { printer.print("FunctionDescriptor.ofVoid(", isEmptyParam ? .continue : .newLine) printer.indent() } else { printer.print("FunctionDescriptor.of(") printer.indent() printer.print("/* -> */", .continue) - printer.print(cFunc.resultType.foreignValueLayout, .parameterNewlineSeparator(isEmptyParam)) + printer.print(resultType.foreignValueLayout, .parameterNewlineSeparator(isEmptyParam)) } - for (param, isLast) in cFunc.parameters.withIsLast { + for (param, isLast) in parameters.withIsLast { printer.print("/* \(param.name ?? "_"): */", .continue) printer.print(param.type.foreignValueLayout, .parameterNewlineSeparator(isLast)) } @@ -124,16 +128,158 @@ extension FFMSwift2JavaGenerator { ) } + /// Print required helper classes/interfaces for describing the CFunction. + /// + /// * function pointer parameter as a functional interface. + /// * Unnamed-struct parameter as a record. (unimplemented) + func printParameterDescriptorClasses( + _ printer: inout CodePrinter, + _ cFunc: CFunction + ) { + for param in cFunc.parameters { + switch param.type { + case .pointer(.function): + let name = "$\(param.name!)" + printFunctionPointerParameterDescriptorClass(&printer, name, param.type) + default: + continue + } + } + } + + /// Print a class describing a function pointer parameter type. + /// + /// ```java + /// class $ { + /// @FunctionalInterface + /// interface Function { + /// apply(); + /// } + /// static final MethodDescriptor DESC = FunctionDescriptor.of(...); + /// static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + /// static MemorySegment toUpcallStub(Function fi, Arena arena) { + /// return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + /// } + /// } + /// ``` + func printFunctionPointerParameterDescriptorClass( + _ printer: inout CodePrinter, + _ name: String, + _ cType: CType + ) { + guard case .pointer(.function(let cResultType, let cParameterTypes, variadic: false)) = cType else { + preconditionFailure("must be a C function pointer type; name=\(name), cType=\(cType)") + } + + let cParams = cParameterTypes.enumerated().map { i, ty in + CParameter(name: "_\(i)", type: ty) + } + let paramDecls = cParams.map({"\($0.type.javaType) \($0.name!)"}) + + printer.printBraceBlock( + """ + /** + * {snippet lang=c : + * \(cType) + * } + */ + private static class \(name) + """ + ) { printer in + printer.print( + """ + @FunctionalInterface + public interface Function { + \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", "))); + } + """ + ) + printFunctionDescriptorDefinition(&printer, cResultType, cParams) + printer.print( + """ + private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + """ + ) + } + } + + /// Print the helper type container for a user-facing Java API. + /// + /// * User-facing functional interfaces. + func printJavaBindingWrapperHelperClass( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let translated = self.translatedDecl(for: decl)! + let bindingDescriptorName = self.thunkNameRegistry.functionThunkName(decl: decl) + if translated.functionTypes.isEmpty { + return + } + + printer.printBraceBlock( + """ + public static class \(translated.name) + """ + ) { printer in + for functionType in translated.functionTypes { + printJavaBindingWrapperFunctionTypeHelper(&printer, functionType, bindingDescriptorName) + } + } + } + + /// Print "wrapper" functional interface representing a Swift closure type. + func printJavaBindingWrapperFunctionTypeHelper( + _ printer: inout CodePrinter, + _ functionType: TranslatedFunctionType, + _ bindingDescriptorName: String + ) { + let cdeclDescriptor = "\(bindingDescriptorName).$\(functionType.name)" + if functionType.isCompatibleWithC { + // If the user-facing functional interface is C ABI compatible, just extend + // the lowered function pointer parameter interface. + printer.print( + """ + @FunctionalInterface + public interface \(functionType.name) extends \(cdeclDescriptor).Function {} + private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) { + return \(bindingDescriptorName).$\(functionType.name).toUpcallStub(fi, arena); + } + """ + ) + } else { + // Otherwise, the lambda must be wrapped with the lowered function instance. + assertionFailure("should be unreachable at this point") + let apiParams = functionType.parameters.flatMap { + $0.javaParameters.map { param in "\(param.type) \(param.name)" } + } + + printer.print( + """ + @FunctionalInterface + public interface \(functionType.name) { + \(functionType.result.javaResultType) apply(\(apiParams.joined(separator: ", "))); + } + private static MemorySegment $toUpcallStub(\(functionType.name) fi, Arena arena) { + return \(cdeclDescriptor).toUpcallStub(() -> { + fi() + }, arena); + } + """ + ) + } + } + /// Print the calling body that forwards all the parameters to the `methodName`, /// with adding `SwiftArena.ofAuto()` at the end. - public func printJavaBindingWrapperMethod( + package func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, - _ decl: ImportedFunc) { - let methodName: String = switch decl.apiKind { - case .getter: "get\(decl.name.toCamelCase)" - case .setter: "set\(decl.name.toCamelCase)" - case .function, .initializer: decl.name - } + _ decl: ImportedFunc + ) { + let translated = self.translatedDecl(for: decl)! + let methodName = translated.name var modifiers = "public" switch decl.functionSignature.selfParameter { @@ -143,7 +289,7 @@ extension FFMSwift2JavaGenerator { break } - let translatedSignature = self.translatedSignature(for: decl)! + let translatedSignature = translated.translatedSignature let returnTy = translatedSignature.result.javaResultType var paramDecls = translatedSignature.parameters @@ -182,7 +328,7 @@ extension FFMSwift2JavaGenerator { _ decl: ImportedFunc ) { //=== Part 1: prepare temporary arena if needed. - let translatedSignature = self.translatedSignature(for: decl)! + let translatedSignature = self.translatedDecl(for: decl)!.translatedSignature if translatedSignature.requiresTemporaryArena { printer.print("try(var arena$ = Arena.ofConfined()) {") @@ -273,7 +419,7 @@ extension JavaConversionStep { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .pass, .swiftValueSelfSegment, .construct, .cast, .call: + case .pass, .swiftValueSelfSegment, .construct, .cast, .call, .method: return false case .constructSwiftValue: return true @@ -285,7 +431,7 @@ extension JavaConversionStep { switch self { case .pass, .swiftValueSelfSegment, .construct, .constructSwiftValue, .cast: return false - case .call(_, let withArena): + case .call(_, let withArena), .method(_, _, let withArena): return withArena } } @@ -297,7 +443,7 @@ extension JavaConversionStep { switch self { case .pass, .swiftValueSelfSegment: return true - case .cast, .construct, .constructSwiftValue, .call: + case .cast, .construct, .constructSwiftValue, .call, .method: return false } } @@ -317,6 +463,10 @@ extension JavaConversionStep { let arenaArg = withArena ? ", arena$" : "" return "\(function)(\(placeholder)\(arenaArg))" + case .method(let methodName, let arguments, let withArena): + let argsStr = (arguments + (withArena ? ["arena$"] : [])).joined(separator: " ,") + return "\(placeholder).\(methodName)(\(argsStr))" + case .constructSwiftValue(let javaType): return "new \(javaType.className!)(\(placeholder), swiftArena$)" diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 56eb606d..c7e2338d 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -15,26 +15,23 @@ import JavaTypes extension FFMSwift2JavaGenerator { - func translatedSignature( + func translatedDecl( for decl: ImportedFunc - ) -> TranslatedFunctionSignature? { - if let cached = translatedSignatures[decl] { + ) -> TranslatedFunctionDecl? { + if let cached = translatedDecls[decl] { return cached } - let translated: TranslatedFunctionSignature? + let translated: TranslatedFunctionDecl? 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) + translated = try translation.translate(decl) } catch { self.log.info("Failed to translate: '\(decl.swiftDecl.qualifiedNameForDebug)'; \(error)") translated = nil } - translatedSignatures[decl] = translated + translatedDecls[decl] = translated return translated } } @@ -82,19 +79,47 @@ struct TranslatedResult { var conversion: JavaConversionStep } -/// Translated function signature representing a Swift API. + +/// Translated Java API representing a Swift API. /// /// Since this holds the lowered signature, and the original `SwiftFunctionSignature` /// in it, this contains all the API information (except the name) to generate the /// cdecl thunk, Java binding, and the Java wrapper function. -struct TranslatedFunctionSignature { - var loweredSignature: LoweredFunctionSignature +struct TranslatedFunctionDecl { + /// Java function name. + let name: String + /// Functional interfaces required for the Java method. + let functionTypes: [TranslatedFunctionType] + + /// Function signature. + let translatedSignature: TranslatedFunctionSignature + + /// Cdecl lowerd signature. + let loweredSignature: LoweredFunctionSignature +} + +/// Function signature for a Java API. +struct TranslatedFunctionSignature { var selfParameter: TranslatedParameter? var parameters: [TranslatedParameter] var result: TranslatedResult } +/// Represent a Swift closure type in the user facing Java API. +/// +/// Closures are translated to named functional interfaces in Java. +struct TranslatedFunctionType { + var name: String + var parameters: [TranslatedParameter] + var result: TranslatedResult + + /// Whether or not this functional interface with C ABI compatible. + var isCompatibleWithC: Bool { + result.conversion.isPass && parameters.allSatisfy(\.conversion.isPass) + } +} + extension TranslatedFunctionSignature { /// Whether or not if the down-calling requires temporary "Arena" which is /// only used during the down-calling. @@ -121,12 +146,92 @@ extension TranslatedFunctionSignature { struct JavaTranslation { var swiftStdlibTypes: SwiftStandardLibraryTypes - /// Translate Swift API to user-facing Java API. + func translate( + _ decl: ImportedFunc + ) throws -> TranslatedFunctionDecl { + let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) + let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) + + // Name. + let javaName = switch decl.apiKind { + case .getter: "get\(decl.name.toCamelCase)" + case .setter: "set\(decl.name.toCamelCase)" + case .function, .initializer: decl.name + } + + // Closures. + var funcTypes: [TranslatedFunctionType] = [] + for (idx, param) in decl.functionSignature.parameters.enumerated() { + switch param.type { + case .function(let funcTy): + let paramName = param.parameterName ?? "_\(idx)" + let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy) + funcTypes.append(translatedClosure) + case .tuple: + // TODO: Implement + break + default: + break + } + } + + // Signature. + let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) + + return TranslatedFunctionDecl( + name: javaName, + functionTypes: funcTypes, + translatedSignature: translatedSignature, + loweredSignature: loweredSignature + ) + } + + /// Translate Swift closure type to Java functional interface. + func translateFunctionType( + name: String, + swiftType: SwiftFunctionType + ) throws -> TranslatedFunctionType { + var translatedParams: [TranslatedParameter] = [] + + for (i, param) in swiftType.parameters.enumerated() { + let paramName = param.parameterName ?? "_\(i)" + if let cType = try? CType(cdeclType: param.type) { + let translatedParam = TranslatedParameter( + javaParameters: [ + JavaParameter(type: cType.javaType, name: paramName) + ], + conversion: .pass + ) + translatedParams.append(translatedParam) + continue + } + throw JavaTranslationError.unhandledType(.function(swiftType)) + } + + guard let resultCType = try? CType(cdeclType: swiftType.resultType) else { + throw JavaTranslationError.unhandledType(.function(swiftType)) + } + + let transltedResult = TranslatedResult( + javaResultType: resultCType.javaType, + outParameters: [], + conversion: .pass + ) + + return TranslatedFunctionType( + name: name, + parameters: translatedParams, + result: transltedResult + ) + } + + /// Translate a Swift API signature to the user-facing Java API signature. /// /// Note that the result signature is for the high-level Java API, not the /// low-level FFM down-calling interface. func translate( - loweredFunctionSignature: LoweredFunctionSignature + loweredFunctionSignature: LoweredFunctionSignature, + methodName: String ) throws -> TranslatedFunctionSignature { let swiftSignature = loweredFunctionSignature.original @@ -136,6 +241,7 @@ struct JavaTranslation { selfParameter = try self.translate( swiftParam: swiftSelf, loweredParam: loweredFunctionSignature.selfParameter!, + methodName: methodName, parameterName: swiftSelf.parameterName ?? "self" ) } else { @@ -150,6 +256,7 @@ struct JavaTranslation { return try self.translate( swiftParam: swiftParam, loweredParam: loweredParam, + methodName: methodName, parameterName: parameterName ) } @@ -161,17 +268,17 @@ struct JavaTranslation { ) return TranslatedFunctionSignature( - loweredSignature: loweredFunctionSignature, selfParameter: selfParameter, parameters: parameters, result: result ) } - /// Translate + /// Translate a Swift API parameter to the user-facing Java API parameter. func translate( swiftParam: SwiftParameter, loweredParam: LoweredParameter, + methodName: String, parameterName: String ) throws -> TranslatedParameter { let swiftType = swiftParam.type @@ -184,7 +291,7 @@ struct JavaTranslation { javaParameters: [ JavaParameter( type: javaType, - name: loweredParam.cdeclParameters[0].parameterName! + name: parameterName ) ], conversion: .pass @@ -198,7 +305,7 @@ struct JavaTranslation { javaParameters: [ JavaParameter( type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"), - name: loweredParam.cdeclParameters[0].parameterName!) + name: parameterName) ], conversion: .swiftValueSelfSegment ) @@ -222,7 +329,7 @@ struct JavaTranslation { javaParameters: [ JavaParameter( type: .javaLangString, - name: loweredParam.cdeclParameters[0].parameterName! + name: parameterName ) ], conversion: .call(function: "SwiftKit.toCString", withArena: true) @@ -242,7 +349,7 @@ struct JavaTranslation { javaParameters: [ JavaParameter( type: try translate(swiftType: swiftType), - name: loweredParam.cdeclParameters[0].parameterName! + name: parameterName ) ], conversion: .swiftValueSelfSegment @@ -252,21 +359,22 @@ struct JavaTranslation { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) - case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid: + case .function: return TranslatedParameter( javaParameters: [ JavaParameter( - type: JavaType.class(package: "java.lang", name: "Runnable"), - name: loweredParam.cdeclParameters[0].parameterName!) + type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)"), + name: parameterName) ], - conversion: .call(function: "SwiftKit.toUpcallStub", withArena: true) + conversion: .call(function: "\(methodName).$toUpcallStub", withArena: true) ) - case .optional, .function: + case .optional: throw JavaTranslationError.unhandledType(swiftType) } } + /// Translate a Swift API result to the user-facing Java API result. func translate( swiftResult: SwiftResult, loweredResult: LoweredResult @@ -357,6 +465,10 @@ enum JavaConversionStep { // If `withArena` is true, `arena$` argument is added. case call(function: String, withArena: Bool) + // Apply a method on the placeholder. + // If `withArena` is true, `arena$` argument is added. + case method(methodName: String, arguments: [String] = [], withArena: Bool) + // Call 'new \(Type)(\(placeholder), swiftArena$)'. case constructSwiftValue(JavaType) @@ -365,6 +477,10 @@ enum JavaConversionStep { // Casting the placeholder to the certain type. case cast(JavaType) + + var isPass: Bool { + return if case .pass = self { true } else { false } + } } extension CType { @@ -443,5 +559,5 @@ extension CType { enum JavaTranslationError: Error { case inoutNotSupported(SwiftType) - case unhandledType(SwiftType) + case unhandledType(SwiftType, file: String = #file, line: Int = #line) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift index 416bc851..02d715a0 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+SwiftThunkPrinting.swift @@ -175,11 +175,11 @@ struct SwiftThunkTranslator { st.log.trace("Rendering thunks for: \(decl.displayName)") let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl) - guard let translatedSignatures = st.translatedSignature(for: decl) else { + guard let translated = st.translatedDecl(for: decl) else { return [] } - let thunkFunc = translatedSignatures.loweredSignature.cdeclThunk( + let thunkFunc = translated.loweredSignature.cdeclThunk( cName: thunkName, swiftAPIName: decl.name, as: decl.apiKind, diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index bd3251b4..7cd0075b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -33,7 +33,8 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry() /// Cached Java translation result. 'nil' indicates failed translation. - var translatedSignatures: [ImportedFunc: TranslatedFunctionSignature?] = [:] + var translatedDecls: [ImportedFunc: TranslatedFunctionDecl?] = [:] + package init( translator: Swift2JavaTranslator, @@ -267,18 +268,6 @@ extension FFMSwift2JavaGenerator { """ ) - printer.print( - """ - static MethodHandle upcallHandle(Class fi, String name, FunctionDescriptor fdesc) { - try { - return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType()); - } catch (ReflectiveOperationException ex) { - throw new AssertionError(ex); - } - } - """ - ) - printer.print( """ static MemoryLayout align(MemoryLayout layout, long align) { diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java index 85c491e4..9c04eded 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java @@ -77,9 +77,10 @@ public static void traceDowncall(Object... args) { String traceArgs = Arrays.stream(args) .map(Object::toString) .collect(Collectors.joining(", ")); - System.out.printf("[java][%s:%d] Downcall: %s(%s)\n", + System.out.printf("[java][%s:%d] Downcall: %s.%s(%s)\n", ex.getStackTrace()[1].getFileName(), ex.getStackTrace()[1].getLineNumber(), + ex.getStackTrace()[1].getClassName(), ex.getStackTrace()[1].getMethodName(), traceArgs); } @@ -448,26 +449,26 @@ public static long getSwiftInt(MemorySegment memorySegment, VarHandle handle) { } /** - * Convert String to a MemorySegment filled with the C string. + * Get the method handle of a functional interface. + * + * @param fi functional interface. + * @param name name of the single abstraction method. + * @param fdesc function descriptor of the method. + * @return unbound method handle. */ - public static MemorySegment toCString(String str, Arena arena) { - return arena.allocateFrom(str); + public static MethodHandle upcallHandle(Class fi, String name, FunctionDescriptor fdesc) { + try { + return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType()); + } catch (ReflectiveOperationException ex) { + throw new AssertionError(ex); + } } /** - * Convert Runnable to a MemorySegment which is an upcall stub for it. + * Convert String to a MemorySegment filled with the C string. */ - public static MemorySegment toUpcallStub(Runnable callback, Arena arena) { - try { - FunctionDescriptor descriptor = FunctionDescriptor.ofVoid(); - MethodHandle handle = MethodHandles.lookup() - .findVirtual(Runnable.class, "run", descriptor.toMethodType()) - .bindTo(callback); - return Linker.nativeLinker() - .upcallStub(handle, descriptor, arena); - } catch (Exception e) { - throw new AssertionError("should be unreachable"); - } + public static MemorySegment toCString(String str, Arena arena) { + return arena.allocateFrom(str); } private static class swift_getTypeName { diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index f1afa7c0..457e3a7a 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -29,11 +29,12 @@ final class FuncCallbackImportTests { import _StringProcessing import _SwiftConcurrencyShims - public func callMe(callback: () -> ()) + public func callMe(callback: () -> Void) + public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ()) """ - @Test("Import: public func callMe(callback: () -> ())") - func func_callMeFunc_Runnable() throws { + @Test("Import: public func callMe(callback: () -> Void)") + func func_callMeFunc_callback() throws { let st = Swift2JavaTranslator( swiftModuleName: "__FakeModule" ) @@ -51,22 +52,185 @@ final class FuncCallbackImportTests { ) let output = CodePrinter.toString { printer in - generator.printJavaBindingWrapperMethod(&printer, funcDecl) + generator.printFunctionDowncallMethods(&printer, funcDecl) } assertOutput( output, expected: """ + // ==== -------------------------------------------------- + // callMe + /** + * {@snippet lang=c : + * void swiftjava___FakeModule_callMe_callback(void (*callback)(void)) + * } + */ + private static class swiftjava___FakeModule_callMe_callback { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* callback: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + __FakeModule.findOrThrow("swiftjava___FakeModule_callMe_callback"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment callback) { + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(callback); + } + HANDLE.invokeExact(callback); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * {snippet lang=c : + * void (*)(void) + * } + */ + private static class $callback { + @FunctionalInterface + public interface Function { + void apply(); + } + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); + private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } + } + public static class callMe { + @FunctionalInterface + public interface callback extends swiftjava___FakeModule_callMe_callback.$callback.Function {} + private static MemorySegment $toUpcallStub(callback fi, Arena arena) { + return swiftjava___FakeModule_callMe_callback.$callback.toUpcallStub(fi, arena); + } + } + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func callMe(callback: () -> Void) + * } + */ + public static void callMe(callMe.callback callback) { + try(var arena$ = Arena.ofConfined()) { + swiftjava___FakeModule_callMe_callback.call(callMe.$toUpcallStub(callback, arena$)); + } + } + """ + ) + } + + @Test("Import: public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ())") + func func_callMeMoreFunc_callback() throws { + let st = Swift2JavaTranslator( + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .error + + try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) + + let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMeMore" }! + + let generator = FFMSwift2JavaGenerator( + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = CodePrinter.toString { printer in + generator.printFunctionDowncallMethods(&printer, funcDecl) + } + + assertOutput( + output, + expected: + """ + // ==== -------------------------------------------------- + // callMeMore + /** + * {@snippet lang=c : + * void swiftjava___FakeModule_callMeMore_callback_fn(ptrdiff_t (*callback)(const void *, float), void (*fn)(void)) + * } + */ + private static class swiftjava___FakeModule_callMeMore_callback_fn { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* callback: */SwiftValueLayout.SWIFT_POINTER, + /* fn: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = + __FakeModule.findOrThrow("swiftjava___FakeModule_callMeMore_callback_fn"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + public static void call(java.lang.foreign.MemorySegment callback, java.lang.foreign.MemorySegment fn) { + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(callback, fn); + } + HANDLE.invokeExact(callback, fn); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** + * {snippet lang=c : + * ptrdiff_t (*)(const void *, float) + * } + */ + private static class $callback { + @FunctionalInterface + public interface Function { + long apply(java.lang.foreign.MemorySegment _0, float _1); + } + private static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_INT, + /* _0: */SwiftValueLayout.SWIFT_POINTER, + /* _1: */SwiftValueLayout.SWIFT_FLOAT + ); + private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } + /** + * {snippet lang=c : + * void (*)(void) + * } + */ + private static class $fn { + @FunctionalInterface + public interface Function { + void apply(); + } + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); + private static final MethodHandle HANDLE = SwiftKit.upcallHandle(Function.class, "apply", DESC); + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } + } + public static class callMeMore { + @FunctionalInterface + public interface callback extends swiftjava___FakeModule_callMeMore_callback_fn.$callback.Function {} + private static MemorySegment $toUpcallStub(callback fi, Arena arena) { + return swiftjava___FakeModule_callMeMore_callback_fn.$callback.toUpcallStub(fi, arena); + } + @FunctionalInterface + public interface fn extends swiftjava___FakeModule_callMeMore_callback_fn.$fn.Function {} + private static MemorySegment $toUpcallStub(fn fi, Arena arena) { + return swiftjava___FakeModule_callMeMore_callback_fn.$fn.toUpcallStub(fi, arena); + } + } /** * Downcall to Swift: * {@snippet lang=swift : - * public func callMe(callback: () -> ()) + * public func callMeMore(callback: (UnsafeRawPointer, Float) -> Int, fn: () -> ()) * } */ - public static void callMe(java.lang.Runnable callback) { + public static void callMeMore(callMeMore.callback callback, callMeMore.fn fn) { try(var arena$ = Arena.ofConfined()) { - swiftjava___FakeModule_callMe_callback.call(SwiftKit.toUpcallStub(callback, arena$)) + swiftjava___FakeModule_callMeMore_callback_fn.call(callMeMore.$toUpcallStub(callback, arena$), callMeMore.$toUpcallStub(fn, arena$)); } } """ diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index f3a101aa..2cac6218 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -57,12 +57,12 @@ final class FunctionDescriptorTests { * } */ private static class swiftjava_SwiftModule_globalTakeInt_i { - public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( /* i: */SwiftValueLayout.SWIFT_INT ); - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = SwiftModule.findOrThrow("swiftjava_SwiftModule_globalTakeInt_i"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long i) { try { if (SwiftKit.TRACE_DOWNCALLS) { @@ -92,13 +92,13 @@ final class FunctionDescriptorTests { * } */ private static class swiftjava_SwiftModule_globalTakeLongInt_l_i32 { - public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( /* l: */SwiftValueLayout.SWIFT_INT64, /* i32: */SwiftValueLayout.SWIFT_INT32 ); - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = SwiftModule.findOrThrow("swiftjava_SwiftModule_globalTakeLongInt_l_i32"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long l, int i32) { try { if (SwiftKit.TRACE_DOWNCALLS) { @@ -128,13 +128,13 @@ final class FunctionDescriptorTests { * } */ private static class swiftjava_SwiftModule_echoInt_i { - public static final FunctionDescriptor DESC = FunctionDescriptor.of( + private static final FunctionDescriptor DESC = FunctionDescriptor.of( /* -> */SwiftValueLayout.SWIFT_INT, /* i: */SwiftValueLayout.SWIFT_INT ); - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = SwiftModule.findOrThrow("swiftjava_SwiftModule_echoInt_i"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(long i) { try { if (SwiftKit.TRACE_DOWNCALLS) { @@ -164,13 +164,13 @@ final class FunctionDescriptorTests { * } */ private static class swiftjava_SwiftModule_MySwiftClass_counter$get { - public static final FunctionDescriptor DESC = FunctionDescriptor.of( + private static final FunctionDescriptor DESC = FunctionDescriptor.of( /* -> */SwiftValueLayout.SWIFT_INT32, /* self: */SwiftValueLayout.SWIFT_POINTER ); - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = SwiftModule.findOrThrow("swiftjava_SwiftModule_MySwiftClass_counter$get"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static int call(java.lang.foreign.MemorySegment self) { try { if (SwiftKit.TRACE_DOWNCALLS) { @@ -199,13 +199,13 @@ final class FunctionDescriptorTests { * } */ private static class swiftjava_SwiftModule_MySwiftClass_counter$set { - public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( /* newValue: */SwiftValueLayout.SWIFT_INT32, /* self: */SwiftValueLayout.SWIFT_POINTER ); - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = SwiftModule.findOrThrow("swiftjava_SwiftModule_MySwiftClass_counter$set"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(int newValue, java.lang.foreign.MemorySegment self) { try { if (SwiftKit.TRACE_DOWNCALLS) { diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index d0ad7784..92ce8ea6 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -40,13 +40,13 @@ final class StringPassingTests { * } */ private static class swiftjava___FakeModule_writeString_string { - public static final FunctionDescriptor DESC = FunctionDescriptor.of( + private static final FunctionDescriptor DESC = FunctionDescriptor.of( /* -> */SwiftValueLayout.SWIFT_INT, /* string: */SwiftValueLayout.SWIFT_POINTER ); - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = __FakeModule.findOrThrow("swiftjava___FakeModule_writeString_string"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(java.lang.foreign.MemorySegment string) { try { if (SwiftKit.TRACE_DOWNCALLS) { diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index 9d873cc3..562b92ae 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -42,17 +42,17 @@ final class VariableImportTests { try assertOutput( st, input: class_interfaceFile, .java, - detectChunkByInitialLines: 7, + detectChunkByInitialLines: 8, expectedChunks: [ """ private static class swiftjava_FakeModule_MySwiftClass_counterInt$get { - public static final FunctionDescriptor DESC = FunctionDescriptor.of( + private static final FunctionDescriptor DESC = FunctionDescriptor.of( /* -> */SwiftValueLayout.SWIFT_INT, /* self: */SwiftValueLayout.SWIFT_POINTER ); - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$get"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static long call(java.lang.foreign.MemorySegment self) { try { if (SwiftKit.TRACE_DOWNCALLS) { @@ -79,13 +79,13 @@ final class VariableImportTests { """, """ private static class swiftjava_FakeModule_MySwiftClass_counterInt$set { - public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( /* newValue: */SwiftValueLayout.SWIFT_INT, /* self: */SwiftValueLayout.SWIFT_POINTER ); - public static final MemorySegment ADDR = + private static final MemorySegment ADDR = FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$set"); - public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); public static void call(long newValue, java.lang.foreign.MemorySegment self) { try { if (SwiftKit.TRACE_DOWNCALLS) {