Skip to content

Commit 7700181

Browse files
committed
[JExtract] Unify mechanisms between value types and reference types
Unify memory and instance management mechanism between value types (e.g. `string` or `enum`) and reference types (e.g. `class` and `actor`). Now all imported nominal types are allocated using the value witness table, are returned indirectly, and are destroyed in the same manner. `SwiftInstance` is now the abstract base class for all the imported types including reference types. Concrete types can simply construct it by calling `super(memorySegment, arena)`.
1 parent 8269473 commit 7700181

File tree

18 files changed

+214
-363
lines changed

18 files changed

+214
-363
lines changed

Samples/SwiftAndJavaJarSampleLib/src/test/java/org/swift/swiftkit/SwiftArenaTest.java

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,4 @@ public void arena_releaseClassOnClose_class_ok() {
5050

5151
// TODO: should we zero out the $memorySegment perhaps?
5252
}
53-
54-
@Test
55-
public void arena_releaseClassOnClose_class_leaked() {
56-
String memorySegmentDescription = "<none>";
57-
58-
try {
59-
try (var arena = SwiftArena.ofConfined()) {
60-
var obj = new MySwiftClass(arena,1, 2);
61-
memorySegmentDescription = obj.$memorySegment().toString();
62-
63-
// Pretend that we "leaked" the class, something still holds a reference to it while we try to destroy it
64-
retain(obj.$memorySegment());
65-
assertEquals(2, retainCount(obj.$memorySegment()));
66-
}
67-
68-
fail("Expected exception to be thrown while the arena is closed!");
69-
} catch (Exception ex) {
70-
// The message should point out which objects "leaked":
71-
assertTrue(ex.getMessage().contains(memorySegmentDescription));
72-
}
73-
74-
}
7553
}

Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,27 +88,6 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
8888
}
8989
}
9090

91-
@Test
92-
public void arena_releaseClassOnClose_class_leaked() {
93-
String memorySegmentDescription = "<none>";
94-
95-
try {
96-
try (var arena = SwiftArena.ofConfined()) {
97-
var obj = new MySwiftClass(arena,1, 2);
98-
memorySegmentDescription = obj.$memorySegment().toString();
99-
100-
// Pretend that we "leaked" the class, something still holds a reference to it while we try to destroy it
101-
retain(obj.$memorySegment());
102-
assertEquals(2, retainCount(obj.$memorySegment()));
103-
}
104-
105-
fail("Expected exception to be thrown while the arena is closed!");
106-
} catch (Exception ex) {
107-
// The message should point out which objects "leaked":
108-
assertTrue(ex.getMessage().contains(memorySegmentDescription));
109-
}
110-
}
111-
11291
@Test
11392
public void arena_initializeWithCopy_struct() {
11493

Sources/JExtractSwift/ImportedDecls.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,12 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
267267
public var isInit: Bool = false
268268

269269
public var isIndirectReturn: Bool {
270-
returnType.isValueType ||
271-
(isInit && (parent?.isValueType ?? false))
270+
switch returnType.originalSwiftTypeKind {
271+
case .actor, .class, .struct, .enum:
272+
return true
273+
default:
274+
return false
275+
}
272276
}
273277

274278
public init(

Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift

Lines changed: 17 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,8 @@ extension Swift2JavaTranslator {
290290
parentProtocol = "SwiftValue"
291291
}
292292

293-
printer.printTypeDecl("public final class \(decl.javaClassName) implements \(parentProtocol)") {
293+
printer.printTypeDecl("public final class \(decl.javaClassName) extends SwiftInstance implements \(parentProtocol)") {
294294
printer in
295-
// ==== Storage of the class
296-
printClassSelfProperty(&printer, decl)
297-
printStatusFlagsField(&printer, decl)
298295

299296
// Constants
300297
printClassConstants(printer: &printer)
@@ -400,35 +397,6 @@ extension Swift2JavaTranslator {
400397
)
401398
}
402399

403-
/// Print a property where we can store the "self" pointer of a class.
404-
private func printClassSelfProperty(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
405-
printer.print(
406-
"""
407-
// Pointer to the referred to class instance's "self".
408-
private final MemorySegment selfMemorySegment;
409-
410-
public final MemorySegment $memorySegment() {
411-
return this.selfMemorySegment;
412-
}
413-
"""
414-
)
415-
}
416-
417-
private func printStatusFlagsField(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
418-
printer.print(
419-
"""
420-
// TODO: make this a flagset integer and/or use a field updater
421-
/** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */
422-
private final AtomicBoolean $state$destroyed = new AtomicBoolean(false);
423-
424-
@Override
425-
public final AtomicBoolean $statusDestroyedFlag() {
426-
return this.$state$destroyed;
427-
}
428-
"""
429-
)
430-
}
431-
432400
private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
433401
printer.print(
434402
"""
@@ -472,30 +440,11 @@ extension Swift2JavaTranslator {
472440
\(decl.renderCommentSnippet ?? " *")
473441
*/
474442
public \(parentName.unqualifiedJavaTypeName)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
475-
this(/*arena=*/null, \(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
443+
this(SwiftArena.ofAuto(), \(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
476444
}
477445
"""
478446
)
479447

480-
let initializeMemorySegment =
481-
if let parent = decl.parent,
482-
parent.isReferenceType
483-
{
484-
"""
485-
this.selfMemorySegment = (MemorySegment) mh$.invokeExact(
486-
\(renderForwardJavaParams(decl, paramPassingStyle: nil))
487-
);
488-
"""
489-
} else {
490-
"""
491-
this.selfMemorySegment = arena.allocate($layout());
492-
mh$.invokeExact(
493-
\(renderForwardJavaParams(decl, paramPassingStyle: nil)),
494-
/* indirect return buffer */this.selfMemorySegment
495-
);
496-
"""
497-
}
498-
499448
printer.print(
500449
"""
501450
/**
@@ -505,20 +454,23 @@ extension Swift2JavaTranslator {
505454
\(decl.renderCommentSnippet ?? " *")
506455
*/
507456
public \(parentName.unqualifiedJavaTypeName)(SwiftArena arena, \(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
508-
var mh$ = \(descClassIdentifier).HANDLE;
509-
try {
457+
super(() -> {
458+
var mh$ = \(descClassIdentifier).HANDLE;
459+
try {
460+
MemorySegment _result = arena.allocate($LAYOUT);
461+
510462
if (SwiftKit.TRACE_DOWNCALLS) {
511463
SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: nil)));
512464
}
513-
514-
\(initializeMemorySegment)
515-
516-
if (arena != null) {
517-
arena.register(this);
518-
}
519-
} catch (Throwable ex$) {
520-
throw new AssertionError("should not reach here", ex$);
521-
}
465+
mh$.invokeExact(
466+
\(renderForwardJavaParams(decl, paramPassingStyle: nil)),
467+
/* indirect return buffer */_result
468+
);
469+
return _result;
470+
} catch (Throwable ex$) {
471+
throw new AssertionError("should not reach here", ex$);
472+
}
473+
}, arena);
522474
}
523475
"""
524476
)
@@ -714,9 +666,7 @@ extension Swift2JavaTranslator {
714666
let guardFromDestroyedObjectCalls: String =
715667
if decl.hasParent {
716668
"""
717-
if (this.$state$destroyed.get()) {
718-
throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!");
719-
}
669+
$ensureAlive();
720670
"""
721671
} else { "" }
722672

Sources/JExtractSwift/SwiftThunkTranslator.swift

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -93,32 +93,18 @@ struct SwiftThunkTranslator {
9393
"""
9494
let typeName = "\(parent.swiftTypeName)"
9595

96-
if parent.isReferenceType {
97-
return [
98-
"""
99-
\(raw: cDecl)
100-
public func \(raw: thunkName)(\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil))) -> UnsafeMutableRawPointer /* \(raw: typeName) */ {
101-
var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
102-
let self$ = unsafeBitCast(_self, to: UnsafeMutableRawPointer.self)
103-
_swiftjava_swift_retain(object: self$)
104-
return self$
105-
}
106-
"""
107-
]
108-
} else {
109-
return [
110-
"""
111-
\(raw: cDecl)
112-
public func \(raw: thunkName)(
113-
\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil)),
114-
resultBuffer: /* \(raw: typeName) */ UnsafeMutableRawPointer
115-
) {
116-
var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
117-
resultBuffer.assumingMemoryBound(to: \(raw: typeName).self).initialize(to: _self)
118-
}
119-
"""
120-
]
121-
}
96+
return [
97+
"""
98+
\(raw: cDecl)
99+
public func \(raw: thunkName)(
100+
\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil)),
101+
resultBuffer: /* \(raw: typeName) */ UnsafeMutableRawPointer
102+
) {
103+
var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil)))
104+
resultBuffer.assumingMemoryBound(to: \(raw: typeName).self).initialize(to: _self)
105+
}
106+
"""
107+
]
122108
}
123109

124110
func render(forFunc decl: ImportedFunc) -> [DeclSyntax] {
@@ -136,11 +122,7 @@ struct SwiftThunkTranslator {
136122
let paramPassingStyle: SelfParameterVariant?
137123
let callBase: String
138124
let callBaseDot: String
139-
if let parent = decl.parent, parent.isReferenceType {
140-
paramPassingStyle = .swiftThunkSelf
141-
callBase = "let self$ = unsafeBitCast(_self, to: \(parent.originalSwiftType).self)"
142-
callBaseDot = "self$."
143-
} else if let parent = decl.parent, !parent.isReferenceType {
125+
if let parent = decl.parent {
144126
paramPassingStyle = .swiftThunkSelf
145127
callBase =
146128
"var self$ = _self.assumingMemoryBound(to: \(parent.originalSwiftType).self).pointee"

SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,38 +48,16 @@ public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) {
4848
}
4949

5050
@Override
51-
public void register(SwiftHeapObject object) {
52-
var statusDestroyedFlag = object.$statusDestroyedFlag();
53-
Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
54-
55-
SwiftHeapObjectCleanup cleanupAction = new SwiftHeapObjectCleanup(
56-
object.$memorySegment(),
57-
object.$swiftType(),
58-
markAsDestroyed
59-
);
60-
register(object, cleanupAction);
61-
}
62-
63-
// visible for testing
64-
void register(SwiftHeapObject object, SwiftHeapObjectCleanup cleanupAction) {
65-
Objects.requireNonNull(object, "obj");
66-
Objects.requireNonNull(cleanupAction, "cleanupAction");
67-
68-
69-
cleaner.register(object, cleanupAction);
70-
}
71-
72-
@Override
73-
public void register(SwiftValue value) {
74-
Objects.requireNonNull(value, "value");
51+
public void register(SwiftInstance instance) {
52+
Objects.requireNonNull(instance, "value");
7553

7654
// We're doing this dance to avoid keeping a strong reference to the value itself
77-
var statusDestroyedFlag = value.$statusDestroyedFlag();
55+
var statusDestroyedFlag = instance.$statusDestroyedFlag();
7856
Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
7957

80-
MemorySegment resource = value.$memorySegment();
81-
var cleanupAction = new SwiftValueCleanup(resource, value.$swiftType(), markAsDestroyed);
82-
cleaner.register(value, cleanupAction);
58+
MemorySegment resource = instance.$memorySegment();
59+
var cleanupAction = new SwiftInstanceCleanup(resource, instance.$swiftType(), markAsDestroyed);
60+
cleaner.register(instance, cleanupAction);
8361
}
8462

8563
@Override

SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,28 +61,15 @@ public void close() {
6161
}
6262

6363
@Override
64-
public void register(SwiftHeapObject object) {
64+
public void register(SwiftInstance instance) {
6565
checkValid();
6666

67-
var statusDestroyedFlag = object.$statusDestroyedFlag();
67+
var statusDestroyedFlag = instance.$statusDestroyedFlag();
6868
Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
6969

70-
var cleanup = new SwiftHeapObjectCleanup(
71-
object.$memorySegment(), object.$swiftType(),
72-
markAsDestroyed);
73-
this.resources.add(cleanup);
74-
}
75-
76-
@Override
77-
public void register(SwiftValue value) {
78-
checkValid();
79-
80-
var statusDestroyedFlag = value.$statusDestroyedFlag();
81-
Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
82-
83-
var cleanup = new SwiftValueCleanup(
84-
value.$memorySegment(),
85-
value.$swiftType(),
70+
var cleanup = new SwiftInstanceCleanup(
71+
instance.$memorySegment(),
72+
instance.$swiftType(),
8673
markAsDestroyed);
8774
this.resources.add(cleanup);
8875
}

SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,18 @@ public SwiftAnyType(MemorySegment memorySegment) {
3434
this.memorySegment = memorySegment.asReadOnly();
3535
}
3636

37-
public SwiftAnyType(SwiftHeapObject object) {
38-
if (object.$layout().name().isEmpty()) {
39-
throw new IllegalArgumentException("SwiftHeapObject must have a mangled name in order to obtain its SwiftType.");
40-
}
41-
42-
String mangledName = object.$layout().name().get();
43-
var type = SwiftKit.getTypeByMangledNameInEnvironment(mangledName);
44-
if (type.isEmpty()) {
45-
throw new IllegalArgumentException("A Swift Any.Type cannot be null!");
46-
}
47-
this.memorySegment = type.get().memorySegment;
48-
}
37+
// public SwiftAnyType(SwiftHeapObject object) {
38+
// if (object.$layout().name().isEmpty()) {
39+
// throw new IllegalArgumentException("SwiftHeapObject must have a mangled name in order to obtain its SwiftType.");
40+
// }
41+
//
42+
// String mangledName = object.$layout().name().get();
43+
// var type = SwiftKit.getTypeByMangledNameInEnvironment(mangledName);
44+
// if (type.isEmpty()) {
45+
// throw new IllegalArgumentException("A Swift Any.Type cannot be null!");
46+
// }
47+
// this.memorySegment = type.get().memorySegment;
48+
// }
4949

5050

5151
public MemorySegment $memorySegment() {

SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,10 @@ static SwiftArena ofAuto() {
3636
}
3737

3838
/**
39-
* Register a Swift reference counted heap object with this arena (such as a {@code class} or {@code actor}).
39+
* Register a Swift object.
4040
* Its memory should be considered managed by this arena, and be destroyed when the arena is closed.
4141
*/
42-
void register(SwiftHeapObject object);
43-
44-
/**
45-
* Register a struct, enum or other non-reference counted Swift object.
46-
* Its memory should be considered managed by this arena, and be destroyed when the arena is closed.
47-
*/
48-
void register(SwiftValue value);
42+
void register(SwiftInstance instance);
4943

5044
}
5145

0 commit comments

Comments
 (0)