From 3a396e20454b6c455fad7e7522faea5a5f1b682c Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski <konrad_malawski@apple.com> Date: Wed, 15 Jan 2025 13:01:03 +0900 Subject: [PATCH 1/2] Some steps towards importing structs as SwiftValue This was a pre-holidays WIP and it's gotten far enough to be useful as a small step --- .../Sources/MySwiftLibrary/MySwiftClass.swift | 58 ++++++++++++++++ .../MySwiftLibrary/MySwiftLibrary.swift | 56 +-------------- .../MySwiftLibrary/MySwiftStruct.swift | 44 ++++++++++++ .../MySwiftLibrary/_RuntimeWorkarounds.swift | 22 ++++++ .../com/example/swift/HelloJava2Swift.java | 3 + .../com/example/swift/MySwiftStructTest.java | 37 ++++++++++ .../org/swift/swiftkit/SwiftArenaTest.java | 4 ++ .../ExampleSwiftLibrary/MySwiftLibrary.swift | 11 +-- Sources/JExtractSwift/ImportedDecls.swift | 9 +++ .../Swift2JavaTranslator+Printing.swift | 13 +++- Sources/JExtractSwift/Swift2JavaVisitor.swift | 18 +++++ .../swiftkit/ConfinedSwiftMemorySession.java | 3 +- .../swiftkit/SwiftValueWitnessTable.java | 68 +++++++++++++++++++ 13 files changed, 277 insertions(+), 69 deletions(-) create mode 100644 Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift create mode 100644 Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift create mode 100644 Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift create mode 100644 Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift new file mode 100644 index 00000000..1ae962a3 --- /dev/null +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftClass.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public class MySwiftClass { + + public var len: Int + public var cap: Int + + public init(len: Int, cap: Int) { + self.len = len + self.cap = cap + + p("\(MySwiftClass.self).len = \(self.len)") + p("\(MySwiftClass.self).cap = \(self.cap)") + let addr = unsafeBitCast(self, to: UInt64.self) + p("initializer done, self = 0x\(String(addr, radix: 16, uppercase: true))") + } + + deinit { + let addr = unsafeBitCast(self, to: UInt64.self) + p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))") + } + + public var counter: Int32 = 0 + + public func voidMethod() { + p("") + } + + public func takeIntMethod(i: Int) { + p("i:\(i)") + } + + public func echoIntMethod(i: Int) -> Int { + p("i:\(i)") + return i + } + + public func makeIntMethod() -> Int { + p("make int -> 12") + return 12 + } + + public func makeRandomIntMethod() -> Int { + return Int.random(in: 1..<256) + } +} diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 84e4618f..7ee78f20 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -39,63 +39,9 @@ public func globalCallMeRunnable(run: () -> ()) { run() } -public class MySwiftClass { - - public var len: Int - public var cap: Int - - public init(len: Int, cap: Int) { - self.len = len - self.cap = cap - - p("\(MySwiftClass.self).len = \(self.len)") - p("\(MySwiftClass.self).cap = \(self.cap)") - let addr = unsafeBitCast(self, to: UInt64.self) - p("initializer done, self = 0x\(String(addr, radix: 16, uppercase: true))") - } - - deinit { - let addr = unsafeBitCast(self, to: UInt64.self) - p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))") - } - - public var counter: Int32 = 0 - - public func voidMethod() { - p("") - } - - public func takeIntMethod(i: Int) { - p("i:\(i)") - } - - public func echoIntMethod(i: Int) -> Int { - p("i:\(i)") - return i - } - - public func makeIntMethod() -> Int { - p("make int -> 12") - return 12 - } - - public func makeRandomIntMethod() -> Int { - return Int.random(in: 1..<256) - } -} - // ==== Internal helpers -private func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { +func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { print("[swift][\(file):\(line)](\(function)) \(msg)") fflush(stdout) } - -#if os(Linux) -// FIXME: why do we need this workaround? -@_silgen_name("_objc_autoreleaseReturnValue") -public func _objc_autoreleaseReturnValue(a: Any) {} - -@_silgen_name("objc_autoreleaseReturnValue") -public func objc_autoreleaseReturnValue(a: Any) {} -#endif diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift new file mode 100644 index 00000000..85bcb4c7 --- /dev/null +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +public struct MySwiftStruct { + + public var number: Int + + public init(number: Int) { + self.number = number + } + + public func voidMethod() { + p("") + } + + public func takeIntMethod(i: Int) { + p("i:\(i)") + } + + public func echoIntMethod(i: Int) -> Int { + p("i:\(i)") + return i + } + + public func makeIntMethod() -> Int { + p("make int -> 12") + return 12 + } + + public func makeRandomIntMethod() -> Int { + return Int.random(in: 1..<256) + } +} diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift new file mode 100644 index 00000000..f3056973 --- /dev/null +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/_RuntimeWorkarounds.swift @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if os(Linux) +// FIXME: why do we need this workaround? +@_silgen_name("_objc_autoreleaseReturnValue") +public func _objc_autoreleaseReturnValue(a: Any) {} + +@_silgen_name("objc_autoreleaseReturnValue") +public func objc_autoreleaseReturnValue(a: Any) {} +#endif 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 2a86e403..014f368d 100644 --- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -52,6 +52,9 @@ static void examples() { obj.voidMethod(); obj.takeIntMethod(42); + + MySwiftStruct swiftValue = new MySwiftStruct(12); + } System.out.println("DONE."); diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java new file mode 100644 index 00000000..27126587 --- /dev/null +++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.SwiftArena; +import org.swift.swiftkit.SwiftKit; + +import java.io.File; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MySwiftStructTest { + + @Test + void test_MySwiftClass_voidMethod() { + try (var arena = SwiftArena.ofConfined()) { + MySwiftStruct o = new MySwiftStruct(12); +// o.voidMethod(); + } + } + +} diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java index ad514e1b..1bf3c388 100644 --- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java +++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java @@ -70,6 +70,10 @@ public void arena_releaseClassOnClose_class_leaked() { // The message should point out which objects "leaked": assertTrue(ex.getMessage().contains(memorySegmentDescription)); } + } + + @Test + public void arena_initializeWithCopy_struct() { } } diff --git a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift index 8376d9b6..96afd331 100644 --- a/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift +++ b/Sources/ExampleSwiftLibrary/MySwiftLibrary.swift @@ -97,16 +97,7 @@ public func _getTypeByMangledNameInEnvironment( // ==== Internal helpers -private func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { +func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) { print("[swift][\(file):\(line)](\(function)) \(msg)") fflush(stdout) } - -#if os(Linux) -// FIXME: why do we need this workaround? -@_silgen_name("_objc_autoreleaseReturnValue") -public func _objc_autoreleaseReturnValue(a: Any) {} - -@_silgen_name("objc_autoreleaseReturnValue") -public func objc_autoreleaseReturnValue(a: Any) {} -#endif diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index cbff9969..a40eb3a2 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -49,6 +49,15 @@ public struct ImportedNominalType: ImportedDecl { javaType: javaType ) } + + public var isReferenceType: Bool { + switch self.kind { + case .class, .actor: + return true + case .enum, .struct: + return false + } + } /// The Java class name without the package. public var javaClassName: String { diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index 76721e46..6adb5d20 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -173,7 +173,7 @@ extension Swift2JavaTranslator { printPackage(&printer) printImports(&printer) - printClass(&printer, decl) { printer in + 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 @@ -257,10 +257,17 @@ extension Swift2JavaTranslator { printer.print("") } - public func printClass( + public func printNominal( _ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void ) { - printer.printTypeDecl("public final class \(decl.javaClassName) implements SwiftHeapObject") { + let parentProtocol: String + if decl.isReferenceType { + parentProtocol = "SwiftHeapObject" + } else { + parentProtocol = "SwiftValue" + } + + printer.printTypeDecl("public final class \(decl.javaClassName) implements \(parentProtocol)") { printer in // ==== Storage of the class printClassSelfProperty(&printer, decl) diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index 3feceec3..25a6b8da 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -40,6 +40,7 @@ final class Swift2JavaVisitor: SyntaxVisitor { } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { + log.debug("Visit \(node.kind): \(node)") guard let importedNominalType = translator.importedNominalType(node) else { return .skipChildren } @@ -54,7 +55,24 @@ final class Swift2JavaVisitor: SyntaxVisitor { currentTypeName = nil } } + + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + log.debug("Visit \(node.kind): \(node)") + guard let importedNominalType = translator.importedNominalType(node) else { + return .skipChildren + } + + currentTypeName = importedNominalType.swiftTypeName + return .visitChildren + } + override func visitPost(_ node: StructDeclSyntax) { + if currentTypeName != nil { + log.debug("Completed import: \(node.kind) \(node.name)") + currentTypeName = nil + } + } + override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { // Resolve the extended type of the extension as an imported nominal, and // recurse if we found it. diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java b/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java index e727f5db..36d7be77 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java @@ -36,7 +36,8 @@ public ConfinedSwiftMemorySession(Thread owner) { public void checkValid() throws RuntimeException { if (this.owner != null && this.owner != Thread.currentThread()) { - throw new WrongThreadException("ConfinedSwift arena is confined to %s but was closed from %s!".formatted(this.owner, Thread.currentThread())); + throw new WrongThreadException("ConfinedSwift arena is confined to %s but was closed from %s!" + .formatted(this.owner, Thread.currentThread())); } else if (this.state.get() < ACTIVE) { throw new RuntimeException("SwiftArena is already closed!"); } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java index 826a4fa2..d2f7d729 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java @@ -225,4 +225,72 @@ public static void destroy(SwiftAnyType type, MemorySegment object) { } } + /** + * {@snippet lang = C: + * /// T *(*initializeWithCopy)(T *dest, T *src, M *self); + * /// + * /// Given an invalid object of this type, initialize it as a copy of + * /// the source object. Returns the dest object. + * FUNCTION_VALUE_WITNESS(initializeWithCopy, + * InitializeWithCopy, + * MUTABLE_VALUE_TYPE, + * (MUTABLE_VALUE_TYPE, MUTABLE_VALUE_TYPE, TYPE_TYPE)) + *} + */ + private static class initializeWithCopy { + + static final long $offset = + $LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("initializeWithCopy")); + + static final FunctionDescriptor DESC = FunctionDescriptor.of( + /* -> */ ValueLayout.ADDRESS, // returns the destination object + ValueLayout.ADDRESS, // destination + ValueLayout.ADDRESS, // source + ValueLayout.ADDRESS // pointer to the witness table + ); + + /** + * Function pointer for the initializeWithCopy operation + */ + static MemorySegment addr(SwiftAnyType ty) { + // Get the value witness table of the type + final var vwt = SwiftValueWitnessTable.valueWitnessTable(ty.$memorySegment()); + + // Get the address of the function stored at the offset of the witness table + long funcAddress = getSwiftInt(vwt, initializeWithCopy.$offset); + return MemorySegment.ofAddress(funcAddress); + } + + static MethodHandle handle(SwiftAnyType ty) { + return Linker.nativeLinker().downcallHandle(addr(ty), DESC); + } + } + + + /** + * Given an invalid object of this type, initialize it as a copy of + * the source object. + * <p/> + * Returns the dest object. + */ + public static MemorySegment initializeWithCopy(SwiftAnyType type, MemorySegment dest, MemorySegment src) { + var fullTypeMetadata = fullTypeMetadata(type.$memorySegment()); + var wtable = valueWitnessTable(fullTypeMetadata); + + var mh = initializeWithCopy.handle(type); + + try (var arena = Arena.ofConfined()) { + // we need to make a pointer to the self pointer when calling witness table functions: + MemorySegment indirectDest = arena.allocate(SwiftValueLayout.SWIFT_POINTER); + MemorySegmentUtils.setSwiftPointerAddress(indirectDest, dest); + MemorySegment indirectSrc = arena.allocate(SwiftValueLayout.SWIFT_POINTER); + MemorySegmentUtils.setSwiftPointerAddress(indirectSrc, src); + + var returnedDest = (MemorySegment) mh.invokeExact(indirectDest, indirectSrc, wtable); + return returnedDest; + } catch (Throwable th) { + throw new AssertionError("Failed to initializeWithCopy '" + type + "' (" + dest + ", " + src + ")", th); + } + } + } From 650620e17b033f40f91e2cd1977d99a97a7afa58 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski <konrad.malawski@project13.pl> Date: Wed, 15 Jan 2025 13:11:29 +0900 Subject: [PATCH 2/2] Update MySwiftLibrary.swift --- .../Sources/MySwiftLibrary/MySwiftLibrary.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 285ad9fb..2bd1905c 100644 --- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -53,3 +53,12 @@ func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: Stri print("[swift][\(file):\(line)](\(function)) \(msg)") fflush(stdout) } + + #if os(Linux) + // FIXME: why do we need this workaround? + @_silgen_name("_objc_autoreleaseReturnValue") + public func _objc_autoreleaseReturnValue(a: Any) {} + + @_silgen_name("objc_autoreleaseReturnValue") + public func objc_autoreleaseReturnValue(a: Any) {} + #endif