Skip to content

Commit cc8c991

Browse files
committed
Prototyping Swift Array Accessor
1 parent 84587b4 commit cc8c991

File tree

11 files changed

+490
-29
lines changed

11 files changed

+490
-29
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import SwiftKitSwift
16+
17+
//@_cdecl("swiftjava_manual_getArrayInt")
18+
//public func swiftjava_manual_getArrayInt() -> UnsafeMutableRawPointer /* [Int] */ {
19+
// var array = getArrayInt()
20+
//}
21+
22+
@_cdecl("swiftjava_manual_getArrayMySwiftClass")
23+
public func swiftjava_manual_getArrayMySwiftClass() -> UnsafeMutableRawPointer /* [MySwiftClass] */ {
24+
p("[thunk] swiftjava_manual_getArrayMySwiftClass")
25+
var array: [MySwiftClass] = getArrayMySwiftClass()
26+
p("[thunk] swiftjava_manual_getArrayMySwiftClass -> \(array)")
27+
// TODO: we need to retain it I guess as we escape it into Java
28+
var ptr: UnsafeRawBufferPointer!
29+
array.withUnsafeBytes {
30+
ptr = $0
31+
}
32+
p("[thunk] swiftjava_manual_getArrayMySwiftClass -> \(ptr)")
33+
34+
// p("[thunk] swiftjava_manual_getArrayMySwiftClass -> extra retain \(ptr)")
35+
// _swiftjava_swift_retain(object: ptr)
36+
37+
return UnsafeMutableRawPointer(mutating:ptr!.baseAddress)!
38+
}
39+
40+
@_cdecl("swiftjava_SwiftKitSwift_Array_count") // FIXME: hardcoded for MySwiftClass
41+
public func swiftjava_SwiftKitSwift_Array____count(
42+
rawPointer: UnsafeMutableRawPointer, // Array<T>
43+
elementType: UnsafeMutableRawPointer // Metadata of T
44+
) -> Int {
45+
print("[swift][\(#fileID):\(#line)](\(#function) passed in rawPointer = \(rawPointer)")
46+
print("[swift][\(#fileID):\(#line)](\(#function) passed in metadata = \(elementType)")
47+
48+
let array = rawPointer.assumingMemoryBound(to: [MySwiftClass].self)
49+
.pointee
50+
51+
// let array =
52+
// unsafeBitCast(rawPointer, to: [MySwiftClass].self)
53+
54+
print("[swift][\(#fileID):\(#line)](\(#function) ARRAY count = \(array.count)")
55+
print("[swift][\(#fileID):\(#line)](\(#function) ARRAY[0] = \(unsafeBitCast(array[0], to: UInt64.self))")
56+
return array.count
57+
}
58+
59+
@_cdecl("swiftjava_SwiftKitSwift_Array_get") // FIXME: hardcoded for MySwiftClass
60+
public func swiftjava_SwiftKitSwift_Array____get(
61+
rawPointer: UnsafeMutableRawPointer, // Array<T>
62+
index: Int,
63+
elementType: UnsafeMutableRawPointer // Metadata of T
64+
) -> UnsafeMutableRawPointer {
65+
print("[swift][\(#fileID):\(#line)](\(#function) passed in rawPointer = \(rawPointer)")
66+
print("[swift][\(#fileID):\(#line)](\(#function) passed in index = \(index)")
67+
print("[swift][\(#fileID):\(#line)](\(#function) passed in metadata = \(elementType)")
68+
69+
let array: UnsafeMutableBufferPointer<MySwiftClass> = UnsafeMutableBufferPointer(
70+
start: rawPointer.assumingMemoryBound(to: MySwiftClass.self),
71+
count: 999 // FIXME: we need this to be passed in
72+
)
73+
74+
print("[swift][\(#fileID):\(#line)](\(#function) ARRAY[\(index)] = \(unsafeBitCast(array[index], to: UInt64.self))")
75+
let object = array[index]
76+
77+
let objectPointer = unsafeBitCast(object, to: UnsafeMutableRawPointer.self)
78+
return _swiftjava_swift_retain(object: objectPointer)
79+
}

Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ public func globalCallMeRunnable(run: () -> ()) {
4747
run()
4848
}
4949

50+
public func getArrayInt() -> [Int] {
51+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
52+
}
53+
54+
let DATA = [
55+
MySwiftClass(len: 1, cap: 11),
56+
MySwiftClass(len: 2, cap: 22),
57+
MySwiftClass(len: 3, cap: 33),
58+
]
59+
60+
public func getArrayMySwiftClass() -> [MySwiftClass] {
61+
DATA
62+
}
63+
5064
public class MySwiftClass {
5165

5266
public var len: Int
@@ -64,7 +78,7 @@ public class MySwiftClass {
6478

6579
deinit {
6680
let addr = unsafeBitCast(self, to: UInt64.self)
67-
p("Deinit, self = 0x\(String(addr, radix: 16, uppercase: true))")
81+
p("MySwiftClass.deinit, self = 0x\(String(addr, radix: 16, uppercase: true))")
6882
}
6983

7084
public var counter: Int32 = 0
@@ -77,6 +91,15 @@ public class MySwiftClass {
7791
p("i:\(i)")
7892
}
7993

94+
// TODO: workaround until we expose properties again
95+
public func getterForLen() -> Int {
96+
len
97+
}
98+
// TODO: workaround until we expose properties again
99+
public func getterForCap() -> Int {
100+
cap
101+
}
102+
80103
public func echoIntMethod(i: Int) -> Int {
81104
p("i:\(i)")
82105
return i
@@ -99,9 +122,9 @@ public class MySwiftClass {
99122

100123
// ==== Internal helpers
101124

102-
private func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {
103-
// print("[swift][\(file):\(line)](\(function)) \(msg)")
104-
// fflush(stdout)
125+
package func p(_ msg: String, file: String = #fileID, line: UInt = #line, function: String = #function) {
126+
print("[swift][\(file):\(line)](\(function)) \(msg)")
127+
fflush(stdout)
105128
}
106129

107130
#if os(Linux)

Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,19 @@
2121

2222
// Import javakit/swiftkit support libraries
2323
import org.swift.swiftkit.SwiftArena;
24+
import org.swift.swiftkit.SwiftArrayAccessor;
2425
import org.swift.swiftkit.SwiftKit;
2526
import org.swift.swiftkit.SwiftValueWitnessTable;
2627

28+
import java.lang.foreign.Arena;
29+
import java.lang.foreign.FunctionDescriptor;
30+
import java.lang.foreign.Linker;
31+
import java.lang.foreign.MemorySegment;
32+
import java.lang.invoke.MethodHandle;
2733
import java.util.Arrays;
34+
import java.util.Objects;
35+
36+
import static org.swift.swiftkit.SwiftValueLayout.SWIFT_POINTER;
2837

2938
public class HelloJava2Swift {
3039

@@ -38,26 +47,95 @@ public static void main(String[] args) {
3847
}
3948

4049
static void examples() {
41-
MySwiftLibrary.helloWorld();
50+
// MySwiftLibrary.helloWorld();
51+
//
52+
// MySwiftLibrary.globalTakeInt(1337);
53+
//
54+
// // Example of using an arena; MyClass.deinit is run at end of scope
55+
// try (var arena = SwiftArena.ofConfined()) {
56+
// MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
57+
//
58+
// // just checking retains/releases work
59+
// SwiftKit.retain(obj.$memorySegment());
60+
// SwiftKit.release(obj.$memorySegment());
61+
//
62+
// obj.voidMethod();
63+
// obj.takeIntMethod(42);
64+
// }
4265

43-
MySwiftLibrary.globalTakeInt(1337);
66+
SwiftKit.loadLibrary("swiftCore");
67+
SwiftKit.loadLibrary("SwiftKitSwift");
68+
SwiftKit.loadLibrary("MySwiftLibrary");
4469

45-
// Example of using an arena; MyClass.deinit is run at end of scope
46-
try (var arena = SwiftArena.ofConfined()) {
47-
MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
70+
// public func getArrayMySwiftClass() -> [MySwiftClass]
71+
SwiftArrayAccessor<MySwiftClass> arr = ManualImportedMethods.getArrayMySwiftClass();
4872

49-
// just checking retains/releases work
50-
SwiftKit.retain(obj.$memorySegment());
51-
SwiftKit.release(obj.$memorySegment());
73+
MySwiftClass first = arr.get(0, MySwiftClass::new);
74+
System.out.println("[java] first = " + first);
75+
76+
// FIXME: properties don't work yet, need the thunks!
77+
// System.out.println("[java] first.getLen() = " + first.getLen());
78+
// assert(first.getLen() == 1);
79+
// System.out.println("[java] first.getCap() = " + first.getCap());
80+
// assert(first.getCap() == 2);
81+
82+
System.out.println("[java] first.getterForLen() = " + first.getterForLen());
83+
System.out.println("[java] first.getForCap() = " + first.getterForCap());
84+
precondition(1, first.getterForLen());
85+
precondition(11, first.getterForCap());
86+
87+
MySwiftClass second = arr.get(1, MySwiftClass::new);
88+
System.out.println("[java] second = " + second);
89+
System.out.println("[java] second.getterForLen() = " + second.getterForLen());
90+
System.out.println("[java] second.getForCap() = " + second.getterForCap());
91+
precondition(2, second.getterForLen());
92+
precondition(22, second.getterForCap());
5293

53-
obj.voidMethod();
54-
obj.takeIntMethod(42);
55-
}
5694

5795
System.out.println("DONE.");
5896
}
5997

98+
private static void precondition(long expected, long got) {
99+
if (expected != got) {
100+
throw new AssertionError("Expected '" + expected + "', but got '" + got + "'!");
101+
}
102+
}
103+
60104
public static native long jniWriteString(String str);
61105
public static native long jniGetInt();
62106

63107
}
108+
109+
final class ManualImportedMethods {
110+
111+
private static class getArrayMySwiftClass {
112+
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
113+
/* -> */SWIFT_POINTER
114+
);
115+
public static final MemorySegment ADDR =
116+
SwiftKit.findOrThrow("swiftjava_manual_getArrayMySwiftClass");
117+
118+
public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC);
119+
}
120+
121+
122+
public static SwiftArrayAccessor<MySwiftClass> getArrayMySwiftClass() {
123+
MethodHandle mh = getArrayMySwiftClass.HANDLE;
124+
125+
Arena arena = Arena.ofAuto();
126+
try {
127+
if (SwiftKit.TRACE_DOWNCALLS) {
128+
SwiftKit.traceDowncall();
129+
}
130+
131+
MemorySegment arrayPointer = (MemorySegment) mh.invokeExact();
132+
return new SwiftArrayAccessor<>(
133+
arena,
134+
arrayPointer,
135+
/* element type = */MySwiftClass.TYPE_METADATA
136+
);
137+
} catch (Throwable e) {
138+
throw new RuntimeException("Failed to invoke Swift method", e);
139+
}
140+
}
141+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
package org.swift.swiftkit;
16+
17+
import com.example.swift.MySwiftClass;
18+
import com.example.swift.MySwiftLibrary;
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.lang.foreign.Arena;
22+
import java.lang.foreign.FunctionDescriptor;
23+
import java.lang.foreign.Linker;
24+
import java.lang.foreign.MemorySegment;
25+
import java.lang.invoke.MethodHandle;
26+
27+
import static org.junit.jupiter.api.Assertions.assertEquals;
28+
import static org.swift.swiftkit.SwiftValueLayout.SWIFT_POINTER;
29+
30+
public class SwiftArrayTest {
31+
32+
static {
33+
SwiftArrayAccessor.initializeLibs();
34+
var x = MySwiftLibrary.SYMBOL_LOOKUP;
35+
}
36+
37+
@Test
38+
public void array_of_MySwiftClass() {
39+
40+
try (var arena = SwiftArena.ofConfined()) {
41+
SwiftArrayAccessor<MySwiftClass> arr = ManualImportedMethods.getArrayMySwiftClass();
42+
43+
int size = arr.size();
44+
assertEquals(3, size);
45+
46+
// We can copy references into a Java array...
47+
MySwiftClass[] instances = new MySwiftClass[Math.toIntExact(size)];
48+
}
49+
}
50+
}
51+

Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ extension Swift2JavaTranslator {
303303

304304
printer.print(
305305
"""
306-
static MemorySegment findOrThrow(String symbol) {
306+
public static MemorySegment findOrThrow(String symbol) {
307307
return SYMBOL_LOOKUP.find(symbol)
308308
.orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: %s".formatted(symbol)));
309309
}
@@ -344,7 +344,7 @@ extension Swift2JavaTranslator {
344344
// https://bugs.openjdk.org/browse/JDK-8311090
345345
printer.print(
346346
"""
347-
static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup();
347+
public static final SymbolLookup SYMBOL_LOOKUP = getSymbolLookup();
348348
private static SymbolLookup getSymbolLookup() {
349349
// Ensure Swift and our Lib are loaded during static initialization of the class.
350350
SwiftKit.loadLibrary("swiftCore");
@@ -479,6 +479,17 @@ extension Swift2JavaTranslator {
479479
) {
480480
let descClassIdentifier = renderDescClassName(decl)
481481

482+
printer.print(
483+
"""
484+
/**
485+
* Wrap a memory segment which is pointing to an instance of {@code \(parentName.unqualifiedJavaTypeName)}.
486+
*/
487+
public \(parentName.unqualifiedJavaTypeName)(MemorySegment self) {
488+
this.selfMemorySegment = self;
489+
}
490+
"""
491+
)
492+
482493
printer.print(
483494
"""
484495
/**

Sources/JExtractSwift/Swift2JavaVisitor.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ final class Swift2JavaVisitor: SyntaxVisitor {
108108

109109
let fullName = "\(node.name.text)"
110110

111+
guard !fullName.hasPrefix("swiftjava_") else {
112+
self.log.debug("Skip swiftjava_ thunk method during importing: \(fullName)")
113+
return .skipChildren
114+
}
115+
111116
let funcDecl = ImportedFunc(
112117
module: self.translator.swiftModuleName,
113118
decl: node.trimmed,
@@ -170,7 +175,8 @@ final class Swift2JavaVisitor: SyntaxVisitor {
170175
log.debug("Record variable in \(currentTypeName)")
171176
translator.importedTypes[currentTypeName]!.variables.append(varDecl)
172177
} else {
173-
fatalError("Global variables are not supported yet: \(node.debugDescription)")
178+
log.warning("Global variables are not supported yet: \(node.debugDescription)")
179+
return .skipChildren
174180
}
175181

176182
return .skipChildren

0 commit comments

Comments
 (0)