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 ffa90359..82472418 100644 --- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -30,15 +30,11 @@ public class MySwiftLibraryTest { @Test void call_helloWorld() { MySwiftLibrary.helloWorld(); - - assertNotNull(MySwiftLibrary.helloWorld$address()); } @Test void call_globalTakeInt() { MySwiftLibrary.globalTakeInt(12); - - assertNotNull(MySwiftLibrary.globalTakeInt$address()); } @Test diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift index 1ae962a3..97f5149e 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -34,6 +34,10 @@ public class MySwiftClass { public var counter: Int32 = 0 + public static func factory(len: Int, cap: Int) -> MySwiftClass { + return MySwiftClass(len: len, cap: cap) + } + public func voidMethod() { p("") } diff --git a/Samples/SwiftKitSampleApp/build.gradle b/Samples/SwiftKitSampleApp/build.gradle index fdd38a11..18a55f44 100644 --- a/Samples/SwiftKitSampleApp/build.gradle +++ b/Samples/SwiftKitSampleApp/build.gradle @@ -81,7 +81,7 @@ def jextract = tasks.register("jextract", Exec) { workingDir = layout.projectDirectory commandLine "swift" - args("package", "jextract", "-v", "--log-level", "info") // TODO: pass log level from Gradle build + args("package", "jextract", "-v", "--log-level", "debug") // TODO: pass log level from Gradle build } // Add the java-swift generated Java sources 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 82e62d6d..f94a2abb 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -16,13 +16,10 @@ // Import swift-extract generated sources -import com.example.swift.MySwiftLibrary; -import com.example.swift.MySwiftClass; - // Import javakit/swiftkit support libraries + import org.swift.swiftkit.SwiftArena; import org.swift.swiftkit.SwiftKit; -import org.swift.swiftkit.SwiftValueWitnessTable; public class HelloJava2Swift { @@ -40,21 +37,33 @@ static void examples() { MySwiftLibrary.globalTakeInt(1337); + long cnt = MySwiftLibrary.globalWriteString("String from Java"); + + SwiftKit.trace("count = " + cnt); + + MySwiftLibrary.globalCallMeRunnable(() -> { + SwiftKit.trace("running runnable"); + }); + // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = SwiftArena.ofConfined()) { - MySwiftClass obj = new MySwiftClass(2222, 7777, arena); + MySwiftClass obj = new MySwiftClass(2222, 7777, arena); + + // just checking retains/releases work + SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); + SwiftKit.retain(obj); + SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); + SwiftKit.release(obj); + SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); - // just checking retains/releases work - SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); - SwiftKit.retain(obj); - SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); - SwiftKit.release(obj); - SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj)); + obj.setCounter(12); + SwiftKit.trace("obj.counter = " + obj.getCounter()); - obj.voidMethod(); - obj.takeIntMethod(42); + obj.voidMethod(); + obj.takeIntMethod(42); MySwiftStruct swiftValue = new MySwiftStruct(2222, 1111, arena); + SwiftKit.trace("swiftValue.capacity = " + swiftValue.getCapacity()); } 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 007b06bd..41d83305 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftLibraryTest.java @@ -35,15 +35,11 @@ public class MySwiftLibraryTest { @Test void call_helloWorld() { MySwiftLibrary.helloWorld(); - - assertNotNull(MySwiftLibrary.helloWorld$address()); } @Test void call_globalTakeInt() { MySwiftLibrary.globalTakeInt(12); - - assertNotNull(MySwiftLibrary.globalTakeInt$address()); } @Test diff --git a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift index 4ab28e2c..17e461aa 100644 --- a/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift +++ b/Sources/JExtractSwift/CDeclLowering/Swift2JavaTranslator+FunctionLowering.swift @@ -619,14 +619,7 @@ extension LoweredFunctionSignature { @_spi(Testing) public func cFunctionDecl(cName: String) throws -> CFunction { - return CFunction( - resultType: try CType(cdeclType: self.result.cdeclResultType), - name: cName, - parameters: try self.allLoweredParameters.map { - try CParameter(name: $0.parameterName, type: CType(cdeclType: $0.type).parameterDecay) - }, - isVariadic: false - ) + try CFunction(cdeclSignature: self.cdeclSignature, cName: cName) } } diff --git a/Sources/JExtractSwift/CTypes/CType.swift b/Sources/JExtractSwift/CTypes/CType.swift index 9b8744d0..4cd683a6 100644 --- a/Sources/JExtractSwift/CTypes/CType.swift +++ b/Sources/JExtractSwift/CTypes/CType.swift @@ -298,3 +298,12 @@ extension CType { } } } + +extension CType { + var isVoid: Bool { + return switch self { + case .void: true + default: false + } + } +} diff --git a/Sources/JExtractSwift/CodePrinter.swift b/Sources/JExtractSwift/CodePrinter.swift index 83669ebb..db823aa2 100644 --- a/Sources/JExtractSwift/CodePrinter.swift +++ b/Sources/JExtractSwift/CodePrinter.swift @@ -68,16 +68,16 @@ public struct CodePrinter { } } - public mutating func printTypeDecl( - _ text: Any, + public mutating func printBraceBlock( + _ header: Any, function: String = #function, file: String = #fileID, line: UInt = #line, - body: (inout CodePrinter) -> () - ) { - print("\(text) {") + body: (inout CodePrinter) throws -> () + ) rethrows { + print("\(header) {") indent() - body(&self) + try body(&self) outdent() print("}", .sloc, function: function, file: file, line: line) } @@ -145,9 +145,10 @@ public struct CodePrinter { // TODO: remove this in real mode, this just helps visually while working on it public mutating func printSeparator(_ text: String) { - // TODO: actually use the indentationDepth + assert(!text.contains(where: \.isNewline)) print( """ + // ==== -------------------------------------------------- // \(text) diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift index a7c12cc9..848797a4 100644 --- a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift @@ -36,39 +36,6 @@ extension ImplicitlyUnwrappedOptionalTypeSyntax { } } -extension SyntaxProtocol { - - var asNominalTypeKind: NominalTypeKind { - if isClass { - .class - } else if isActor { - .actor - } else if isStruct { - .struct - } else if isEnum { - .enum - } else { - fatalError("Unknown nominal kind: \(self)") - } - } - - var isClass: Bool { - return self.is(ClassDeclSyntax.self) - } - - var isActor: Bool { - return self.is(ActorDeclSyntax.self) - } - - var isEnum: Bool { - return self.is(EnumDeclSyntax.self) - } - - var isStruct: Bool { - return self.is(StructDeclSyntax.self) - } -} - extension DeclModifierSyntax { var isAccessControl: Bool { switch self.name.tokenKind { diff --git a/Sources/JExtractSwift/ImportedDecls+Printing.swift b/Sources/JExtractSwift/ImportedDecls+Printing.swift deleted file mode 100644 index bc755015..00000000 --- a/Sources/JExtractSwift/ImportedDecls+Printing.swift +++ /dev/null @@ -1,101 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Foundation -import JavaTypes -import SwiftSyntax - -extension ImportedFunc { - /// Render a `@{@snippet ... }` comment section that can be put inside a JavaDoc comment - /// when referring to the original declaration a printed method refers to. - var renderCommentSnippet: String? { - if let syntax { - """ - * {@snippet lang=swift : - * \(syntax) - * } - """ - } else { - nil - } - } -} - -extension VariableAccessorKind { - - public var fieldSuffix: String { - switch self { - case .get: "_GET" - case .set: "_SET" - } - } - - public var renderDescFieldName: String { - switch self { - case .get: "DESC_GET" - case .set: "DESC_SET" - } - } - - public var renderAddrFieldName: String { - switch self { - case .get: "ADDR_GET" - case .set: "ADDR_SET" - } - } - - public var renderHandleFieldName: String { - switch self { - case .get: "HANDLE_GET" - case .set: "HANDLE_SET" - } - } - - /// Renders a "$get" part that can be used in a method signature representing this accessor. - public var renderMethodNameSegment: String { - switch self { - case .get: "$get" - case .set: "$set" - } - } - - func renderMethodName(_ decl: ImportedFunc) -> String? { - switch self { - case .get: "get\(decl.identifier.toCamelCase)" - case .set: "set\(decl.identifier.toCamelCase)" - } - } -} - -extension Optional where Wrapped == VariableAccessorKind { - public var renderDescFieldName: String { - self?.renderDescFieldName ?? "DESC" - } - - public var renderAddrFieldName: String { - self?.renderAddrFieldName ?? "ADDR" - } - - public var renderHandleFieldName: String { - self?.renderHandleFieldName ?? "HANDLE" - } - - public var renderMethodNameSegment: String { - self?.renderMethodNameSegment ?? "" - } - - func renderMethodName(_ decl: ImportedFunc) -> String { - self?.renderMethodName(decl) ?? decl.baseIdentifier - } -} diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index f893cbb1..c29e3d5a 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -12,466 +12,132 @@ // //===----------------------------------------------------------------------===// -import Foundation -import JavaTypes import SwiftSyntax /// Any imported (Swift) declaration -protocol ImportedDecl { - -} +protocol ImportedDecl: AnyObject {} public typealias JavaPackage = String /// Describes a Swift nominal type (e.g., a class, struct, enum) that has been /// imported and is being translated into Java. -package struct ImportedNominalType: ImportedDecl { +package class ImportedNominalType: ImportedDecl { let swiftNominal: SwiftNominalTypeDeclaration - let javaType: JavaType - var kind: NominalTypeKind package var initializers: [ImportedFunc] = [] package var methods: [ImportedFunc] = [] - package var variables: [ImportedVariable] = [] + package var variables: [ImportedFunc] = [] - init(swiftNominal: SwiftNominalTypeDeclaration, javaType: JavaType, kind: NominalTypeKind) { + init(swiftNominal: SwiftNominalTypeDeclaration) { self.swiftNominal = swiftNominal - self.javaType = javaType - self.kind = kind - } - - var translatedType: TranslatedType { - TranslatedType( - cCompatibleConvention: .direct, - originalSwiftType: "\(raw: swiftNominal.qualifiedName)", - originalSwiftTypeKind: self.kind, - cCompatibleSwiftType: "UnsafeRawPointer", - cCompatibleJavaMemoryLayout: .heapObject, - javaType: javaType - ) } - public var isReferenceType: Bool { - switch self.kind { - case .class, .actor: - true - default: - false - } - } - - /// The Java class name without the package. - public var javaClassName: String { - switch javaType { - case .class(package: _, let name): name - default: javaType.description - } + var javaClassName: String { + swiftNominal.name } } -// TODO: replace this with `SwiftNominalTypeDeclaration.Kind` -public enum NominalTypeKind { - case `actor` - case `class` - case `enum` - case `struct` - case `void` // TODO: NOT NOMINAL, BUT... - case function // TODO: NOT NOMINAL, BUT... - case primitive // TODO: NOT NOMINAL, BUT... - - var isReferenceType: Bool { - switch self { - case .actor, .class: true - case .enum, .struct: false - case .void, .function, .primitive: false - } - } +public final class ImportedFunc: ImportedDecl, CustomStringConvertible { + /// Swift module name (e.g. the target name where a type or function was declared) + public var module: String - var isValueType: Bool { - switch self { - case .actor, .class: false - case .enum, .struct: true - case .void, .function, .primitive: false - } - } + /// The function name. + /// e.g., "init" for an initializer or "foo" for "foo(a:b:)". + public var name: String - var isVoid: Bool { - switch self { - case .actor, .class: false - case .enum, .struct: false - case .void: true - case .function, .primitive: false - } - } -} - -public struct ImportedParam { - let syntax: FunctionParameterSyntax + public var swiftDecl: any DeclSyntaxProtocol - var firstName: String? { - let text = syntax.firstName.trimmed.text - guard text != "_" else { - return nil - } + var translatedSignature: TranslatedFunctionSignature - return text + public var signatureString: String { + // FIXME: Remove comments and normalize trivia. + self.swiftDecl.signatureString } - var secondName: String? { - let text = syntax.secondName?.trimmed.text - guard text != "_" else { - return nil - } - - return text + var loweredSignature: LoweredFunctionSignature { + translatedSignature.loweredSignature } - var effectiveName: String? { - firstName ?? secondName + var swiftSignature: SwiftFunctionSignature { + loweredSignature.original } - var effectiveValueName: String { - secondName ?? firstName ?? "_" + package func cFunctionDecl(cName: String) -> CFunction { + // 'try!' because we know 'loweredSignature' can be described with C. + try! loweredSignature.cFunctionDecl(cName: cName) } - // The Swift type as-is from the swift interface - var swiftType: String { - syntax.type.trimmed.description + package var kind: SwiftAPIKind { + loweredSignature.apiKind } - // The mapped-to Java type of the above Java type, collections and optionals may be replaced with Java ones etc. - var type: TranslatedType -} - -extension ImportedParam { - func renderParameterForwarding() -> String? { - if type.javaType.isPrimitive { - effectiveName - } else if type.javaType.isSwiftClosure { - // use the name of the upcall handle we'll have emitted by now - "\(effectiveName!)$" - } else { - "\(effectiveName!).$memorySegment()" + var parentType: SwiftType? { + guard let selfParameter = swiftSignature.selfParameter else { + return nil + } + switch selfParameter { + case .instance(let parameter): + return parameter.type + case .staticMethod(let type): + return type + case .initializer(let type): + return type } } -} - -public enum ParameterVariant { - /// Used when declaring the "Swift thunks" we call through into Swift. - /// - /// Some types need to be represented as raw pointers and recovered into - /// Swift types inside the thunks when we do this. - case cDeclThunk -} - -// TODO: this is used in different contexts and needs a cleanup -// Perhaps this is "which parameter passing style"? -public enum SelfParameterVariant { - // ==== Java forwarding patterns - - /// Make a method that accepts the raw memory pointer as a MemorySegment - case memorySegment - /// Make a method that accepts the the Java wrapper class of the type - case wrapper - /// Raw SWIFT_POINTER - case pointer - - // ==== Swift forwarding patterns - - case swiftThunkSelf -} - -public struct ImportedFunc: ImportedDecl, CustomStringConvertible { - - /// Swift module name (e.g. the target name where a type or function was declared) - public var module: String /// If this function/method is member of a class/struct/protocol, /// 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 parent: TranslatedType? - public var hasParent: Bool { parent != nil } - - /// This is a full name such as init(cap:name:). - public var identifier: String - - /// This is the base identifier for the function, e.g., "init" for an - /// initializer or "f" for "f(a:b:)". - public var baseIdentifier: String { - guard let idx = identifier.firstIndex(of: "(") else { - return identifier - } - return String(identifier[.. [ImportedParam] { - if let parent { - var params = parameters - - // Add `self: Self` for method calls on a member - // - // allocating initializer takes a Self.Type instead, but it's also a pointer - switch paramPassingStyle { - case nil, .wrapper: - break - - case .pointer where !isInit: - let selfParam: FunctionParameterSyntax = "self$: $swift_pointer" - params.append( - ImportedParam(syntax: selfParam, type: parent) - ) - - case .memorySegment where !isInit: - let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment" - var parentForSelf = parent - parentForSelf.javaType = .javaForeignMemorySegment - params.append( - ImportedParam(syntax: selfParam, type: parentForSelf) - ) - - case .swiftThunkSelf: - break - - default: - break - } - - // TODO: add any metadata for generics and other things we may need to add here - - return params + let context = if let parentType { + "\(parentType)." } else { - return self.parameters + "" } - } - - public var swiftDecl: any DeclSyntaxProtocol - - public var syntax: String? { - self.swiftDecl.signatureString - } - public var isInit: Bool = false - - public var isIndirectReturn: Bool { - switch returnType.originalSwiftTypeKind { - case .actor, .class, .struct, .enum: - return true - default: - return false - } + return prefix + context + self.name } - public init( + init( module: String, - decl: any DeclSyntaxProtocol, - parent: TranslatedType?, - identifier: String, - returnType: TranslatedType, - parameters: [ImportedParam] + swiftDecl: any DeclSyntaxProtocol, + name: String, + translatedSignature: TranslatedFunctionSignature ) { - self.swiftDecl = decl self.module = module - self.parent = parent - self.identifier = identifier - self.returnType = returnType - self.parameters = parameters + self.name = name + self.swiftDecl = swiftDecl + self.translatedSignature = translatedSignature } public var description: String { """ ImportedFunc { - identifier: \(identifier) - returnType: \(returnType) - parameters: \(parameters) - - Swift mangled name: - Imported from: - \(syntax?.description ?? "") + kind: \(kind) + module: \(module) + name: \(name) + signature: \(self.swiftDecl.signatureString) } """ } } extension ImportedFunc: Hashable { - public func hash(into hasher: inout Swift.Hasher) { - self.swiftDecl.id.hash(into: &hasher) - } - - public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool { - lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id - && lhs.swiftDecl.id == rhs.swiftDecl.id - } -} - -public enum VariableAccessorKind { - case get - case set -} - -public struct ImportedVariable: ImportedDecl, CustomStringConvertible { - - public var module: String - - /// If this function/method is member of a class/struct/protocol, - /// 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 parentName: TranslatedType? - public var hasParent: Bool { parentName != nil } - - /// This is a full name such as "counter". - public var identifier: String - - /// Which accessors are we able to expose. - /// - /// Usually this will be all the accessors the variable declares, - /// however if the getter is async or throwing we may not be able to import it - /// (yet), and therefore would skip it from the supported set. - public var supportedAccessorKinds: [VariableAccessorKind] = [.get, .set] - - /// This is the base identifier for the function, e.g., "init" for an - /// initializer or "f" for "f(a:b:)". - public var baseIdentifier: String { - guard let idx = identifier.firstIndex(of: "(") else { - return identifier - } - return String(identifier[.. ImportedFunc? { - guard self.supportedAccessorKinds.contains(kind) else { - return nil - } - - switch kind { - case .set: - let newValueParam: FunctionParameterSyntax = - "_ newValue: \(self.returnType.cCompatibleSwiftType)" - let funcDecl = ImportedFunc( - module: self.module, - decl: self.syntax!, - parent: self.parentName, - identifier: self.identifier, - returnType: TranslatedType.void, - parameters: [.init(syntax: newValueParam, type: self.returnType)]) - return funcDecl - - case .get: - let funcDecl = ImportedFunc( - module: self.module, - decl: self.syntax!, - parent: self.parentName, - identifier: self.identifier, - returnType: self.returnType, - parameters: []) - return funcDecl - } - } - - public func effectiveAccessorParameters( - _ kind: VariableAccessorKind, paramPassingStyle: SelfParameterVariant? - ) -> [ImportedParam] { - var params: [ImportedParam] = [] - - if kind == .set { - let newValueParam: FunctionParameterSyntax = - "_ newValue: \(raw: self.returnType.swiftTypeName)" - params.append( - ImportedParam( - syntax: newValueParam, - type: self.returnType) - ) - } - - if let parentName { - // Add `self: Self` for method calls on a member - // - // allocating initializer takes a Self.Type instead, but it's also a pointer - switch paramPassingStyle { - case .pointer: - let selfParam: FunctionParameterSyntax = "self$: $swift_pointer" - params.append( - ImportedParam( - syntax: selfParam, - type: parentName - ) - ) - - case .memorySegment: - let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment" - var parentForSelf = parentName - parentForSelf.javaType = .javaForeignMemorySegment - params.append( - ImportedParam( - syntax: selfParam, - type: parentForSelf - ) - ) - - case nil, - .wrapper, - .swiftThunkSelf: - break - } - } - - return params - } - - public var swiftMangledName: String = "" - - public var syntax: VariableDeclSyntax? = nil - - public init( - module: String, - parentName: TranslatedType?, - identifier: String, - returnType: TranslatedType - ) { - self.module = module - self.parentName = parentName - self.identifier = identifier - self.returnType = returnType - } - - public var description: String { - """ - ImportedFunc { - mangledName: \(swiftMangledName) - identifier: \(identifier) - returnType: \(returnType) - - Swift mangled name: - Imported from: - \(syntax?.description ?? "") - } - """ + public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Bool { + return lhs === rhs } } diff --git a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift index 9b9d4ece..7cf1d4df 100644 --- a/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift +++ b/Sources/JExtractSwift/JavaConstants/ForeignValueLayouts.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -import Foundation import JavaTypes /// Represents a value of a `java.lang.foreign.Self` that we want to render in generated Java code. @@ -22,18 +21,16 @@ public struct ForeignValueLayout: CustomStringConvertible, Equatable { var inlineComment: String? var value: String - var needsMemoryLayoutCall: Bool = false - public init(inlineComment: String? = nil, javaConstant: String) { self.inlineComment = inlineComment - self.value = javaConstant - self.needsMemoryLayoutCall = false + self.value = "SwiftValueLayout.\(javaConstant)" } public init(inlineComment: String? = nil, customType: String) { self.inlineComment = inlineComment - self.value = customType - self.needsMemoryLayoutCall = true + // When the type is some custom type, e.g. another Swift struct that we imported, + // we need to import its layout. We do this by referring $LAYOUT on it. + self.value = "\(customType).$LAYOUT" } public init?(javaType: JavaType) { @@ -57,13 +54,7 @@ public struct ForeignValueLayout: CustomStringConvertible, Equatable { result.append("/*\(inlineComment)*/") } - result.append("SwiftValueLayout.\(value)") - - // When the type is some custom type, e.g. another Swift struct that we imported, - // we need to import its layout. We do this by calling $layout() on it. - if needsMemoryLayoutCall { - result.append(".$layout()") - } + result.append(value) return result } @@ -83,9 +74,4 @@ extension ForeignValueLayout { public static let SwiftFloat = Self(javaConstant: "SWIFT_FLOAT") public static let SwiftDouble = Self(javaConstant: "SWIFT_DOUBLE") - - var isPrimitive: Bool { - // FIXME: This is a hack, we need an enum to better describe this! - value != "SWIFT_POINTER" - } } diff --git a/Sources/JExtractSwift/JavaTypes.swift b/Sources/JExtractSwift/JavaConstants/JavaTypes.swift similarity index 100% rename from Sources/JExtractSwift/JavaTypes.swift rename to Sources/JExtractSwift/JavaConstants/JavaTypes.swift diff --git a/Sources/JExtractSwift/JavaType+Printing.swift b/Sources/JExtractSwift/JavaType+Printing.swift deleted file mode 100644 index 39a348d9..00000000 --- a/Sources/JExtractSwift/JavaType+Printing.swift +++ /dev/null @@ -1,47 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Foundation -import SwiftBasicFormat -import SwiftParser -import SwiftSyntax -import JavaTypes - -extension JavaType { - /// Returns a 'handle' name to pass to the `invoke` call as well as the - /// `FunctionDescription` and `MethodHandle` of the downcall handle for this parameter. - /// - /// Pass the prior to `invoke`, and directly render the latter in the Java wrapper downcall function body. - func prepareClosureDowncallHandle(decl: ImportedFunc, parameter: String) -> String { - let varNameBase = "\(decl.baseIdentifier)_\(parameter)" - let handle = "\(varNameBase)_handle$" - let desc = "\(varNameBase)_desc$" - - if self == .javaLangRunnable { - return - """ - FunctionDescriptor \(desc) = FunctionDescriptor.ofVoid(); - MethodHandle \(handle) = MethodHandles.lookup() - .findVirtual(Runnable.class, "run", - \(desc).toMethodType()); - \(handle) = \(handle).bindTo(\(parameter)); - - Linker linker = Linker.nativeLinker(); - MemorySegment \(parameter)$ = linker.upcallStub(\(handle), \(desc), arena); - """ - } - - fatalError("Cannot render closure downcall handle for: \(self), in: \(decl), parameter: \(parameter)") - } -} diff --git a/Sources/JExtractSwift/Swift2Java.swift b/Sources/JExtractSwift/Swift2Java.swift index 5eaab18a..6525fc0a 100644 --- a/Sources/JExtractSwift/Swift2Java.swift +++ b/Sources/JExtractSwift/Swift2Java.swift @@ -91,7 +91,6 @@ public struct SwiftToJava: ParsableCommand { try translator.analyze() try translator.writeSwiftThunkSources(outputDirectory: outputDirectorySwift) try translator.writeExportedJavaSources(outputDirectory: outputDirectoryJava) - try translator.writeExportedJavaModule(outputDirectory: outputDirectoryJava) print("[swift-java] Generated Java sources (\(packageName)) in: \(outputDirectoryJava)/") print("[swift-java] Imported Swift module '\(swiftModule)': " + "done.".green) } diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift new file mode 100644 index 00000000..15fd56e3 --- /dev/null +++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaBindingsPrinting.swift @@ -0,0 +1,356 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +extension Swift2JavaTranslator { + public func printInitializerDowncallConstructors( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + printer.printSeparator(decl.displayName) + + printJavaBindingDescriptorClass(&printer, decl) + + // Render the "make the downcall" functions. + printInitializerDowncallConstructor(&printer, decl) + } + + public func printFunctionDowncallMethods( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + printer.printSeparator(decl.displayName) + + printJavaBindingDescriptorClass(&printer, decl) + + // Render the "make the downcall" functions. + printFuncDowncallMethod(&printer, decl) + } + + /// Print FFM Java binding descriptors for the imported Swift API. + func printJavaBindingDescriptorClass( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + let thunkName = thunkNameRegistry.functionThunkName(decl: decl) + let cFunc = decl.cFunctionDecl(cName: thunkName) + + printer.printBraceBlock("private static class \(cFunc.name)") { printer in + printFunctionDescriptorValue(&printer, cFunc) + printer.print( + """ + public static final MemorySegment ADDR = + \(self.swiftModuleName).findOrThrow("\(cFunc.name)"); + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + """ + ) + } + } + + /// Print the 'FunctionDescriptor' of the lowered cdecl thunk. + public func printFunctionDescriptorValue( + _ printer: inout CodePrinter, + _ cFunc: CFunction + ) { + printer.start("public static final FunctionDescriptor DESC = ") + + let isEmptyParam = cFunc.parameters.isEmpty + if cFunc.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)) + } + + for (param, isLast) in cFunc.parameters.withIsLast { + printer.print("/* \(param.name ?? "_"): */", .continue) + printer.print(param.type.foreignValueLayout, .parameterNewlineSeparator(isLast)) + } + + printer.outdent() + printer.print(");") + } + + public func printInitializerDowncallConstructor( + _ printer: inout CodePrinter, + _ decl: ImportedFunc + ) { + guard let className = decl.parentType?.asNominalTypeDeclaration?.name else { + return + } + let modifiers = "public" + + var paramDecls = decl.translatedSignature.parameters + .flatMap(\.javaParameters) + .map { "\($0.type) \($0.name)" } + + assert(decl.translatedSignature.requiresSwiftArena, "constructor always require the SwiftArena") + paramDecls.append("SwiftArena swiftArena$") + + printer.printBraceBlock( + """ + /** + * Create an instance of {@code \(className)}. + * + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + \(modifiers) \(className)(\(paramDecls.joined(separator: ", "))) + """ + ) { printer in + // Call super constructor `SwiftValue(Supplier , SwiftArena)`. + printer.print("super(() -> {") + printer.indent() + printDowncall(&printer, decl, isConstructor: true) + printer.outdent() + printer.print("}, swiftArena$);") + } + } + + /// Print the calling body that forwards all the parameters to the `methodName`, + /// with adding `SwiftArena.ofAuto()` at the end. + public func printFuncDowncallMethod( + _ printer: inout CodePrinter, + _ decl: ImportedFunc) { + let methodName: String = switch decl.kind { + case .getter: "get\(decl.name.toCamelCase)" + case .setter: "set\(decl.name.toCamelCase)" + case .function: decl.name + case .initializer: fatalError("initializers must use printInitializerDowncallConstructor()") + } + + var modifiers = "public" + switch decl.swiftSignature.selfParameter { + case .staticMethod(_), nil: + modifiers.append(" static") + default: + break + } + + let returnTy = decl.translatedSignature.result.javaResultType + + var paramDecls = decl.translatedSignature.parameters + .flatMap(\.javaParameters) + .map { "\($0.type) \($0.name)" } + if decl.translatedSignature.requiresSwiftArena { + paramDecls.append("SwiftArena swiftArena$") + } + + // TODO: we could copy the Swift method's documentation over here, that'd be great UX + printer.printBraceBlock( + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * \(decl.signatureString) + * } + */ + \(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) + """ + ) { printer in + if case .instance(_) = decl.swiftSignature.selfParameter { + // Make sure the object has not been destroyed. + printer.print("$ensureAlive();") + } + + printDowncall(&printer, decl) + } + } + + /// Print the actual downcall to the Swift API. + /// + /// This assumes that all the parameters are passed-in with appropriate names. + package func printDowncall( + _ printer: inout CodePrinter, + _ decl: ImportedFunc, + isConstructor: Bool = false + ) { + //=== Part 1: MethodHandle + let descriptorClassIdentifier = thunkNameRegistry.functionThunkName(decl: decl) + printer.print( + "var mh$ = \(descriptorClassIdentifier).HANDLE;" + ) + + let tryHead = if decl.translatedSignature.requiresTemporaryArena { + "try(var arena$ = Arena.ofConfined()) {" + } else { + "try {" + } + printer.print(tryHead); + printer.indent(); + + //=== Part 2: prepare all arguments. + var downCallArguments: [String] = [] + + // Regular parameters. + for (i, parameter) in decl.translatedSignature.parameters.enumerated() { + let original = decl.swiftSignature.parameters[i] + let parameterName = original.parameterName ?? "_\(i)" + let converted = parameter.conversion.render(&printer, parameterName) + let lowered: String + if parameter.conversion.isTrivial { + lowered = converted + } else { + // Store the conversion to a temporary variable. + lowered = "\(parameterName)$" + printer.print("var \(lowered) = \(converted);") + } + downCallArguments.append(lowered) + } + + // 'self' parameter. + if let selfParameter = decl.translatedSignature.selfParameter { + let lowered = selfParameter.conversion.render(&printer, "this") + downCallArguments.append(lowered) + } + + // Indirect return receivers. + for outParameter in decl.translatedSignature.result.outParameters { + let memoryLayout = renderMemoryLayoutValue(for: outParameter.type) + + let arena = if let className = outParameter.type.className, + self.importedTypes[className] != nil { + // Use passed-in 'SwiftArena' for 'SwiftValue'. + "swiftArena$" + } else { + // Otherwise use the temporary 'Arena'. + "arena$" + } + + let varName = "_result" + outParameter.name + + printer.print( + "MemorySegment \(varName) = \(arena).allocate(\(memoryLayout));" + ) + downCallArguments.append(varName) + } + + //=== Part 3: Downcall. + printer.print( + """ + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(\(downCallArguments.joined(separator: ", "))); + } + """ + ) + let downCall = "mh$.invokeExact(\(downCallArguments.joined(separator: ", ")))" + + //=== Part 4: Convert the return value. + if isConstructor { + // For constructors, the caller expects the "self" memory segment. + printer.print("\(downCall);") + printer.print("return _result;") + } else if decl.translatedSignature.result.javaResultType == .void { + printer.print("\(downCall);") + } else { + let placeholder = if decl.translatedSignature.result.outParameters.isEmpty { + downCall + } else { + // FIXME: Support cdecl thunk returning a value while populating the out parameters. + "_result" + } + let result = decl.translatedSignature.result.conversion.render(&printer, placeholder) + + if decl.translatedSignature.result.javaResultType != .void { + printer.print("return \(result);") + } else { + printer.print("\(result);") + } + } + + printer.outdent() + printer.print( + """ + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + """ + ) + } + + func renderMemoryLayoutValue(for javaType: JavaType) -> String { + if let layout = ForeignValueLayout(javaType: javaType) { + return layout.description + } else if case .class(package: _, name: let customClass) = javaType { + return ForeignValueLayout(customType: customClass).description + } else { + fatalError("renderMemoryLayoutValue not supported for \(javaType)") + } + } +} + +extension JavaConversionStep { + /// Whether the conversion uses SwiftArena. + var requiresSwiftArena: Bool { + switch self { + case .pass, .swiftValueSelfSegment, .construct, .cast, .call: + return false + case .constructSwiftValue: + return true + } + } + + /// Whether the conversion uses temporary Arena. + var requiresTemporaryArena: Bool { + switch self { + case .pass, .swiftValueSelfSegment, .construct, .constructSwiftValue, .cast: + return false + case .call(_, let withArena): + return withArena + } + } + + /// Whether if the result evaluation is trivial. + /// + /// If this is false, it's advised to store it to a variable if it's used multiple times + var isTrivial: Bool { + switch self { + case .pass, .swiftValueSelfSegment: + return true + case .cast, .construct, .constructSwiftValue, .call: + return false + } + } + + /// Returns the conversion string applied to the placeholder. + func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { + // NOTE: 'printer' is used if the conversion wants to cause side-effects. + // E.g. storing a temporary values into a variable. + switch self { + case .pass: + return placeholder + + case .swiftValueSelfSegment: + return "\(placeholder).$memorySegment()" + + case .call(let function, let withArena): + let arenaArg = withArena ? ", arena$" : "" + return "\(function)(\(placeholder)\(arenaArg))" + + case .constructSwiftValue(let javaType): + return "new \(javaType.className!)(\(placeholder), swiftArena$)" + + case .construct(let javaType): + return "new \(javaType)(\(placeholder))" + + case .cast(let javaType): + return "(\(javaType)) \(placeholder)" + } + } +} diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift new file mode 100644 index 00000000..4d5fc80a --- /dev/null +++ b/Sources/JExtractSwift/Swift2JavaTranslator+JavaTranslation.swift @@ -0,0 +1,437 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import JavaTypes + +extension Swift2JavaTranslator { + func translate( + swiftSignature: SwiftFunctionSignature, + as apiKind: SwiftAPIKind + ) throws -> TranslatedFunctionSignature { + let lowering = CdeclLowering(swiftStdlibTypes: self.swiftStdlibTypes) + let loweredSignature = try lowering.lowerFunctionSignature(swiftSignature, apiKind: apiKind) + + let translation = JavaTranslation(swiftStdlibTypes: self.swiftStdlibTypes) + let translated = try translation.translate(loweredFunctionSignature: loweredSignature) + + return translated + } +} + +/// Represent a parameter in Java code. +struct JavaParameter { + /// The type. + var type: JavaType + + /// The name. + var name: String +} + +/// Represent a Swift API parameter translated to Java. +struct TranslatedParameter { + /// Java parameter(s) mapped to the Swift parameter. + /// + /// Array because one Swift parameter can be mapped to multiple parameters. + var javaParameters: [JavaParameter] + + /// Describes how to convert the Java parameter to the lowered arguments for + /// the foreign function. + var conversion: JavaConversionStep +} + +/// Represent a Swift API result translated to Java. +struct TranslatedResult { + /// Java type that represents the Swift result type. + var javaResultType: JavaType + + /// Required indirect return receivers for receiving the result. + /// + /// 'JavaParameter.name' is the suffix for the receiver variable names. For example + /// + /// var _result_pointer = MemorySegment.allocate(...) + /// var _result_count = MemroySegment.allocate(...) + /// downCall(_result_pointer, _result_count) + /// return constructResult(_result_pointer, _result_count) + /// + /// This case, there're two out parameter, named '_pointer' and '_count'. + var outParameters: [JavaParameter] + + /// Describes how to construct the Java result from the foreign function return + /// value and/or the out parameters. + var conversion: JavaConversionStep +} + +/// Translated function signature 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 + + var selfParameter: TranslatedParameter? + var parameters: [TranslatedParameter] + var result: TranslatedResult +} + +extension TranslatedFunctionSignature { + /// Whether or not if the down-calling requires temporary "Arena" which is + /// only used during the down-calling. + var requiresTemporaryArena: Bool { + if self.parameters.contains(where: { $0.conversion.requiresTemporaryArena }) { + return true + } + if self.selfParameter?.conversion.requiresTemporaryArena ?? false { + return true + } + if self.result.conversion.requiresTemporaryArena { + return true + } + return false + } + + /// Whether if the down-calling requires "SwiftArena" or not, which should be + /// passed-in by the API caller. This is needed if the API returns a `SwiftValue` + var requiresSwiftArena: Bool { + return self.result.conversion.requiresSwiftArena + } +} + +struct JavaTranslation { + var swiftStdlibTypes: SwiftStandardLibraryTypes + + /// Translate Swift API to user-facing Java API. + /// + /// Note that the result signature is for the high-level Java API, not the + /// low-level FFM down-calling interface. + func translate( + loweredFunctionSignature: LoweredFunctionSignature + ) throws -> TranslatedFunctionSignature { + let swiftSignature = loweredFunctionSignature.original + + // 'self' + let selfParameter: TranslatedParameter? + if case .instance(let swiftSelf) = swiftSignature.selfParameter { + selfParameter = try self.translate( + swiftParam: swiftSelf, + loweredParam: loweredFunctionSignature.selfParameter!, + parameterName: swiftSelf.parameterName ?? "self" + ) + } else { + selfParameter = nil + } + + // Regular parameters. + let parameters: [TranslatedParameter] = try swiftSignature.parameters.enumerated() + .map { (idx, swiftParam) in + let loweredParam = loweredFunctionSignature.parameters[idx] + let parameterName = swiftParam.parameterName ?? "_\(idx)" + return try self.translate( + swiftParam: swiftParam, + loweredParam: loweredParam, + parameterName: parameterName + ) + } + + // Result. + let result = try self.translate( + swiftResult: swiftSignature.result, + loweredResult: loweredFunctionSignature.result + ) + + return TranslatedFunctionSignature( + loweredSignature: loweredFunctionSignature, + selfParameter: selfParameter, + parameters: parameters, + result: result + ) + } + + /// Translate + func translate( + swiftParam: SwiftParameter, + loweredParam: LoweredParameter, + parameterName: String + ) throws -> TranslatedParameter { + let swiftType = swiftParam.type + + // If there is a 1:1 mapping between this Swift type and a C type, that can + // be expressed as a Java primitive type. + if let cType = try? CType(cdeclType: swiftType) { + let javaType = cType.javaType + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: javaType, + name: loweredParam.cdeclParameters[0].parameterName! + ) + ], + conversion: .pass + ) + } + + switch swiftType { + case .metatype: + // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType"), + name: loweredParam.cdeclParameters[0].parameterName!) + ], + conversion: .swiftValueSelfSegment + ) + + case .nominal(let swiftNominalType): + if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + if swiftParam.convention == .inout { + // FIXME: Support non-trivial 'inout' for builtin types. + throw JavaTranslationError.inoutNotSupported(swiftType) + } + switch knownType { + case .unsafePointer, .unsafeMutablePointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + + case .string: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: .javaLangString, + name: loweredParam.cdeclParameters[0].parameterName! + ) + ], + conversion: .call(function: "SwiftKit.toCString", withArena: true) + ) + + default: + throw JavaTranslationError.unhandledType(swiftType) + } + } + + // Generic types are not supported yet. + guard swiftNominalType.genericArguments == nil else { + throw JavaTranslationError.unhandledType(swiftType) + } + + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: try translate(swiftType: swiftType), + name: loweredParam.cdeclParameters[0].parameterName! + ) + ], + conversion: .swiftValueSelfSegment + ) + + case .tuple: + // TODO: Implement. + throw JavaTranslationError.unhandledType(swiftType) + + case .function(let fn) where fn.parameters.isEmpty && fn.resultType.isVoid: + return TranslatedParameter( + javaParameters: [ + JavaParameter( + type: JavaType.class(package: "java.lang", name: "Runnable"), + name: loweredParam.cdeclParameters[0].parameterName!) + ], + conversion: .call(function: "SwiftKit.toUpcallStub", withArena: true) + ) + + case .optional, .function: + throw JavaTranslationError.unhandledType(swiftType) + } + } + + func translate( + swiftResult: SwiftResult, + loweredResult: LoweredResult + ) throws -> TranslatedResult { + let swiftType = swiftResult.type + + // If there is a 1:1 mapping between this Swift type and a C type, that can + // be expressed as a Java primitive type. + if let cType = try? CType(cdeclType: swiftType) { + let javaType = cType.javaType + return TranslatedResult( + javaResultType: javaType, + outParameters: [], + conversion: .cast(javaType) + ) + } + + switch swiftType { + case .metatype(_): + // Metatype are expressed as 'org.swift.swiftkit.SwiftAnyType' + let javaType = JavaType.class(package: "org.swift.swiftkit", name: "SwiftAnyType") + return TranslatedResult( + javaResultType: javaType, + outParameters: [], + conversion: .construct(javaType) + ) + + case .nominal(let swiftNominalType): + if let knownType = swiftNominalType.nominalTypeDecl.knownStandardLibraryType { + switch knownType { + case .unsafePointer, .unsafeMutablePointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + case .string: + // FIXME: Implement + throw JavaTranslationError.unhandledType(swiftType) + default: + throw JavaTranslationError.unhandledType(swiftType) + } + } + + // Generic types are not supported yet. + guard swiftNominalType.genericArguments == nil else { + throw JavaTranslationError.unhandledType(swiftType) + } + + let javaType: JavaType = .class(package: nil, name: swiftNominalType.nominalTypeDecl.name) + return TranslatedResult( + javaResultType: javaType, + outParameters: [ + JavaParameter(type: javaType, name: "") + ], + conversion: .constructSwiftValue(javaType) + ) + + case .tuple: + // TODO: Implement. + throw JavaTranslationError.unhandledType(swiftType) + + case .optional, .function: + throw JavaTranslationError.unhandledType(swiftType) + } + + } + + func translate( + swiftType: SwiftType + ) throws -> JavaType { + guard let nominalName = swiftType.asNominalTypeDeclaration?.name else { + throw JavaTranslationError.unhandledType(swiftType) + } + return .class(package: nil, name: nominalName) + } +} + +/// Describes how to convert values between Java types and FFM types. +enum JavaConversionStep { + // Pass through. + case pass + + // 'value.$memorySegment()' + case swiftValueSelfSegment + + // call specified function using the placeholder as arguments. + // If `withArena` is true, `arena$` argument is added. + case call(function: String, withArena: Bool) + + // Call 'new \(Type)(\(placeholder), swiftArena$)'. + case constructSwiftValue(JavaType) + + // Construct the type using the placeholder as arguments. + case construct(JavaType) + + // Casting the placeholder to the certain type. + case cast(JavaType) +} + +extension CType { + /// Map lowered C type to Java type for FFM binding. + var javaType: JavaType { + switch self { + case .void: return .void + + case .integral(.bool): return .boolean + case .integral(.signed(bits: 8)): return .byte + case .integral(.signed(bits: 16)): return .short + case .integral(.signed(bits: 32)): return .int + case .integral(.signed(bits: 64)): return .long + case .integral(.unsigned(bits: 8)): return .byte + case .integral(.unsigned(bits: 16)): return .short + case .integral(.unsigned(bits: 32)): return .int + case .integral(.unsigned(bits: 64)): return .long + + case .floating(.float): return .float + case .floating(.double): return .double + + // FIXME: 32 bit consideration. + // The 'FunctionDescriptor' uses 'SWIFT_INT' which relies on the running + // machine arch. That means users can't pass Java 'long' values to the + // function without casting. But how do we generate code that runs both + // 32 and 64 bit machine? + case .integral(.ptrdiff_t), .integral(.size_t): + return .long + + case .pointer(_), .function(resultType: _, parameters: _, variadic: _): + return .javaForeignMemorySegment + + case .qualified(const: _, volatile: _, let inner): + return inner.javaType + + case .tag(_): + fatalError("unsupported") + case .integral(.signed(bits: _)), .integral(.unsigned(bits: _)): + fatalError("unreachable") + } + } + + /// Map lowered C type to FFM ValueLayout. + var foreignValueLayout: ForeignValueLayout { + switch self { + case .integral(.bool): return .SwiftBool + case .integral(.signed(bits: 8)): return .SwiftInt8 + case .integral(.signed(bits: 16)): return .SwiftInt16 + case .integral(.signed(bits: 32)): return .SwiftInt32 + case .integral(.signed(bits: 64)): return .SwiftInt64 + + case .integral(.unsigned(bits: 8)): return .SwiftInt8 + case .integral(.unsigned(bits: 16)): return .SwiftInt16 + case .integral(.unsigned(bits: 32)): return .SwiftInt32 + case .integral(.unsigned(bits: 64)): return .SwiftInt64 + + case .floating(.double): return .SwiftDouble + case .floating(.float): return .SwiftFloat + + case .integral(.ptrdiff_t), .integral(.size_t): + return .SwiftInt + + case .pointer(_), .function(resultType: _, parameters: _, variadic: _): + return .SwiftPointer + + case .qualified(const: _, volatile: _, type: let inner): + return inner.foreignValueLayout + + case .tag(_): + fatalError("unsupported") + case .void, .integral(.signed(bits: _)), .integral(.unsigned(bits: _)): + fatalError("unreachable") + } + } +} + +enum JavaTranslationError: Error { + case inoutNotSupported(SwiftType) + case unhandledType(SwiftType) +} diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift deleted file mode 100644 index fa5b6318..00000000 --- a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift +++ /dev/null @@ -1,48 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import Foundation -import SwiftBasicFormat -import SwiftParser -import SwiftSyntax - -extension Swift2JavaTranslator { - - public func javaMemoryLayoutDescriptors( - forParametersOf decl: ImportedFunc, - paramPassingStyle: SelfParameterVariant? - ) -> [ForeignValueLayout] { - var layouts: [ForeignValueLayout] = [] - layouts.reserveCapacity(decl.parameters.count + 1) - - for param in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { - if param.type.cCompatibleJavaMemoryLayout == CCompatibleJavaMemoryLayout.primitive(.void) { - continue - } - - var layout = param.type.foreignValueLayout - layout.inlineComment = "\(param.effectiveValueName)" - layouts.append(layout) - } - - // an indirect return passes the buffer as the last parameter to our thunk - if decl.isIndirectReturn { - var layout = ForeignValueLayout.SwiftPointer - layout.inlineComment = "indirect return buffer" - layouts.append(layout) - } - - return layouts - } -} diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 30dc3fb7..98b3c767 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -12,10 +12,9 @@ // //===----------------------------------------------------------------------===// -import Foundation -import SwiftBasicFormat -import SwiftParser +import JavaTypes import SwiftSyntax +import SwiftSyntaxBuilder // ==== --------------------------------------------------------------------------------------------------------------- // MARK: File writing @@ -32,16 +31,16 @@ extension Swift2JavaTranslator { public func writeExportedJavaSources(outputDirectory: String, printer: inout CodePrinter) throws { for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { - let filename = "\(ty.javaClassName).java" + let filename = "\(ty.swiftNominal.name).java" log.info("Printing contents: \(filename)") - printImportedClass(&printer, ty) + printImportedNominal(&printer, ty) if let outputFile = try printer.writeContents( outputDirectory: outputDirectory, javaPackagePath: javaPackagePath, filename: filename ) { - print("[swift-java] Generated: \(ty.javaClassName.bold).java (at \(outputFile))") + print("[swift-java] Generated: \(ty.swiftNominal.name.bold).java (at \(outputFile))") } } @@ -55,119 +54,10 @@ extension Swift2JavaTranslator { javaPackagePath: javaPackagePath, filename: filename) { - print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile)") + print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile))") } } } - - public func writeSwiftThunkSources(outputDirectory: String) throws { - var printer = CodePrinter() - - try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer) - } - - public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws { - let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" - let moduleFilename = "\(moduleFilenameBase).swift" - do { - log.info("Printing contents: \(moduleFilename)") - - try printGlobalSwiftThunkSources(&printer) - - if let outputFile = try printer.writeContents( - outputDirectory: outputDirectory, - javaPackagePath: nil, - filename: moduleFilename) - { - print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)") - } - } catch { - log.warning("Failed to write to Swift thunks: \(moduleFilename)") - } - - // === All types - for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { - let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" - let filename = "\(fileNameBase).swift" - log.info("Printing contents: \(filename)") - - do { - try printSwiftThunkSources(&printer, ty: ty) - - if let outputFile = try printer.writeContents( - outputDirectory: outputDirectory, - javaPackagePath: nil, - filename: filename) - { - print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)") - } - } catch { - log.warning("Failed to write to Swift thunks: \(filename)") - } - } - } - - public func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { - let stt = SwiftThunkTranslator(self) - - printer.print( - """ - // Generated by swift-java - - import SwiftKitSwift - """) - - for thunk in stt.renderGlobalThunks() { - printer.print(thunk) - printer.println() - } - } - - public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ImportedFunc) { - let stt = SwiftThunkTranslator(self) - - for thunk in stt.render(forFunc: decl) { - printer.print(thunk) - printer.println() - } - } - - package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws { - let stt = SwiftThunkTranslator(self) - - printer.print( - """ - // Generated by swift-java - - import SwiftKitSwift - - """ - ) - - for thunk in stt.renderThunks(forType: ty) { - printer.print("\(thunk)") - printer.print("") - } - } - - /// A module contains all static and global functions from the Swift module, - /// potentially from across multiple swift interfaces. - public func writeExportedJavaModule(outputDirectory: String) throws { - var printer = CodePrinter() - try writeExportedJavaModule(outputDirectory: outputDirectory, printer: &printer) - } - - public func writeExportedJavaModule(outputDirectory: String, printer: inout CodePrinter) throws { - printModule(&printer) - - if let file = try printer.writeContents( - outputDirectory: outputDirectory, - javaPackagePath: javaPackagePath, - filename: "\(swiftModuleName).java" - ) { - self.log.info("Generated: \(file): \("done".green).") - } - } } // ==== --------------------------------------------------------------------------------------------------------------- @@ -192,21 +82,12 @@ extension Swift2JavaTranslator { } } - package func printImportedClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + package func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) printImports(&printer) printNominal(&printer, decl) { printer in - // Prepare type metadata, we're going to need these when invoking e.g. initializers so cache them in a static. - // We call into source swift-java source generated accessors which give us the type of the Swift object: - // TODO: seems we no longer need the mangled name per se, so avoiding such constant and downcall - // printer.printParts( - // "public static final String TYPE_MANGLED_NAME = ", - // SwiftKitPrinting.renderCallGetSwiftTypeMangledName(module: self.swiftModuleName, nominal: decl), - // ";" - // ) - // We use a static field to abuse the initialization order such that by the time we get type metadata, // we already have loaded the library where it will be obtained from. printer.printParts( @@ -234,14 +115,22 @@ extension Swift2JavaTranslator { printer.print("") + printer.print( + """ + public \(decl.swiftNominal.name)(MemorySegment segment, SwiftArena arena) { + super(segment, arena); + } + """ + ) + // Initializers for initDecl in decl.initializers { - printClassConstructors(&printer, initDecl) + printInitializerDowncallConstructors(&printer, initDecl) } // Properties - for varDecl in decl.variables { - printVariableDowncallMethods(&printer, varDecl) + for accessorDecl in decl.variables { + printFunctionDowncallMethods(&printer, accessorDecl) } // Methods @@ -250,7 +139,7 @@ extension Swift2JavaTranslator { } // Helper methods and default implementations - printHeapObjectToStringMethod(&printer, decl) + printToStringMethod(&printer, decl) } } @@ -284,15 +173,14 @@ extension Swift2JavaTranslator { _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void ) { let parentProtocol: String - if decl.isReferenceType { + if decl.swiftNominal.isReferenceType { parentProtocol = "SwiftHeapObject" } else { parentProtocol = "SwiftValue" } - printer.printTypeDecl("public final class \(decl.javaClassName) extends SwiftInstance implements \(parentProtocol)") { + printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends SwiftInstance implements \(parentProtocol)") { printer in - // Constants printClassConstants(printer: &printer) @@ -301,7 +189,7 @@ extension Swift2JavaTranslator { } public func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) { - printer.printTypeDecl("public final class \(swiftModuleName)") { printer in + printer.printBraceBlock("public final class \(swiftModuleName)") { printer in printPrivateConstructor(&printer, swiftModuleName) // Constants @@ -400,8 +288,7 @@ extension Swift2JavaTranslator { private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printer.print( """ - private static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment()); - + public static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment()); public final GroupLayout $layout() { return $LAYOUT; } @@ -409,594 +296,19 @@ extension Swift2JavaTranslator { ) } - public func printClassConstructors(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - guard let parentName = decl.parent else { - fatalError("init must be inside a parent type! Was: \(decl)") - } - printer.printSeparator(decl.identifier) - - let descClassIdentifier = renderDescClassName(decl) - printer.printTypeDecl("private static class \(descClassIdentifier)") { printer in - printFunctionDescriptorValue(&printer, decl) - printAccessorFunctionAddr(&printer, decl) - printMethodDowncallHandleForAddrDesc(&printer) - } - - printNominalInitializerConstructors(&printer, decl, parentName: parentName) - } - - public func printNominalInitializerConstructors( - _ printer: inout CodePrinter, - _ decl: ImportedFunc, - parentName: TranslatedType - ) { - let descClassIdentifier = renderDescClassName(decl) - - printer.print( - """ - /** - * Create an instance of {@code \(parentName.unqualifiedJavaTypeName)}. - * This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime. - * - \(decl.renderCommentSnippet ?? " *") - */ - public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper)), SwiftArena arena) { - super(() -> { - var mh$ = \(descClassIdentifier).HANDLE; - try { - MemorySegment _result = arena.allocate($LAYOUT); - - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: nil))); - } - mh$.invokeExact( - \(renderForwardJavaParams(decl, paramPassingStyle: nil)), - /* indirect return buffer */_result - ); - return _result; - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } - }, arena); - } - """ - ) - } - - public func printFunctionDowncallMethods(_ printer: inout CodePrinter, _ decl: ImportedFunc) { - printer.printSeparator(decl.identifier) - - printer.printTypeDecl("private static class \(decl.baseIdentifier)") { printer in - printFunctionDescriptorValue(&printer, decl) - printAccessorFunctionAddr(&printer, decl) - printMethodDowncallHandleForAddrDesc(&printer) - } - - printFunctionDescriptorMethod(&printer, decl: decl) - printFunctionMethodHandleMethod(&printer, decl: decl) - printFunctionAddressMethod(&printer, decl: decl) - - // Render the basic "make the downcall" function - if decl.hasParent { - printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: .memorySegment) - printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: .wrapper) - } else { - printFuncDowncallMethod(&printer, decl: decl, paramPassingStyle: nil) - } - } - - private func printFunctionAddressMethod( - _ printer: inout CodePrinter, - decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil - ) { - - let addrName = accessorKind.renderAddrFieldName - let methodNameSegment = accessorKind.renderMethodNameSegment - let snippet = decl.renderCommentSnippet ?? "* " - - printer.print( - """ - /** - * Address for: - \(snippet) - */ - public static MemorySegment \(decl.baseIdentifier)\(methodNameSegment)$address() { - return \(decl.baseIdentifier).\(addrName); - } - """ - ) - } - - private func printFunctionMethodHandleMethod( - _ printer: inout CodePrinter, - decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil - ) { - let handleName = accessorKind.renderHandleFieldName - let methodNameSegment = accessorKind.renderMethodNameSegment - let snippet = decl.renderCommentSnippet ?? "* " - - printer.print( - """ - /** - * Downcall method handle for: - \(snippet) - */ - public static MethodHandle \(decl.baseIdentifier)\(methodNameSegment)$handle() { - return \(decl.baseIdentifier).\(handleName); - } - """ - ) - } - - private func printFunctionDescriptorMethod( - _ printer: inout CodePrinter, - decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil - ) { - let descName = accessorKind.renderDescFieldName - let methodNameSegment = accessorKind.renderMethodNameSegment - let snippet = decl.renderCommentSnippet ?? "* " - - printer.print( - """ - /** - * Function descriptor for: - \(snippet) - */ - public static FunctionDescriptor \(decl.baseIdentifier)\(methodNameSegment)$descriptor() { - return \(decl.baseIdentifier).\(descName); - } - """ - ) - } - - public func printVariableDowncallMethods(_ printer: inout CodePrinter, _ decl: ImportedVariable) { - printer.printSeparator(decl.identifier) - - printer.printTypeDecl("private static class \(decl.baseIdentifier)") { printer in - for accessorKind in decl.supportedAccessorKinds { - guard let accessor = decl.accessorFunc(kind: accessorKind) else { - log.warning("Skip print for \(accessorKind) of \(decl.identifier)!") - continue - } - - printFunctionDescriptorValue(&printer, accessor, accessorKind: accessorKind) - printAccessorFunctionAddr(&printer, accessor, accessorKind: accessorKind) - printMethodDowncallHandleForAddrDesc(&printer, accessorKind: accessorKind) - } - } - - // First print all the supporting infra - for accessorKind in decl.supportedAccessorKinds { - guard let accessor = decl.accessorFunc(kind: accessorKind) else { - log.warning("Skip print for \(accessorKind) of \(decl.identifier)!") - continue - } - printFunctionDescriptorMethod(&printer, decl: accessor, accessorKind: accessorKind) - printFunctionMethodHandleMethod(&printer, decl: accessor, accessorKind: accessorKind) - printFunctionAddressMethod(&printer, decl: accessor, accessorKind: accessorKind) - } - - // Then print the actual downcall methods - for accessorKind in decl.supportedAccessorKinds { - guard let accessor = decl.accessorFunc(kind: accessorKind) else { - log.warning("Skip print for \(accessorKind) of \(decl.identifier)!") - continue - } - - // Render the basic "make the downcall" function - if decl.hasParent { - printFuncDowncallMethod( - &printer, decl: accessor, paramPassingStyle: .memorySegment, accessorKind: accessorKind) - printFuncDowncallMethod( - &printer, decl: accessor, paramPassingStyle: .wrapper, accessorKind: accessorKind) - } else { - printFuncDowncallMethod( - &printer, decl: accessor, paramPassingStyle: nil, accessorKind: accessorKind) - } - } - } - - func printAccessorFunctionAddr( - _ printer: inout CodePrinter, _ decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil - ) { - let thunkName = thunkNameRegistry.functionThunkName(module: self.swiftModuleName, decl: decl) - printer.print( - """ - public static final MemorySegment \(accessorKind.renderAddrFieldName) = - \(self.swiftModuleName).findOrThrow("\(thunkName)"); - """ - ) - } - - func printMethodDowncallHandleForAddrDesc( - _ printer: inout CodePrinter, accessorKind: VariableAccessorKind? = nil - ) { - printer.print( - """ - public static final MethodHandle \(accessorKind.renderHandleFieldName) = Linker.nativeLinker().downcallHandle(\(accessorKind.renderAddrFieldName), \(accessorKind.renderDescFieldName)); - """ - ) - } - - public func printFuncDowncallMethod( - _ printer: inout CodePrinter, - decl: ImportedFunc, - paramPassingStyle: SelfParameterVariant?, - accessorKind: VariableAccessorKind? = nil - ) { - let returnTy = decl.returnType.javaType - - let maybeReturnCast: String - if decl.returnType.javaType == .void { - maybeReturnCast = "" // nothing to return or cast to - } else { - maybeReturnCast = "return (\(returnTy))" - } - - // TODO: we could copy the Swift method's documentation over here, that'd be great UX - let javaDocComment: String = - """ - /** - * Downcall to Swift: - \(decl.renderCommentSnippet ?? "* ") - */ - """ - - // An identifier may be "getX", "setX" or just the plain method name - let identifier = accessorKind.renderMethodName(decl) - - if paramPassingStyle == SelfParameterVariant.wrapper { - let guardFromDestroyedObjectCalls: String = - if decl.hasParent { - """ - $ensureAlive(); - """ - } else { "" } - - // delegate to the MemorySegment "self" accepting overload - printer.print( - """ - \(javaDocComment) - public \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) { - \(guardFromDestroyedObjectCalls) - \(maybeReturnCast) \(identifier)(\(renderForwardJavaParams(decl, paramPassingStyle: .wrapper))); - } - """ - ) - return - } - - let needsArena = downcallNeedsConfinedArena(decl) - let handleName = accessorKind.renderHandleFieldName - - printer.printParts( - """ - \(javaDocComment) - public static \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, paramPassingStyle: paramPassingStyle))) { - var mh$ = \(decl.baseIdentifier).\(handleName); - \(renderTry(withArena: needsArena)) - """, - renderUpcallHandles(decl), - renderParameterDowncallConversions(decl), - """ - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: .memorySegment))); - } - \(maybeReturnCast) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: paramPassingStyle))); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } - } - """ - ) - } - - public func printPropertyAccessorDowncallMethod( - _ printer: inout CodePrinter, - decl: ImportedFunc, - paramPassingStyle: SelfParameterVariant? - ) { - let returnTy = decl.returnType.javaType - - let maybeReturnCast: String - if decl.returnType.javaType == .void { - maybeReturnCast = "" // nothing to return or cast to - } else { - maybeReturnCast = "return (\(returnTy))" - } - - if paramPassingStyle == SelfParameterVariant.wrapper { - // delegate to the MemorySegment "self" accepting overload - printer.print( - """ - /** - * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") - * } - */ - public \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) { - \(maybeReturnCast) \(decl.baseIdentifier)(\(renderForwardJavaParams(decl, paramPassingStyle: .wrapper))); - } - """ - ) - return - } - - printer.print( - """ - /** - * {@snippet lang=swift : - * \(/*TODO: make a printSnippet func*/decl.syntax ?? "") - * } - */ - public static \(returnTy) \(decl.baseIdentifier)(\(renderJavaParamDecls(decl, paramPassingStyle: paramPassingStyle))) { - var mh$ = \(decl.baseIdentifier).HANDLE; - try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: .memorySegment))); - } - \(maybeReturnCast) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: paramPassingStyle))); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } - } - """ - ) - } - - /// Given a function like `init(cap:name:)`, renders a name like `init_cap_name` - public func renderDescClassName(_ decl: ImportedFunc) -> String { - var ps: [String] = [decl.baseIdentifier] - var pCounter = 0 - - func nextUniqueParamName() -> String { - pCounter += 1 - return "p\(pCounter)" - } - - for p in decl.effectiveParameters(paramPassingStyle: nil) { - let param = "\(p.effectiveName ?? nextUniqueParamName())" - ps.append(param) - } - - let res = ps.joined(separator: "_") - return res - } - - /// Do we need to construct an inline confined arena for the duration of the downcall? - public func downcallNeedsConfinedArena(_ decl: ImportedFunc) -> Bool { - for p in decl.parameters { - // We need to detect if any of the parameters is a closure we need to prepare - // an upcall handle for. - if p.type.javaType.isSwiftClosure { - return true - } - - if p.type.javaType.isString { - return true - } - } - - return false - } - - public func renderTry(withArena: Bool) -> String { - if withArena { - "try (var arena = Arena.ofConfined()) {" - } else { - "try {" - } - } - - public func renderJavaParamDecls(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String { - var ps: [String] = [] - var pCounter = 0 - - func nextUniqueParamName() -> String { - pCounter += 1 - return "p\(pCounter)" - } - - for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { - let param = "\(p.type.javaType.description) \(p.effectiveName ?? nextUniqueParamName())" - ps.append(param) - } - - let res = ps.joined(separator: ", ") - return res - } - - // TODO: these are stateless, find new place for them? - public func renderSwiftParamDecls( - _ decl: ImportedFunc, - paramPassingStyle: SelfParameterVariant?, - style: ParameterVariant? = nil - ) -> String { - var ps: [String] = [] - var pCounter = 0 - - func nextUniqueParamName() -> String { - pCounter += 1 - return "p\(pCounter)" - } - - for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { - let firstName = p.firstName ?? "_" - let secondName = p.secondName ?? p.firstName ?? nextUniqueParamName() - - let paramTy: String = - if style == .cDeclThunk, p.type.javaType.isString { - "UnsafeMutablePointer" // TODO: is this ok? - } else if paramPassingStyle == .swiftThunkSelf { - "\(p.type.cCompatibleSwiftType)" - } else { - p.type.swiftTypeName.description - } - - let param = - if firstName == secondName { - // We have to do this to avoid a 'extraneous duplicate parameter name; 'number' already has an argument label' warning - "\(firstName): \(paramTy)" - } else { - "\(firstName) \(secondName): \(paramTy)" - } - ps.append(param) - } - - if paramPassingStyle == .swiftThunkSelf { - ps.append("_self: UnsafeMutableRawPointer") - } - - let res = ps.joined(separator: ", ") - return res - } - - public func renderUpcallHandles(_ decl: ImportedFunc) -> String { - var printer = CodePrinter() - for p in decl.parameters where p.type.javaType.isSwiftClosure { - if p.type.javaType == .javaLangRunnable { - let paramName = p.secondName ?? p.firstName ?? "_" - let handleDesc = p.type.javaType.prepareClosureDowncallHandle( - decl: decl, parameter: paramName) - printer.print(handleDesc) - } - } - - return printer.contents - } - - public func renderParameterDowncallConversions(_ decl: ImportedFunc) -> String { - var printer = CodePrinter() - for p in decl.parameters { - if p.type.javaType.isString { - printer.print( - """ - var \(p.effectiveValueName)$ = arena.allocateFrom(\(p.effectiveValueName)); - """ - ) - } - } - - return printer.contents - } - - public func renderForwardJavaParams( - _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant? - ) -> String { - var ps: [String] = [] - var pCounter = 0 - - func nextUniqueParamName() -> String { - pCounter += 1 - return "p\(pCounter)" - } - - for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { - // FIXME: fix the handling here we're already a memory segment - let param: String - if p.effectiveName == "self$" { - precondition(paramPassingStyle == .memorySegment) - param = "self$" - } else if p.type.javaType.isString { - // TODO: make this less one-off and maybe call it "was adapted"? - if paramPassingStyle == .wrapper { - // pass it raw, we're not performing adaptation here it seems as we're passing wrappers around - param = "\(p.effectiveValueName)" - } else { - param = "\(p.effectiveValueName)$" - } - } else { - param = "\(p.renderParameterForwarding() ?? nextUniqueParamName())" - } - ps.append(param) - } - - // Add the forwarding "self" - if paramPassingStyle == .wrapper && !decl.isInit { - ps.append("$memorySegment()") - } - - return ps.joined(separator: ", ") - } - - // TODO: these are stateless, find new place for them? - public func renderForwardSwiftParams( - _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant? - ) -> String { - var ps: [String] = [] - - for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { - if let firstName = p.firstName { - ps.append("\(firstName): \(p.effectiveValueName)") - } else { - ps.append("\(p.effectiveValueName)") - } - } - - return ps.joined(separator: ", ") - } - - public func printFunctionDescriptorValue( - _ printer: inout CodePrinter, - _ decl: ImportedFunc, - accessorKind: VariableAccessorKind? = nil - ) { - let fieldName = accessorKind.renderDescFieldName - printer.start("public static final FunctionDescriptor \(fieldName) = ") - - let isIndirectReturn = decl.isIndirectReturn - - let parameterLayoutDescriptors: [ForeignValueLayout] = javaMemoryLayoutDescriptors( - forParametersOf: decl, - paramPassingStyle: .pointer - ) - - if decl.returnType.javaType == .void || isIndirectReturn { - printer.print("FunctionDescriptor.ofVoid(") - printer.indent() - } else { - printer.print("FunctionDescriptor.of(") - printer.indent() - - // Write return type - let returnTyIsLastTy = decl.parameters.isEmpty && !decl.hasParent - if decl.isInit { - // when initializing, we return a pointer to the newly created object - printer.print( - "/* -> */\(ForeignValueLayout.SwiftPointer)", .parameterNewlineSeparator(returnTyIsLastTy) - ) - } else { - var returnDesc = decl.returnType.foreignValueLayout - returnDesc.inlineComment = " -> " - printer.print(returnDesc, .parameterNewlineSeparator(returnTyIsLastTy)) - } - } - - // Write all parameters (including synthesized ones, like self) - for (desc, isLast) in parameterLayoutDescriptors.withIsLast { - printer.print(desc, .parameterNewlineSeparator(isLast)) - } - - printer.outdent() - printer.print(");") - } - - package func printHeapObjectToStringMethod( + package func printToStringMethod( _ printer: inout CodePrinter, _ decl: ImportedNominalType ) { printer.print( """ @Override public String toString() { - return getClass().getSimpleName() + "(" + - SwiftKit.nameOfSwiftType($swiftType().$memorySegment(), true) + - ")@" + $memorySegment(); + return getClass().getSimpleName() + + "(" + + SwiftKit.nameOfSwiftType($swiftType().$memorySegment(), true) + + ")@" + + $memorySegment(); } """) } - } diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index 25fd3e44..c8ed5bbe 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -42,6 +42,8 @@ public final class Swift2JavaTranslator { // ==== Output state + package var importedGlobalVariables: [ImportedFunc] = [] + package var importedGlobalFuncs: [ImportedFunc] = [] /// A mapping from Swift type names (e.g., A.B) over to the imported nominal @@ -50,9 +52,9 @@ public final class Swift2JavaTranslator { package var swiftStdlibTypes: SwiftStandardLibraryTypes - let symbolTable: SwiftSymbolTable + package let symbolTable: SwiftSymbolTable - var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry() + package var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry() /// The name of the Swift module being translated. var swiftModuleName: String { @@ -175,12 +177,15 @@ extension Swift2JavaTranslator { guard let swiftNominalDecl = swiftType.asNominalTypeDeclaration else { return nil } + + // Whether to import this extension? guard let nominalNode = symbolTable.parsedModule.nominalTypeSyntaxNodes[swiftNominalDecl] else { return nil } guard nominalNode.shouldImport(log: log) else { return nil } + return importedNominalType(swiftNominalDecl) } @@ -191,24 +196,7 @@ extension Swift2JavaTranslator { return alreadyImported } - // Determine the nominal type kind. - let kind: NominalTypeKind - switch nominal.kind { - case .actor: kind = .actor - case .class: kind = .class - case .enum: kind = .enum - case .struct: kind = .struct - default: return nil - } - - let importedNominal = ImportedNominalType( - swiftNominal: nominal, - javaType: .class( - package: javaPackage, - name: nominal.qualifiedName - ), - kind: kind - ) + let importedNominal = ImportedNominalType(swiftNominal: nominal) importedTypes[fullName] = importedNominal return importedNominal diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 3df49782..e51a7115 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -32,6 +32,11 @@ final class Swift2JavaVisitor: SyntaxVisitor { /// Innermost type context. var currentType: ImportedNominalType? { typeContext.last?.type } + var currentSwiftType: SwiftType? { + guard let currentType else { return nil } + return .nominal(SwiftNominalType(nominalTypeDecl: currentType.swiftNominal)) + } + /// The current type name as a nested name like A.B.C. var currentTypeName: String? { self.currentType?.swiftNominal.qualifiedName } @@ -114,49 +119,33 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } - self.log.debug("Import function: \(node.kind) \(node.name)") - - let returnTy: TypeSyntax - if let returnClause = node.signature.returnClause { - returnTy = returnClause.type - } else { - returnTy = "Swift.Void" - } + self.log.debug("Import function: '\(node.qualifiedNameForDebug)'") - let params: [ImportedParam] - let javaResultType: TranslatedType + let translatedSignature: TranslatedFunctionSignature do { - params = try node.signature.parameterClause.parameters.map { param in - // TODO: more robust parameter handling - // TODO: More robust type handling - ImportedParam( - syntax: param, - type: try cCompatibleType(for: param.type) - ) - } - - javaResultType = try cCompatibleType(for: returnTy) + let swiftSignature = try SwiftFunctionSignature( + node, + enclosingType: self.currentSwiftType, + symbolTable: self.translator.symbolTable + ) + translatedSignature = try translator.translate(swiftSignature: swiftSignature, as: .function) } catch { - self.log.info("Unable to import function \(node.name) - \(error)") + self.log.debug("Failed to translate: '\(node.qualifiedNameForDebug)'; \(error)") return .skipChildren } - let fullName = "\(node.name.text)" - - let funcDecl = ImportedFunc( - module: self.translator.swiftModuleName, - decl: node.trimmed, - parent: currentTypeName.map { translator.importedTypes[$0] }??.translatedType, - identifier: fullName, - returnType: javaResultType, - parameters: params + let imported = ImportedFunc( + module: translator.swiftModuleName, + swiftDecl: node, + name: node.name.text, + translatedSignature: translatedSignature ) - if let currentTypeName { - log.debug("Record method in \(currentTypeName)") - translator.importedTypes[currentTypeName]?.methods.append(funcDecl) + log.debug("Record imported method \(node.qualifiedNameForDebug)") + if let currentType { + currentType.methods.append(imported) } else { - translator.importedGlobalFuncs.append(funcDecl) + translator.importedGlobalFuncs.append(imported) } return .skipChildren @@ -171,54 +160,58 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } - let fullName = "\(binding.pattern.trimmed)" - - // TODO: filter out kinds of variables we cannot import + let varName = "\(binding.pattern.trimmed)" self.log.debug("Import variable: \(node.kind) '\(node.qualifiedNameForDebug)'") - let returnTy: TypeSyntax - if let typeAnnotation = binding.typeAnnotation { - returnTy = typeAnnotation.type - } else { - returnTy = "Swift.Void" + 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, as: kind) + } catch { + self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)") + throw error + } + + let imported = ImportedFunc( + module: translator.swiftModuleName, + swiftDecl: node, + name: varName, + translatedSignature: translatedSignature + ) + + log.debug("Record imported variable accessor \(kind == .getter ? "getter" : "setter"):\(node.qualifiedNameForDebug)") + if let currentType { + currentType.variables.append(imported) + } else { + translator.importedGlobalVariables.append(imported) + } } - let javaResultType: TranslatedType do { - javaResultType = try cCompatibleType(for: returnTy) + let supportedAccessors = node.supportedAccessorKinds(binding: binding) + if supportedAccessors.contains(.get) { + try importAccessor(kind: .getter) + } + if supportedAccessors.contains(.set) { + try importAccessor(kind: .setter) + } } catch { - log.info("Unable to import variable '\(node.qualifiedNameForDebug)' - \(error)") + self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)") return .skipChildren } - var varDecl = ImportedVariable( - module: self.translator.swiftModuleName, - parentName: currentTypeName.map { translator.importedTypes[$0] }??.translatedType, - identifier: fullName, - returnType: javaResultType - ) - varDecl.syntax = node.trimmed - - // Retrieve the mangled name, if available. - if let mangledName = node.mangledNameFromComment { - varDecl.swiftMangledName = mangledName - } - - if let currentTypeName { - log.debug("Record variable in \(currentTypeName)") - translator.importedTypes[currentTypeName]!.variables.append(varDecl) - } else { - fatalError("Global variables are not supported yet: \(node.qualifiedNameForDebug)") - } - return .skipChildren } override func visit(_ node: InitializerDeclSyntax) -> SyntaxVisitorContinueKind { - guard let currentTypeName, - let currentType = translator.importedTypes[currentTypeName] - else { + guard let currentType else { fatalError("Initializer must be within a current type, was: \(node)") } guard node.shouldImport(log: log) else { @@ -226,37 +219,27 @@ final class Swift2JavaVisitor: SyntaxVisitor { } self.log.debug("Import initializer: \(node.kind) '\(node.qualifiedNameForDebug)'") - let params: [ImportedParam] + + let translatedSignature: TranslatedFunctionSignature do { - params = try node.signature.parameterClause.parameters.map { param in - // TODO: more robust parameter handling - // TODO: More robust type handling - return ImportedParam( - syntax: param, - type: try cCompatibleType(for: param.type) - ) - } + let swiftSignature = try SwiftFunctionSignature( + node, + enclosingType: self.currentSwiftType, + symbolTable: self.translator.symbolTable + ) + translatedSignature = try translator.translate(swiftSignature: swiftSignature, as: .initializer) } catch { - self.log.info("Unable to import initializer due to \(error)") + self.log.debug("Failed to translate: \(node.qualifiedNameForDebug); \(error)") return .skipChildren } - - let initIdentifier = - "init(\(String(params.flatMap { "\($0.effectiveName ?? "_"):" })))" - - var funcDecl = ImportedFunc( - module: self.translator.swiftModuleName, - decl: node.trimmed, - parent: currentType.translatedType, - identifier: initIdentifier, - returnType: currentType.translatedType, - parameters: params + let imported = ImportedFunc( + module: translator.swiftModuleName, + swiftDecl: node, + name: "init", + translatedSignature: translatedSignature ) - funcDecl.isInit = true - log.debug( - "Record initializer method in \(currentType.javaType.description): \(funcDecl.identifier)") - translator.importedTypes[currentTypeName]!.initializers.append(funcDecl) + currentType.initializers.append(imported) return .skipChildren } @@ -289,23 +272,3 @@ extension DeclSyntaxProtocol where Self: WithModifiersSyntax & WithAttributesSyn return true } } - -private let mangledNameCommentPrefix = "MANGLED NAME: " - -extension SyntaxProtocol { - /// Look in the comment text prior to the node to find a mangled name - /// identified by "// MANGLED NAME: ". - var mangledNameFromComment: String? { - for triviaPiece in leadingTrivia { - guard case .lineComment(let comment) = triviaPiece, - let matchRange = comment.range(of: mangledNameCommentPrefix) - else { - continue - } - - return String(comment[matchRange.upperBound...]) - } - - return nil - } -} diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift index f72f0c58..5676e3d5 100644 --- a/Sources/JExtractSwift/SwiftThunkTranslator.swift +++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift @@ -12,10 +12,101 @@ // //===----------------------------------------------------------------------===// -import Foundation -import SwiftBasicFormat -import SwiftParser import SwiftSyntax +import SwiftSyntaxBuilder + +extension Swift2JavaTranslator { + public func writeSwiftThunkSources(outputDirectory: String) throws { + var printer = CodePrinter() + + try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer) + } + + public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws { + let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava" + let moduleFilename = "\(moduleFilenameBase).swift" + do { + log.info("Printing contents: \(moduleFilename)") + + try printGlobalSwiftThunkSources(&printer) + + if let outputFile = try printer.writeContents( + outputDirectory: outputDirectory, + javaPackagePath: nil, + filename: moduleFilename) + { + print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)") + } + } catch { + log.warning("Failed to write to Swift thunks: \(moduleFilename)") + } + + // === All types + for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) { + let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava" + let filename = "\(fileNameBase).swift" + log.info("Printing contents: \(filename)") + + do { + try printSwiftThunkSources(&printer, ty: ty) + + if let outputFile = try printer.writeContents( + outputDirectory: outputDirectory, + javaPackagePath: nil, + filename: filename) + { + print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)") + } + } catch { + log.warning("Failed to write to Swift thunks: \(filename)") + } + } + } + + public func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { + let stt = SwiftThunkTranslator(self) + + printer.print( + """ + // Generated by swift-java + + import SwiftKitSwift + + """) + + for thunk in stt.renderGlobalThunks() { + printer.print(thunk) + printer.println() + } + } + + public func printSwiftThunkSources(_ printer: inout CodePrinter, decl: ImportedFunc) { + let stt = SwiftThunkTranslator(self) + + for thunk in stt.render(forFunc: decl) { + printer.print(thunk) + printer.println() + } + } + + package func printSwiftThunkSources(_ printer: inout CodePrinter, ty: ImportedNominalType) throws { + let stt = SwiftThunkTranslator(self) + + printer.print( + """ + // Generated by swift-java + + import SwiftKitSwift + + """ + ) + + for thunk in stt.renderThunks(forType: ty) { + printer.print("\(thunk)") + printer.print("") + } + } +} struct SwiftThunkTranslator { @@ -27,6 +118,13 @@ struct SwiftThunkTranslator { func renderGlobalThunks() -> [DeclSyntax] { var decls: [DeclSyntax] = [] + decls.reserveCapacity( + st.importedGlobalVariables.count + st.importedGlobalFuncs.count + ) + + for decl in st.importedGlobalVariables { + decls.append(contentsOf: render(forFunc: decl)) + } for decl in st.importedGlobalFuncs { decls.append(contentsOf: render(forFunc: decl)) @@ -38,27 +136,23 @@ struct SwiftThunkTranslator { /// Render all the thunks that make Swift methods accessible to Java. func renderThunks(forType nominal: ImportedNominalType) -> [DeclSyntax] { var decls: [DeclSyntax] = [] - decls.reserveCapacity(nominal.initializers.count + nominal.methods.count) + decls.reserveCapacity( + 1 + nominal.initializers.count + nominal.variables.count + nominal.methods.count + ) decls.append(renderSwiftTypeAccessor(nominal)) for decl in nominal.initializers { - decls.append(contentsOf: renderSwiftInitAccessor(decl)) + decls.append(contentsOf: render(forFunc: decl)) } - for decl in nominal.methods { + for decl in nominal.variables { decls.append(contentsOf: render(forFunc: decl)) } - // TODO: handle variables - // for v in nominal.variables { - // if let acc = v.accessorFunc(kind: .get) { - // decls.append(contentsOf: render(forFunc: acc)) - // } - // if let acc = v.accessorFunc(kind: .set) { - // decls.append(contentsOf: render(forFunc: acc)) - // } - // } + for decl in nominal.methods { + decls.append(contentsOf: render(forFunc: decl)) + } return decls } @@ -78,107 +172,14 @@ struct SwiftThunkTranslator { """ } - func renderSwiftInitAccessor(_ function: ImportedFunc) -> [DeclSyntax] { - guard let parent = function.parent else { - fatalError( - "Cannot render initializer accessor if init function has no parent! Was: \(function)") - } - - let thunkName = self.st.thunkNameRegistry.functionThunkName( - module: st.swiftModuleName, decl: function) - - let cDecl = - """ - @_cdecl("\(thunkName)") - """ - let typeName = "\(parent.swiftTypeName)" - - return [ - """ - \(raw: cDecl) - public func \(raw: thunkName)( - \(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil)), - resultBuffer: /* \(raw: typeName) */ UnsafeMutableRawPointer - ) { - var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil))) - resultBuffer.assumingMemoryBound(to: \(raw: typeName).self).initialize(to: _self) - } - """ - ] - } - func render(forFunc decl: ImportedFunc) -> [DeclSyntax] { - st.log.trace("Rendering thunks for: \(decl.baseIdentifier)") - let thunkName = st.thunkNameRegistry.functionThunkName(module: st.swiftModuleName, decl: decl) - - let returnArrowTy = - if decl.returnType.cCompatibleJavaMemoryLayout == .primitive(.void) { - "/* \(decl.returnType.swiftTypeName) */" - } else { - "-> \(decl.returnType.cCompatibleSwiftType) /* \(decl.returnType.swiftTypeName) */" - } - - // Do we need to pass a self parameter? - let paramPassingStyle: SelfParameterVariant? - let callBase: String - let callBaseDot: String - if let parent = decl.parent { - paramPassingStyle = .swiftThunkSelf - callBase = - "var self$ = _self.assumingMemoryBound(to: \(parent.originalSwiftType).self).pointee" - callBaseDot = "self$." - } else { - paramPassingStyle = nil - callBase = "" - callBaseDot = "" - } - - // FIXME: handle in thunk: errors - - let returnStatement: String - if decl.returnType.javaType.isString { - returnStatement = - """ - let adaptedReturnValue = fatalError("Not implemented: adapting return types in Swift thunks") - return adaptedReturnValue - """ - } else { - returnStatement = "return returnValue" - } - - let declParams = st.renderSwiftParamDecls( - decl, - paramPassingStyle: paramPassingStyle, - style: .cDeclThunk + st.log.trace("Rendering thunks for: \(decl.displayName)") + let thunkName = st.thunkNameRegistry.functionThunkName(decl: decl) + let thunkFunc = decl.loweredSignature.cdeclThunk( + cName: thunkName, + swiftAPIName: decl.name, + stdlibTypes: st.swiftStdlibTypes ) - return - [ - """ - @_cdecl("\(raw: thunkName)") - public func \(raw: thunkName)(\(raw: declParams)) \(raw: returnArrowTy) { - \(raw: adaptArgumentsInThunk(decl)) - \(raw: callBase) - let returnValue = \(raw: callBaseDot)\(raw: decl.baseIdentifier)(\(raw: st.renderForwardSwiftParams(decl, paramPassingStyle: paramPassingStyle))) - \(raw: returnStatement) - } - """ - ] - } - - func adaptArgumentsInThunk(_ decl: ImportedFunc) -> String { - var lines: [String] = [] - for p in decl.parameters { - if p.type.javaType.isString { - // FIXME: is there a way we can avoid the copying here? - let adaptedType = - """ - let \(p.effectiveValueName) = String(cString: \(p.effectiveValueName)) - """ - - lines += [adaptedType] - } - } - - return lines.joined(separator: "\n") + return [DeclSyntax(thunkFunc)] } } diff --git a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift index 4e17e32d..5b0d8961 100644 --- a/Sources/JExtractSwift/SwiftTypes/SwiftType.swift +++ b/Sources/JExtractSwift/SwiftTypes/SwiftType.swift @@ -290,3 +290,14 @@ extension SwiftType { return "\(type).self" } } + +enum TypeTranslationError: Error { + /// We haven't yet implemented support for this type. + case unimplementedType(TypeSyntax) + + /// Missing generic arguments. + case missingGenericArguments(TypeSyntax) + + /// Unknown nominal type. + case unknown(TypeSyntax) +} diff --git a/Sources/JExtractSwift/ThunkNameRegistry.swift b/Sources/JExtractSwift/ThunkNameRegistry.swift index 92f1397d..87bf1d70 100644 --- a/Sources/JExtractSwift/ThunkNameRegistry.swift +++ b/Sources/JExtractSwift/ThunkNameRegistry.swift @@ -12,8 +12,6 @@ // //===----------------------------------------------------------------------===// -import SwiftSyntax - /// Registry of names we've already emitted as @_cdecl and must be kept unique. /// In order to avoid duplicate symbols, the registry can append some unique identifier to duplicated names package struct ThunkNameRegistry { @@ -25,28 +23,30 @@ package struct ThunkNameRegistry { package init() {} package mutating func functionThunkName( - module: String, decl: ImportedFunc, - file: String = #fileID, line: UInt = #line) -> String { + decl: ImportedFunc, + file: String = #fileID, line: UInt = #line + ) -> String { if let existingName = self.registry[decl] { return existingName } - let params = decl.effectiveParameters(paramPassingStyle: .swiftThunkSelf) - var paramsPart = "" - if !params.isEmpty { - paramsPart = "_" + params.map { param in - param.firstName ?? "_" - }.joined(separator: "_") + let suffix: String + switch decl.kind { + case .getter: + suffix = "$get" + case .setter: + suffix = "$set" + default: + suffix = decl.swiftSignature.parameters + .map { "_" + ($0.argumentLabel ?? "_") } + .joined() } - - - let name = - if let parent = decl.parent { - "swiftjava_\(module)_\(parent.swiftTypeName)_\(decl.baseIdentifier)\(paramsPart)" - } else { - "swiftjava_\(module)_\(decl.baseIdentifier)\(paramsPart)" - } + let name = if let parent = decl.parentType { + "swiftjava_\(decl.module)_\(parent)_\(decl.name)\(suffix)" + } else { + "swiftjava_\(decl.module)_\(decl.name)\(suffix)" + } let emittedCount = self.duplicateNames[name, default: 0] defer { self.duplicateNames[name] = emittedCount + 1 } diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift deleted file mode 100644 index 19c38ce6..00000000 --- a/Sources/JExtractSwift/TranslatedType.swift +++ /dev/null @@ -1,332 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import JavaTypes -import SwiftSyntax - -extension Swift2JavaVisitor { - /// Produce the C-compatible type for the given type, or throw an error if - /// there is no such type. - func cCompatibleType(for type: TypeSyntax) throws -> TranslatedType { - switch type.as(TypeSyntaxEnum.self) { - case .arrayType, .attributedType, .classRestrictionType, .compositionType, - .dictionaryType, .implicitlyUnwrappedOptionalType, .metatypeType, - .missingType, .namedOpaqueReturnType, - .optionalType, .packElementType, .packExpansionType, .someOrAnyType, - .suppressedType, .tupleType: - throw TypeTranslationError.unimplementedType(type) - - case .functionType(let functionType): - // FIXME: Temporary hack to keep existing code paths working. - if functionType.trimmedDescription == "() -> ()" { - return TranslatedType( - cCompatibleConvention: .direct, - originalSwiftType: type, - originalSwiftTypeKind: .function, - cCompatibleSwiftType: "@convention(c) () -> Void", - cCompatibleJavaMemoryLayout: .cFunction, - javaType: .javaLangRunnable - ) - } - - throw TypeTranslationError.unimplementedType(type) - - case .memberType(let memberType): - // If the parent type isn't a known module, translate it. - // FIXME: Need a more reasonable notion of which names are module names - // for this to work. - let parentType: TranslatedType? - if memberType.baseType.trimmedDescription == "Swift" { - parentType = nil - } else { - parentType = try cCompatibleType(for: memberType.baseType) - } - - // Translate the generic arguments to the C-compatible types. - let genericArgs = try memberType.genericArgumentClause.map { genericArgumentClause in - try genericArgumentClause.arguments.map { argument in - try cCompatibleType(for: argument.argument) - } - } - - // Resolve the C-compatible type by name. - return try translateType( - for: type, - parent: parentType, - name: memberType.name.text, - kind: nil, - genericArguments: genericArgs - ) - - case .identifierType(let identifierType): - // Translate the generic arguments to the C-compatible types. - let genericArgs = try identifierType.genericArgumentClause.map { genericArgumentClause in - try genericArgumentClause.arguments.map { argument in - try cCompatibleType(for: argument.argument) - } - } - - // Resolve the C-compatible type by name. - return try translateType( - for: type, - parent: nil, - name: identifierType.name.text, - kind: nil, - genericArguments: genericArgs - ) - } - } - - /// Produce the C compatible type by performing name lookup on the Swift type. - func translateType( - for type: TypeSyntax, - parent: TranslatedType?, - name: String, - kind: NominalTypeKind?, - genericArguments: [TranslatedType]? - ) throws -> TranslatedType { - // Look for a primitive type with this name. - if parent == nil, let primitiveType = JavaType(swiftTypeName: name) { - return TranslatedType( - cCompatibleConvention: .direct, - originalSwiftType: "\(raw: name)", - originalSwiftTypeKind: .primitive, - cCompatibleSwiftType: "Swift.\(raw: name)", - cCompatibleJavaMemoryLayout: .primitive(primitiveType), - javaType: primitiveType - ) - } - - // If this is the Swift "Int" type, it's primitive in Java but might - // map to either "int" or "long" depending whether the platform is - // 32-bit or 64-bit. - if parent == nil, name == "Int" { - return TranslatedType( - cCompatibleConvention: .direct, - originalSwiftType: "\(raw: name)", - originalSwiftTypeKind: .primitive, - cCompatibleSwiftType: "Swift.\(raw: name)", - cCompatibleJavaMemoryLayout: .int, - javaType: translator.javaPrimitiveForSwiftInt - ) - } - - // We special handle String types - if parent == nil, name == "String" { - return TranslatedType( - cCompatibleConvention: .direct, - originalSwiftType: "\(raw: name)", - originalSwiftTypeKind: kind, - cCompatibleSwiftType: "Swift.\(raw: name)", - cCompatibleJavaMemoryLayout: .heapObject, // FIXME: or specialize string? - javaType: .javaLangString - ) - } - - // Identify the various pointer types from the standard library. - if let (requiresArgument, _, hasCount) = name.isNameOfSwiftPointerType, !hasCount { - // Dig out the pointee type if needed. - if requiresArgument { - guard let genericArguments else { - throw TypeTranslationError.missingGenericArguments(type) - } - - guard genericArguments.count == 1 else { - throw TypeTranslationError.missingGenericArguments(type) - } - } else if let genericArguments { - throw TypeTranslationError.unexpectedGenericArguments(type, genericArguments) - } - - return TranslatedType( - cCompatibleConvention: .direct, - originalSwiftType: type, - cCompatibleSwiftType: "UnsafeMutableRawPointer", - cCompatibleJavaMemoryLayout: .heapObject, - javaType: .javaForeignMemorySegment - ) - } - - // FIXME: Generic types aren't mapped into Java. - if let genericArguments { - throw TypeTranslationError.unexpectedGenericArguments(type, genericArguments) - } - - // Look up the imported types by name to resolve it to a nominal type. - guard let importedNominal = translator.importedNominalType(type) else { - throw TypeTranslationError.unknown(type) - } - - return importedNominal.translatedType - } -} - -extension String { - /// Determine whether this string names one of the Swift pointer types. - /// - /// - Returns: a tuple describing three pieces of information: - /// 1. Whether the pointer type requires a generic argument for the - /// pointee. - /// 2. Whether the memory referenced by the pointer is mutable. - /// 3. Whether the pointer type has a `count` property describing how - /// many elements it points to. - var isNameOfSwiftPointerType: (requiresArgument: Bool, mutable: Bool, hasCount: Bool)? { - switch self { - case "COpaquePointer", "UnsafeRawPointer": - return (requiresArgument: false, mutable: true, hasCount: false) - - case "UnsafeMutableRawPointer": - return (requiresArgument: false, mutable: true, hasCount: false) - - case "UnsafePointer": - return (requiresArgument: true, mutable: false, hasCount: false) - - case "UnsafeMutablePointer": - return (requiresArgument: true, mutable: true, hasCount: false) - - case "UnsafeBufferPointer": - return (requiresArgument: true, mutable: false, hasCount: true) - - case "UnsafeMutableBufferPointer": - return (requiresArgument: true, mutable: false, hasCount: true) - - case "UnsafeRawBufferPointer": - return (requiresArgument: false, mutable: false, hasCount: true) - - case "UnsafeMutableRawBufferPointer": - return (requiresArgument: false, mutable: true, hasCount: true) - - default: - return nil - } - } -} - -enum ParameterConvention { - case direct - case indirect -} - -public struct TranslatedType { - /// How a parameter of this type will be passed through C functions. - var cCompatibleConvention: ParameterConvention - - /// The original Swift type, as written in the source. - var originalSwiftType: TypeSyntax - - /// - var originalSwiftTypeKind: NominalTypeKind? - - /// The C-compatible Swift type that should be used in any C -> Swift thunks - /// emitted in Swift. - var cCompatibleSwiftType: TypeSyntax - - /// The Java MemoryLayout constant that is used to describe the layout of - /// the type in memory. - var cCompatibleJavaMemoryLayout: CCompatibleJavaMemoryLayout - - /// The Java type that is used to present these values in Java. - var javaType: JavaType - - /// Produce a Swift type name to reference this type. - var swiftTypeName: String { - originalSwiftType.trimmedDescription - } - - /// Produce the "unqualified" Java type name. - var unqualifiedJavaTypeName: String { - switch javaType { - case .class(package: _, let name): name - default: javaType.description - } - } - - var isReferenceType: Bool { - originalSwiftTypeKind == .class || originalSwiftTypeKind == .actor - } - - var isValueType: Bool { - originalSwiftTypeKind == .struct || originalSwiftTypeKind == .enum - } -} - -extension TranslatedType { - public static var void: Self { - TranslatedType( - cCompatibleConvention: .direct, - originalSwiftType: "Void", - originalSwiftTypeKind: .void, - cCompatibleSwiftType: "Swift.Void", - cCompatibleJavaMemoryLayout: .primitive(.void), - javaType: JavaType.void) - } -} - -/// Describes the C-compatible layout as it should be referenced from Java. -enum CCompatibleJavaMemoryLayout: Hashable { - /// A primitive Java type that has a direct counterpart in C. - case primitive(JavaType) - - /// The Swift "Int" type, which may be either a Java int (32-bit platforms) or - /// Java long (64-bit platforms). - case int - - /// A Swift heap object, which is treated as a pointer for interoperability - /// purposes but must be retained/released to keep it alive. - case heapObject - - /// A C function pointer. In Swift, this will be a @convention(c) function. - /// In Java, a downcall handle to a function. - case cFunction -} - -enum SwiftTypeKind { - case `class` - case `actor` - case `enum` - case `struct` - case primitive - case `void` -} - -extension TranslatedType { - /// Determine the foreign value layout to use for the translated type with - /// the Java Foreign Function and Memory API. - var foreignValueLayout: ForeignValueLayout { - switch cCompatibleJavaMemoryLayout { - case .primitive(let javaType): - return ForeignValueLayout(javaType: javaType)! - - case .int: - return .SwiftInt - - case .heapObject, .cFunction: - return .SwiftPointer - } - } -} - -enum TypeTranslationError: Error { - /// We haven't yet implemented support for this type. - case unimplementedType(TypeSyntax) - - /// Unexpected generic arguments. - case unexpectedGenericArguments(TypeSyntax, [TranslatedType]) - - /// Missing generic arguments. - case missingGenericArguments(TypeSyntax) - - /// Unknown nominal type. - case unknown(TypeSyntax) -} diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift index 768f1480..94cd8a40 100644 --- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift +++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift @@ -54,7 +54,7 @@ struct ClassPrintingTests { return TYPE_METADATA; } - private static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment()); + public static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment()); public final GroupLayout $layout() { return $LAYOUT; } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 739cbb79..4bcb4e05 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -29,7 +29,6 @@ final class FuncCallbackImportTests { import _StringProcessing import _SwiftConcurrencyShims - // MANGLED NAME: $mockName public func callMe(callback: () -> ()) """ @@ -43,10 +42,10 @@ final class FuncCallbackImportTests { try st.analyze(file: "Fake.swift", text: Self.class_interfaceFile) - let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == "callMe" }! + let funcDecl = st.importedGlobalFuncs.first { $0.name == "callMe" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil) + st.printFuncDowncallMethod(&printer, funcDecl) } assertOutput( @@ -60,23 +59,16 @@ final class FuncCallbackImportTests { * } */ public static void callMe(java.lang.Runnable callback) { - var mh$ = callMe.HANDLE; - try (var arena = Arena.ofConfined()) { - FunctionDescriptor callMe_callback_desc$ = FunctionDescriptor.ofVoid(); - MethodHandle callMe_callback_handle$ = MethodHandles.lookup() - .findVirtual(Runnable.class, "run", - callMe_callback_desc$.toMethodType()); - callMe_callback_handle$ = callMe_callback_handle$.bindTo(callback); - Linker linker = Linker.nativeLinker(); - MemorySegment callback$ = linker.upcallStub(callMe_callback_handle$, callMe_callback_desc$, arena); - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(callback$); - } - - mh$.invokeExact(callback$); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); + var mh$ = swiftjava___FakeModule_callMe_callback.HANDLE; + try(var arena$ = Arena.ofConfined()) { + var callback$ = SwiftKit.toUpcallStub(callback, arena$); + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(callback$); } + mh$.invokeExact(callback$); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } } """ ) diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 5c190388..dea63d4e 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -29,26 +29,17 @@ final class FunctionDescriptorTests { import _StringProcessing import _SwiftConcurrencyShims - // MANGLED NAME: $s14MySwiftLibrary10helloWorldyyF public func helloWorld() - // MANGLED NAME: $s14MySwiftLibrary13globalTakeInt1iySi_tF public func globalTakeInt(i: Swift.Int) - // MANGLED NAME: $s14MySwiftLibrary23globalTakeLongIntString1l3i321sys5Int64V_s5Int32VSStF public func globalTakeLongInt(l: Int64, i32: Int32) - // MANGLED NAME: $s14MySwiftLibrary7echoInt1iS2i_tFs public func echoInt(i: Int) -> Int - // MANGLED NAME: $s14MySwiftLibrary0aB5ClassCMa public class MySwiftClass { - // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC3len3capACSi_SitcfC public init(len: Swift.Int, cap: Swift.Int) @objc deinit - // #MySwiftClass.counter!getter: (MySwiftClass) -> () -> Int32 : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvg\t// MySwiftClass.counter.getter - // #MySwiftClass.counter!setter: (MySwiftClass) -> (Int32) -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvs\t// MySwiftClass.counter.setter - // #MySwiftClass.counter!modify: (MySwiftClass) -> () -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32VvM\t// MySwiftClass.counter.modify public var counter: Int32 } """ @@ -61,7 +52,7 @@ final class FunctionDescriptorTests { expected: """ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /*i*/SwiftValueLayout.SWIFT_INT + /* i: */SwiftValueLayout.SWIFT_INT ); """ ) @@ -76,8 +67,8 @@ final class FunctionDescriptorTests { expected: """ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /*l*/SwiftValueLayout.SWIFT_INT64, - /*i32*/SwiftValueLayout.SWIFT_INT32 + /* l: */SwiftValueLayout.SWIFT_INT64, + /* i32: */SwiftValueLayout.SWIFT_INT32 ); """ ) @@ -93,7 +84,7 @@ final class FunctionDescriptorTests { """ public static final FunctionDescriptor DESC = FunctionDescriptor.of( /* -> */SwiftValueLayout.SWIFT_INT, - /*i*/SwiftValueLayout.SWIFT_INT + /* i: */SwiftValueLayout.SWIFT_INT ); """ ) @@ -102,14 +93,14 @@ final class FunctionDescriptorTests { @Test func FunctionDescriptor_class_counter_get() throws { - try variableAccessorDescriptorTest("counter", .get) { output in + try variableAccessorDescriptorTest("counter", .getter) { output in assertOutput( output, expected: """ - public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( + public static final FunctionDescriptor DESC = FunctionDescriptor.of( /* -> */SwiftValueLayout.SWIFT_INT32, - /*self$*/SwiftValueLayout.SWIFT_POINTER + /* self: */SwiftValueLayout.SWIFT_POINTER ); """ ) @@ -117,14 +108,14 @@ final class FunctionDescriptorTests { } @Test func FunctionDescriptor_class_counter_set() throws { - try variableAccessorDescriptorTest("counter", .set) { output in + try variableAccessorDescriptorTest("counter", .setter) { output in assertOutput( output, expected: """ - public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( - /*newValue*/SwiftValueLayout.SWIFT_INT32, - /*self$*/SwiftValueLayout.SWIFT_POINTER + public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* newValue: */SwiftValueLayout.SWIFT_INT32, + /* self: */SwiftValueLayout.SWIFT_POINTER ); """ ) @@ -151,11 +142,13 @@ extension FunctionDescriptorTests { try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) let funcDecl = st.importedGlobalFuncs.first { - $0.baseIdentifier == methodIdentifier + $0.name == methodIdentifier }! + let thunkName = st.thunkNameRegistry.functionThunkName(decl: funcDecl) + let cFunc = funcDecl.cFunctionDecl(cName: thunkName) let output = CodePrinter.toString { printer in - st.printFunctionDescriptorValue(&printer, funcDecl) + st.printFunctionDescriptorValue(&printer, cFunc) } try body(output) @@ -163,7 +156,7 @@ extension FunctionDescriptorTests { func variableAccessorDescriptorTest( _ identifier: String, - _ accessorKind: VariableAccessorKind, + _ accessorKind: SwiftAPIKind, javaPackage: String = "com.example.swift", swiftModuleName: String = "SwiftModule", logLevel: Logger.Level = .trace, @@ -177,22 +170,22 @@ extension FunctionDescriptorTests { try st.analyze(file: "/fake/Sample.swiftinterface", text: interfaceFile) - let varDecl: ImportedVariable? = + let accessorDecl: ImportedFunc? = st.importedTypes.values.compactMap { $0.variables.first { - $0.identifier == identifier + $0.name == identifier && $0.kind == accessorKind } }.first - guard let varDecl else { + guard let accessorDecl else { fatalError("Cannot find descriptor of: \(identifier)") } + let thunkName = st.thunkNameRegistry.functionThunkName(decl: accessorDecl) + let cFunc = accessorDecl.cFunctionDecl(cName: thunkName) let getOutput = CodePrinter.toString { printer in - st.printFunctionDescriptorValue( - &printer, varDecl.accessorFunc(kind: accessorKind)!, accessorKind: accessorKind) + st.printFunctionDescriptorValue(&printer, cFunc) } try body(getOutput) } - } diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index b7d00e89..3cb69808 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -28,25 +28,19 @@ final class MethodImportTests { import _StringProcessing import _SwiftConcurrencyShims - // MANGLED NAME: $s14MySwiftLibrary10helloWorldyyF public func helloWorld() - // MANGLED NAME: $s14MySwiftLibrary13globalTakeInt1iySi_tF public func globalTakeInt(i: Int) - // MANGLED NAME: $s14MySwiftLibrary23globalTakeLongIntString1l3i321sys5Int64V_s5Int32VSStF public func globalTakeIntLongString(i32: Int32, l: Int64, s: String) extension MySwiftClass { public func helloMemberInExtension() } - // MANGLED NAME: $s14MySwiftLibrary0aB5ClassCMa public class MySwiftClass { - // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC3len3capACSi_SitcfC public init(len: Swift.Int, cap: Swift.Int) - // MANGLED NAME: $s14MySwiftLibrary0aB5ClassC19helloMemberFunctionyyF public func helloMemberFunction() public func makeInt() -> Int @@ -69,10 +63,10 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) - let funcDecl = st.importedGlobalFuncs.first { $0.baseIdentifier == "helloWorld" }! + let funcDecl = st.importedGlobalFuncs.first { $0.name == "helloWorld" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil) + st.printFuncDowncallMethod(&printer, funcDecl) } assertOutput( @@ -86,7 +80,7 @@ final class MethodImportTests { * } */ public static void helloWorld() { - var mh$ = helloWorld.HANDLE; + var mh$ = swiftjava___FakeModule_helloWorld.HANDLE; try { if (SwiftKit.TRACE_DOWNCALLS) { SwiftKit.traceDowncall(); @@ -112,11 +106,11 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { - $0.baseIdentifier == "globalTakeInt" + $0.name == "globalTakeInt" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: nil) + st.printFuncDowncallMethod(&printer, funcDecl) } assertOutput( @@ -130,12 +124,11 @@ final class MethodImportTests { * } */ public static void globalTakeInt(long i) { - var mh$ = globalTakeInt.HANDLE; + var mh$ = swiftjava___FakeModule_globalTakeInt_i.HANDLE; try { if (SwiftKit.TRACE_DOWNCALLS) { SwiftKit.traceDowncall(i); } - mh$.invokeExact(i); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); @@ -156,11 +149,11 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl = st.importedGlobalFuncs.first { - $0.baseIdentifier == "globalTakeIntLongString" + $0.name == "globalTakeIntLongString" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment) + st.printFuncDowncallMethod(&printer, funcDecl) } assertOutput( @@ -175,13 +168,12 @@ final class MethodImportTests { * } */ public static void globalTakeIntLongString(int i32, long l, java.lang.String s) { - var mh$ = globalTakeIntLongString.HANDLE; - try (var arena = Arena.ofConfined()) { - var s$ = arena.allocateFrom(s); + var mh$ = swiftjava___FakeModule_globalTakeIntLongString_i32_l_s.HANDLE; + try(var arena$ = Arena.ofConfined()) { + var s$ = SwiftKit.toCString(s, arena$); if (SwiftKit.TRACE_DOWNCALLS) { SwiftKit.traceDowncall(i32, l, s$); } - mh$.invokeExact(i32, l, s$); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); @@ -192,7 +184,7 @@ final class MethodImportTests { } @Test - func method_class_helloMemberFunction_self_memorySegment() throws { + func method_class_helloMemberFunction() throws { let st = Swift2JavaTranslator( javaPackage: "com.example.swift", swiftModuleName: "__FakeModule" @@ -202,11 +194,11 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { - $0.baseIdentifier == "helloMemberFunction" + $0.name == "helloMemberFunction" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment) + st.printFuncDowncallMethod(&printer, funcDecl) } assertOutput( @@ -219,56 +211,14 @@ final class MethodImportTests { * public func helloMemberFunction() * } */ - public static void helloMemberFunction(java.lang.foreign.MemorySegment self$) { - var mh$ = helloMemberFunction.HANDLE; - try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(self$); - } - mh$.invokeExact(self$); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } - } - """ - ) - } - - @Test - func method_class_helloMemberFunction_self_wrapper() throws { - let st = Swift2JavaTranslator( - javaPackage: "com.example.swift", - swiftModuleName: "__FakeModule" - ) - st.log.logLevel = .error - - try st.analyze(file: "Fake.swift", text: class_interfaceFile) - - let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { - $0.baseIdentifier == "helloMemberInExtension" - }! - - let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment) - } - - assertOutput( - output, - expected: - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func helloMemberInExtension() - * } - */ - public static void helloMemberInExtension(java.lang.foreign.MemorySegment self$) { - var mh$ = helloMemberInExtension.HANDLE; + public void helloMemberFunction() { + $ensureAlive() + var mh$ = swiftjava___FakeModule_MySwiftClass_helloMemberFunction.HANDLE; try { if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(self$); + SwiftKit.traceDowncall(this.$memorySegment()); } - mh$.invokeExact(self$); + mh$.invokeExact(this.$memorySegment()); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } @@ -278,7 +228,7 @@ final class MethodImportTests { } @Test - func test_method_class_helloMemberFunction_self_wrapper() throws { + func method_class_makeInt() throws { let st = Swift2JavaTranslator( javaPackage: "com.example.swift", swiftModuleName: "__FakeModule" @@ -288,11 +238,11 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { - $0.baseIdentifier == "helloMemberFunction" + $0.name == "makeInt" }! let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .memorySegment) + st.printFuncDowncallMethod(&printer, funcDecl) } assertOutput( @@ -302,16 +252,17 @@ final class MethodImportTests { /** * Downcall to Swift: * {@snippet lang=swift : - * public func helloMemberFunction() + * public func makeInt() -> Int * } */ - public static void helloMemberFunction(java.lang.foreign.MemorySegment self$) { - var mh$ = helloMemberFunction.HANDLE; + public long makeInt() { + $ensureAlive() + var mh$ = swiftjava___FakeModule_MySwiftClass_makeInt.HANDLE; try { if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(self$); + SwiftKit.traceDowncall(this.$memorySegment()); } - mh$.invokeExact(self$); + return (long) mh$.invokeExact(this.$memorySegment()); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } @@ -320,79 +271,6 @@ final class MethodImportTests { ) } - @Test - func method_class_helloMemberFunction_wrapper() throws { - let st = Swift2JavaTranslator( - javaPackage: "com.example.swift", - swiftModuleName: "__FakeModule" - ) - st.log.logLevel = .info - - try st.analyze(file: "Fake.swift", text: class_interfaceFile) - - let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { - $0.baseIdentifier == "helloMemberFunction" - }! - - let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .wrapper) - } - - assertOutput( - output, - expected: - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func helloMemberFunction() - * } - */ - public void helloMemberFunction() { - $ensureAlive(); - helloMemberFunction($memorySegment()); - } - """ - ) - } - - @Test - func method_class_makeInt_wrapper() throws { - let st = Swift2JavaTranslator( - javaPackage: "com.example.swift", - swiftModuleName: "__FakeModule" - ) - st.log.logLevel = .info - - try st.analyze(file: "Fake.swift", text: class_interfaceFile) - - let funcDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.methods.first { - $0.baseIdentifier == "makeInt" - }! - - let output = CodePrinter.toString { printer in - st.printFuncDowncallMethod(&printer, decl: funcDecl, paramPassingStyle: .wrapper) - } - - assertOutput( - output, - expected: - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * public func makeInt() -> Int - * } - */ - public long makeInt() { - $ensureAlive(); - - return (long) makeInt($memorySegment()); - } - """ - ) - } - @Test func class_constructor() throws { let st = Swift2JavaTranslator( @@ -404,11 +282,11 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftClass"]!.initializers.first { - $0.identifier == "init(len:cap:)" + $0.name == "init" }! let output = CodePrinter.toString { printer in - st.printNominalInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!) + st.printInitializerDowncallConstructor(&printer, initDecl) } assertOutput( @@ -417,29 +295,25 @@ final class MethodImportTests { """ /** * Create an instance of {@code MySwiftClass}. - * This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime. * * {@snippet lang=swift : * public init(len: Swift.Int, cap: Swift.Int) * } */ - public MySwiftClass(long len, long cap, SwiftArena arena) { + public MySwiftClass(long len, long cap, SwiftArena swiftArena$) { super(() -> { - var mh$ = init_len_cap.HANDLE; + var mh$ = swiftjava___FakeModule_MySwiftClass_init_len_cap.HANDLE; try { - MemorySegment _result = arena.allocate($LAYOUT); + MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(len, cap); + SwiftKit.traceDowncall(len, cap, _result); } - mh$.invokeExact( - len, cap, - /* indirect return buffer */_result - ); + mh$.invokeExact(len, cap, _result); return _result; } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } - }, arena); + }, swiftArena$); } """ ) @@ -456,11 +330,11 @@ final class MethodImportTests { try st.analyze(file: "Fake.swift", text: class_interfaceFile) let initDecl: ImportedFunc = st.importedTypes["MySwiftStruct"]!.initializers.first { - $0.identifier == "init(len:cap:)" + $0.name == "init" }! let output = CodePrinter.toString { printer in - st.printNominalInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!) + st.printInitializerDowncallConstructor(&printer, initDecl) } assertOutput( @@ -469,29 +343,25 @@ final class MethodImportTests { """ /** * Create an instance of {@code MySwiftStruct}. - * This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime. * * {@snippet lang=swift : * public init(len: Swift.Int, cap: Swift.Int) * } */ - public MySwiftStruct(long len, long cap, SwiftArena arena) { + public MySwiftStruct(long len, long cap, SwiftArena swiftArena$) { super(() -> { - var mh$ = init_len_cap.HANDLE; + var mh$ = swiftjava___FakeModule_MySwiftStruct_init_len_cap.HANDLE; try { - MemorySegment _result = arena.allocate($LAYOUT); + MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT); if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(len, cap); + SwiftKit.traceDowncall(len, cap, _result); } - mh$.invokeExact( - len, cap, - /* indirect return buffer */_result - ); + mh$.invokeExact(len, cap, _result); return _result; } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } - }, arena); + }, swiftArena$); } """ ) diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift index 7de47816..d57e449e 100644 --- a/Tests/JExtractSwiftTests/MethodThunkTests.swift +++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift @@ -20,8 +20,14 @@ final class MethodThunkTests { """ import Swift + public var globalVar: MyClass = MyClass() public func globalFunc(a: Int32, b: Int64) {} public func globalFunc(a: Double, b: Int64) {} + + public class MyClass { + public var property: Int + public init(arg: Int32) {} + } """ @Test("Thunk overloads: globalFunc(a: Int32, b: Int64) & globalFunc(i32: Int32, l: Int64)") @@ -37,18 +43,52 @@ final class MethodThunkTests { detectChunkByInitialLines: 1, expectedChunks: [ + """ + @_cdecl("swiftjava_FakeModule_globalVar$get") + public func swiftjava_FakeModule_globalVar$get(_ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: MyClass.self).initialize(to: globalVar) + } + """, + """ + @_cdecl("swiftjava_FakeModule_globalVar$set") + public func swiftjava_FakeModule_globalVar$set(_ newValue: UnsafeRawPointer) { + globalVar = newValue.assumingMemoryBound(to: MyClass.self).pointee + } + """, """ @_cdecl("swiftjava_FakeModule_globalFunc_a_b") - public func swiftjava_FakeModule_globalFunc_a_b(a: Int32, b: Int64) /* Void */ { - let returnValue = globalFunc(a: a, b: b) - return returnValue + public func swiftjava_FakeModule_globalFunc_a_b(_ a: Int32, _ b: Int64) { + globalFunc(a: a, b: b) } """, """ @_cdecl("swiftjava_FakeModule_globalFunc_a_b$1") - public func swiftjava_FakeModule_globalFunc_a_b$1(a: Double, b: Int64) /* Void */ { - let returnValue = globalFunc(a: a, b: b) - return returnValue + public func swiftjava_FakeModule_globalFunc_a_b$1(_ a: Double, _ b: Int64) { + globalFunc(a: a, b: b) + } + """, + """ + @_cdecl("swiftjava_getType_FakeModule_MyClass") + public func swiftjava_getType_FakeModule_MyClass() -> UnsafeMutableRawPointer /* Any.Type */ { + return unsafeBitCast(MyClass.self, to: UnsafeMutableRawPointer.self) + } + """, + """ + @_cdecl("swiftjava_FakeModule_MyClass_init_arg") + public func swiftjava_FakeModule_MyClass_init_arg(_ arg: Int32, _ _result: UnsafeMutableRawPointer) { + _result.assumingMemoryBound(to: MyClass.self).initialize(to: MyClass(arg: arg)) + } + """, + """ + @_cdecl("swiftjava_FakeModule_MyClass_property$get") + public func swiftjava_FakeModule_MyClass_property$get(_ self: UnsafeRawPointer) -> Int { + return self.assumingMemoryBound(to: MyClass.self).pointee.property + } + """, + """ + @_cdecl("swiftjava_FakeModule_MyClass_property$set") + public func swiftjava_FakeModule_MyClass_property$set(_ newValue: Int, _ self: UnsafeRawPointer) { + self.assumingMemoryBound(to: MyClass.self).pointee.property = newValue } """ ] diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index cc488dfc..21f0584c 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -36,10 +36,16 @@ final class StringPassingTests { detectChunkByInitialLines: 1, expectedChunks: [ """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func writeString(string: String) -> Int + * } + */ public static long writeString(java.lang.String string) { - var mh$ = writeString.HANDLE; - try (var arena = Arena.ofConfined()) { - var string$ = arena.allocateFrom(string); + var mh$ = swiftjava___FakeModule_writeString_string.HANDLE; + try(var arena$ = Arena.ofConfined()) { + var string$ = SwiftKit.toCString(string, arena$); if (SwiftKit.TRACE_DOWNCALLS) { SwiftKit.traceDowncall(string$); } diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index 3665d277..e407b234 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -46,108 +46,14 @@ final class VariableImportTests { detectChunkByInitialLines: 7, expectedChunks: [ """ - private static class counterInt { - public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( - /* -> */SwiftValueLayout.SWIFT_INT, - /*self$*/SwiftValueLayout.SWIFT_POINTER + private static class swiftjava_FakeModule_MySwiftClass_counterInt$get { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */SwiftValueLayout.SWIFT_INT, + /* self: */SwiftValueLayout.SWIFT_POINTER ); - public static final MemorySegment ADDR_GET = - FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt"); - - public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET); - public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( - /*newValue*/SwiftValueLayout.SWIFT_INT, - /*self$*/SwiftValueLayout.SWIFT_POINTER - ); - public static final MemorySegment ADDR_SET = - FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt"); - - public static final MethodHandle HANDLE_SET = Linker.nativeLinker().downcallHandle(ADDR_SET, DESC_SET); - } - """, - """ - /** - * Function descriptor for: - * {@snippet lang=swift : - * public var counterInt: Int - * } - */ - public static FunctionDescriptor counterInt$get$descriptor() { - return counterInt.DESC_GET; - } - """, - """ - /** - * Downcall method handle for: - * {@snippet lang=swift : - * public var counterInt: Int - * } - */ - public static MethodHandle counterInt$get$handle() { - return counterInt.HANDLE_GET; - } - """, - """ - /** - * Address for: - * {@snippet lang=swift : - * public var counterInt: Int - * } - */ - public static MemorySegment counterInt$get$address() { - return counterInt.ADDR_GET; - } - """, - """ - /** - * Function descriptor for: - * {@snippet lang=swift : - * public var counterInt: Int - * } - */ - public static FunctionDescriptor counterInt$set$descriptor() { - return counterInt.DESC_SET; - } - """, - """ - /** - * Downcall method handle for: - * {@snippet lang=swift : - * public var counterInt: Int - * } - */ - public static MethodHandle counterInt$set$handle() { - return counterInt.HANDLE_SET; - } - """, - """ - /** - * Address for: - * {@snippet lang=swift : - * public var counterInt: Int - * } - */ - public static MemorySegment counterInt$set$address() { - return counterInt.ADDR_SET; - } - """, - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * public var counterInt: Int - * } - */ - public static long getCounterInt(java.lang.foreign.MemorySegment self$) { - var mh$ = counterInt.HANDLE_GET; - try { - if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(self$); - } - return (long) mh$.invokeExact(self$); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } + public static final MemorySegment ADDR = + FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$get"); + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); } """, """ @@ -159,29 +65,29 @@ final class VariableImportTests { */ public long getCounterInt() { $ensureAlive(); - return (long) getCounterInt($memorySegment()); - } - """, - """ - /** - * Downcall to Swift: - * {@snippet lang=swift : - * public var counterInt: Int - * } - */ - public static void setCounterInt(long newValue, java.lang.foreign.MemorySegment self$) { - var mh$ = counterInt.HANDLE_SET; + var mh$ = swiftjava_FakeModule_MySwiftClass_counterInt$get.HANDLE; try { if (SwiftKit.TRACE_DOWNCALLS) { - SwiftKit.traceDowncall(newValue, self$); + SwiftKit.traceDowncall(this.$memorySegment()); } - mh$.invokeExact(newValue, self$); + return (long) mh$.invokeExact(this.$memorySegment()); } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$); } } """, """ + private static class swiftjava_FakeModule_MySwiftClass_counterInt$set { + public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* newValue: */SwiftValueLayout.SWIFT_INT, + /* self: */SwiftValueLayout.SWIFT_POINTER + ); + public static final MemorySegment ADDR = + FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt$set"); + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + """, + """ /** * Downcall to Swift: * {@snippet lang=swift : @@ -190,7 +96,15 @@ final class VariableImportTests { */ public void setCounterInt(long newValue) { $ensureAlive(); - setCounterInt(newValue, $memorySegment()); + var mh$ = swiftjava_FakeModule_MySwiftClass_counterInt$set.HANDLE; + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(newValue, this.$memorySegment()); + } + mh$.invokeExact(newValue, this.$memorySegment()); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } } """, ]