Skip to content

Commit ac80400

Browse files
authored
[JExtract] Generate JNI code for memory management with SwiftKitCore (#302)
1 parent 17210b7 commit ac80400

File tree

6 files changed

+175
-40
lines changed

6 files changed

+175
-40
lines changed

Samples/JExtractJNISampleApp/src/main/java/com/example/swift/HelloJava2SwiftJNI.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
// Import javakit/swiftkit support libraries
2020

2121
import org.swift.swiftkit.core.SwiftLibraries;
22+
import org.swift.swiftkit.core.ConfinedSwiftMemorySession;
2223

2324
public class HelloJava2SwiftJNI {
2425

@@ -40,8 +41,10 @@ static void examples() {
4041

4142
MySwiftClass.method();
4243

43-
MySwiftClass myClass = MySwiftClass.init(10, 5);
44-
MySwiftClass myClass2 = MySwiftClass.init();
44+
try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) {
45+
MySwiftClass myClass = MySwiftClass.init(10, 5, arena);
46+
MySwiftClass myClass2 = MySwiftClass.init(arena);
47+
}
4548

4649
System.out.println("DONE.");
4750
}

Samples/JExtractJNISampleApp/src/test/java/com/example/swift/MySwiftClassTest.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,25 @@
1414

1515
package com.example.swift;
1616

17-
import com.example.swift.MySwiftLibrary;
18-
import org.junit.jupiter.api.BeforeAll;
19-
import org.junit.jupiter.api.Disabled;
2017
import org.junit.jupiter.api.Test;
18+
import org.swift.swiftkit.core.ConfinedSwiftMemorySession;
2119

2220
import static org.junit.jupiter.api.Assertions.*;
2321

2422
public class MySwiftClassTest {
2523
@Test
2624
void init_noParameters() {
27-
MySwiftClass c = MySwiftClass.init();
28-
assertNotNull(c);
25+
try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) {
26+
MySwiftClass c = MySwiftClass.init(arena);
27+
assertNotNull(c);
28+
}
2929
}
3030

3131
@Test
3232
void init_withParameters() {
33-
MySwiftClass c = MySwiftClass.init(1337, 42);
34-
assertNotNull(c);
33+
try (var arena = new ConfinedSwiftMemorySession(Thread.currentThread())) {
34+
MySwiftClass c = MySwiftClass.init(1337, 42, arena);
35+
assertNotNull(c);
36+
}
3537
}
36-
3738
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
16+
// MARK: Defaults
17+
18+
extension JNISwift2JavaGenerator {
19+
/// Default set Java imports for every generated file
20+
static let defaultJavaImports: Array<String> = [
21+
"org.swift.swiftkit.core.*",
22+
"org.swift.swiftkit.core.util.*",
23+
]
24+
}
25+
26+
// MARK: Printing
27+
1528
extension JNISwift2JavaGenerator {
1629
func writeExportedJavaSources() throws {
1730
var printer = CodePrinter()
@@ -72,6 +85,7 @@ extension JNISwift2JavaGenerator {
7285
private func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
7386
printHeader(&printer)
7487
printPackage(&printer)
88+
printImports(&printer)
7589

7690
printNominal(&printer, decl) { printer in
7791
printer.print(
@@ -87,14 +101,10 @@ extension JNISwift2JavaGenerator {
87101
"""
88102
)
89103

90-
printer.println()
91-
92104
printer.print(
93105
"""
94-
private long selfPointer;
95-
96-
private \(decl.swiftNominal.name)(long selfPointer) {
97-
this.selfPointer = selfPointer;
106+
public \(decl.swiftNominal.name)(long selfPointer, SwiftArena swiftArena) {
107+
super(selfPointer, swiftArena);
98108
}
99109
"""
100110
)
@@ -108,6 +118,8 @@ extension JNISwift2JavaGenerator {
108118
for method in decl.methods {
109119
printFunctionBinding(&printer, method)
110120
}
121+
122+
printDestroyFunction(&printer, decl)
111123
}
112124
}
113125

@@ -130,10 +142,17 @@ extension JNISwift2JavaGenerator {
130142
)
131143
}
132144

145+
private func printImports(_ printer: inout CodePrinter) {
146+
for i in JNISwift2JavaGenerator.defaultJavaImports {
147+
printer.print("import \(i);")
148+
}
149+
printer.print("")
150+
}
151+
133152
private func printNominal(
134153
_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void
135154
) {
136-
printer.printBraceBlock("public final class \(decl.swiftNominal.name)") { printer in
155+
printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends JNISwiftInstance") { printer in
137156
body(&printer)
138157
}
139158
}
@@ -160,7 +179,7 @@ extension JNISwift2JavaGenerator {
160179
printer.print(
161180
"""
162181
long selfPointer = \(type.qualifiedName).allocatingInit(\(initArguments.joined(separator: ", ")));
163-
return new \(type.qualifiedName)(selfPointer);
182+
return new \(type.qualifiedName)(selfPointer, swiftArena$);
164183
"""
165184
)
166185
}
@@ -182,14 +201,39 @@ extension JNISwift2JavaGenerator {
182201
)
183202
}
184203

204+
/// Prints the destroy function for a `JNISwiftInstance`
205+
private func printDestroyFunction(_ printer: inout CodePrinter, _ type: ImportedNominalType) {
206+
printer.print("private static native void $destroy(long selfPointer);")
207+
208+
printer.print("@Override")
209+
printer.printBraceBlock("protected Runnable $createDestroyFunction()") { printer in
210+
printer.print(
211+
"""
212+
long $selfPointer = this.pointer();
213+
return new Runnable() {
214+
@Override
215+
public void run() {
216+
\(type.swiftNominal.name).$destroy($selfPointer);
217+
}
218+
};
219+
"""
220+
)
221+
}
222+
}
223+
185224
/// Renders a Java function signature
186225
///
187226
/// `func method(x: Int, y: Int) -> Int` becomes
188227
/// `long method(long x, long y)`
189228
private func renderFunctionSignature(_ decl: ImportedFunc) -> String {
190229
let translatedDecl = translatedDecl(for: decl)
191230
let resultType = translatedDecl.translatedFunctionSignature.resultType
192-
let parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter)
231+
var parameters = translatedDecl.translatedFunctionSignature.parameters.map(\.asParameter)
232+
233+
if decl.isInitializer {
234+
parameters.append("SwiftArena swiftArena$")
235+
}
236+
193237
let throwsClause = decl.isThrowing ? " throws Exception" : ""
194238

195239
return "\(resultType) \(translatedDecl.name)(\(parameters.joined(separator: ", ")))\(throwsClause)"

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import JavaTypes
16+
#if canImport(FoundationEssentials)
17+
import FoundationEssentials
18+
#else
19+
import Foundation
20+
#endif
1621

1722
extension JNISwift2JavaGenerator {
1823
func writeSwiftThunkSources() throws {
@@ -96,6 +101,8 @@ extension JNISwift2JavaGenerator {
96101
printSwiftFunctionThunk(&printer, method)
97102
printer.println()
98103
}
104+
105+
printDestroyFunctionThunk(&printer, type)
99106
}
100107

101108
private func printInitializerThunk(_ printer: inout CodePrinter, _ decl: ImportedFunc) {
@@ -199,24 +206,18 @@ extension JNISwift2JavaGenerator {
199206
resultType: JavaType,
200207
_ body: (inout CodePrinter) -> Void
201208
) {
202-
var jniSignature = parameters.reduce(into: "") { signature, parameter in
209+
let jniSignature = parameters.reduce(into: "") { signature, parameter in
203210
signature += parameter.type.jniTypeSignature
204211
}
205212

206-
// Escape signature characters
207-
jniSignature = jniSignature
208-
.replacingOccurrences(of: "_", with: "_1")
209-
.replacingOccurrences(of: "/", with: "_")
210-
.replacingOccurrences(of: ";", with: "_2")
211-
.replacingOccurrences(of: "[", with: "_3")
212-
213213
let cName =
214214
"Java_"
215215
+ self.javaPackage.replacingOccurrences(of: ".", with: "_")
216-
+ "_\(parentName)_"
217-
+ javaMethodName
216+
+ "_\(parentName.escapedJNIIdentifier)_"
217+
+ javaMethodName.escapedJNIIdentifier
218218
+ "__"
219-
+ jniSignature
219+
+ jniSignature.escapedJNIIdentifier
220+
220221
let translatedParameters = parameters.map {
221222
"\($0.name): \($0.type.jniTypeName)"
222223
}
@@ -251,6 +252,30 @@ extension JNISwift2JavaGenerator {
251252
)
252253
}
253254

255+
/// Prints the implementation of the destroy function.
256+
private func printDestroyFunctionThunk(_ printer: inout CodePrinter, _ type: ImportedNominalType) {
257+
printCDecl(
258+
&printer,
259+
javaMethodName: "$destroy",
260+
parentName: type.swiftNominal.name,
261+
parameters: [
262+
JavaParameter(name: "selfPointer", type: .long)
263+
],
264+
isStatic: true,
265+
resultType: .void
266+
) { printer in
267+
// Deinitialize the pointer allocated (which will call the VWT destroy method)
268+
// then deallocate the memory.
269+
printer.print(
270+
"""
271+
let pointer = UnsafeMutablePointer<\(type.qualifiedName)>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))!
272+
pointer.deinitialize(count: 1)
273+
pointer.deallocate()
274+
"""
275+
)
276+
}
277+
}
278+
254279
/// Renders the arguments for making a downcall
255280
private func renderDowncallArguments(
256281
swiftFunctionSignature: SwiftFunctionSignature,
@@ -266,3 +291,29 @@ extension JNISwift2JavaGenerator {
266291
.joined(separator: ", ")
267292
}
268293
}
294+
295+
extension String {
296+
/// Returns a version of the string correctly escaped for a JNI
297+
var escapedJNIIdentifier: String {
298+
self.map {
299+
if $0 == "_" {
300+
return "_1"
301+
} else if $0 == "/" {
302+
return "_"
303+
} else if $0 == ";" {
304+
return "_2"
305+
} else if $0 == "[" {
306+
return "_3"
307+
} else if $0.isASCII && ($0.isLetter || $0.isNumber) {
308+
return String($0)
309+
} else if let utf16 = $0.utf16.first {
310+
// Escape any non-alphanumeric to their UTF16 hex encoding
311+
let utf16Hex = String(format: "%04x", utf16)
312+
return "_0\(utf16Hex)"
313+
} else {
314+
fatalError("Invalid JNI character: \($0)")
315+
}
316+
}
317+
.joined()
318+
}
319+
}

SwiftKitCore/src/main/java/org/swift/swiftkit/core/JNISwiftInstance.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected JNISwiftInstance(long pointer, SwiftArena arena) {
3939
*
4040
* @return a function that is called when the value should be destroyed.
4141
*/
42-
abstract Runnable $createDestroyFunction();
42+
protected abstract Runnable $createDestroyFunction();
4343

4444
@Override
4545
public SwiftInstanceCleanup createCleanupAction() {

Tests/JExtractSwiftTests/JNI/JNIClassTests.swift

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,11 @@ struct JNIClassTests {
4444
// Swift module: SwiftModule
4545
4646
package com.example.swift;
47+
48+
import org.swift.swiftkit.core.*;
49+
import org.swift.swiftkit.core.util.*;
4750
48-
public final class MyClass {
51+
public final class MyClass extends JNISwiftInstance {
4952
static final String LIB_NAME = "SwiftModule";
5053
5154
@SuppressWarnings("unused")
@@ -55,12 +58,25 @@ struct JNIClassTests {
5558
return true;
5659
}
5760
58-
private long selfPointer;
59-
60-
private MyClass(long selfPointer) {
61-
this.selfPointer = selfPointer;
61+
public MyClass(long selfPointer, SwiftArena swiftArena) {
62+
super(selfPointer, swiftArena);
6263
}
6364
""",
65+
"""
66+
private static native void $destroy(long selfPointer);
67+
""",
68+
"""
69+
@Override
70+
protected Runnable $createDestroyFunction() {
71+
long $selfPointer = this.pointer();
72+
return new Runnable() {
73+
@Override
74+
public void run() {
75+
MyClass.$destroy($selfPointer);
76+
}
77+
};
78+
}
79+
"""
6480
])
6581
}
6682

@@ -116,9 +132,9 @@ struct JNIClassTests {
116132
* public init(x: Int64, y: Int64)
117133
* }
118134
*/
119-
public static MyClass init(long x, long y) {
135+
public static MyClass init(long x, long y, SwiftArena swiftArena$) {
120136
long selfPointer = MyClass.allocatingInit(x, y);
121-
return new MyClass(selfPointer);
137+
return new MyClass(selfPointer, swiftArena$);
122138
}
123139
""",
124140
"""
@@ -128,9 +144,9 @@ struct JNIClassTests {
128144
* public init()
129145
* }
130146
*/
131-
public static MyClass init() {
147+
public static MyClass init(SwiftArena swiftArena$) {
132148
long selfPointer = MyClass.allocatingInit();
133-
return new MyClass(selfPointer);
149+
return new MyClass(selfPointer, swiftArena$);
134150
}
135151
""",
136152
"""
@@ -170,4 +186,24 @@ struct JNIClassTests {
170186
]
171187
)
172188
}
189+
190+
@Test
191+
func destroyFunction_swiftThunks() throws {
192+
try assertOutput(
193+
input: source,
194+
.jni,
195+
.swift,
196+
detectChunkByInitialLines: 1,
197+
expectedChunks: [
198+
"""
199+
@_cdecl("Java_com_example_swift_MyClass__00024destroy__J")
200+
func Java_com_example_swift_MyClass__00024destroy__J(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, selfPointer: jlong) {
201+
let pointer = UnsafeMutablePointer<MyClass>(bitPattern: Int(Int64(fromJNI: selfPointer, in: environment!)))!
202+
pointer.deinitialize(count: 1)
203+
pointer.deallocate()
204+
}
205+
"""
206+
]
207+
)
208+
}
173209
}

0 commit comments

Comments
 (0)