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