Skip to content

Commit d1268d6

Browse files
committed
progress towards SwiftArena which manages swift values and heap objects
1 parent 1bfa241 commit d1268d6

File tree

7 files changed

+243
-21
lines changed

7 files changed

+243
-21
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.generated.MySwiftClass;
18+
import org.junit.jupiter.api.BeforeAll;
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.condition.DisabledOnOs;
21+
import org.junit.jupiter.api.condition.OS;
22+
23+
import java.lang.foreign.Arena;
24+
25+
import static org.junit.jupiter.api.Assertions.assertEquals;
26+
27+
public class SwiftArenaTests {
28+
29+
@BeforeAll
30+
static void beforeAll() {
31+
System.out.printf("java.library.path = %s\n", System.getProperty("java.library.path"));
32+
33+
System.loadLibrary("swiftCore");
34+
System.loadLibrary("ExampleSwiftLibrary");
35+
36+
System.setProperty("jextract.trace.downcalls", "true");
37+
}
38+
39+
@Test
40+
@DisabledOnOs(OS.LINUX) // FIXME: enable on Linux when we get new compiler with mangled names in swift interfaces
41+
void arena_releaseClassOnClose() {
42+
try (var arena = SwiftArena.ofConfined()) {
43+
var obj = new MySwiftClass(1, 2);
44+
45+
assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
46+
// TODO: test directly on SwiftHeapObject inheriting obj
47+
48+
SwiftKit.retain(obj.$memorySegment());
49+
assertEquals(2, SwiftKit.retainCount(obj.$memorySegment()));
50+
51+
SwiftKit.release(obj.$memorySegment());
52+
assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
53+
}
54+
}
55+
}

Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ extension Swift2JavaTranslator {
168168
}
169169

170170
public func printClass(_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void) {
171-
printer.printTypeDecl("public final class \(decl.javaClassName)") { printer in
171+
printer.printTypeDecl("public final class \(decl.javaClassName) implements SwiftHeapObject") { printer in
172172
// ==== Storage of the class
173173
// FIXME: implement the self storage for the memory address and accessors
174174
printClassSelfProperty(&printer, decl)

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

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,50 +16,134 @@
1616

1717
import java.lang.foreign.Arena;
1818
import java.lang.foreign.MemorySegment;
19+
import java.util.LinkedList;
20+
import java.util.List;
1921
import java.util.concurrent.ConcurrentSkipListSet;
20-
22+
import java.util.concurrent.atomic.AtomicInteger;
23+
24+
/**
25+
* A Swift arena manages Swift allocated memory for classes, structs, enums etc.
26+
* When an arena is closed, it will destroy all managed swift objects in a way appropriate to their type.
27+
* <p>
28+
* A confined arena has an associated owner thread that confines some operations to
29+
* associated owner thread such as {@link #close()}.
30+
*/
2131
public interface SwiftArena extends Arena {
2232

23-
void release(MemorySegment segment);
33+
static SwiftArena ofConfined() {
34+
return new ConfinedSwiftMemorySession(Thread.currentThread());
35+
}
2436

25-
void retain(MemorySegment segment);
37+
/**
38+
* Register a struct, enum or other non-reference counted Swift object.
39+
* Its memory should be considered managed by this arena, and be destroyed when the arena is closed.
40+
*
41+
* @param object
42+
*/
43+
void register(SwiftHeapObject object);
2644

27-
long retainCount(MemorySegment segment);
45+
void register(SwiftValue value);
2846

2947
}
3048

31-
final class AutoSwiftArena implements SwiftArena {
32-
Arena underlying;
49+
final class ConfinedSwiftMemorySession implements SwiftArena {
50+
51+
final Arena underlying;
52+
final Thread owner;
53+
final SwiftResourceList resources;
3354

34-
ConcurrentSkipListSet<MemorySegment> managedMemorySegments;
55+
// TODO: just int and volatile updates
56+
final int CLOSED = 0;
57+
final int ACTIVE = 1;
58+
final AtomicInteger state;
59+
60+
public ConfinedSwiftMemorySession(Thread owner) {
61+
this.owner = owner;
62+
underlying = Arena.ofConfined();
63+
resources = new ConfinedResourceList();
64+
state = new AtomicInteger(ACTIVE);
65+
}
3566

3667
@Override
3768
public MemorySegment allocate(long byteSize, long byteAlignment) {
38-
return null;
69+
return underlying.allocate(byteSize, byteAlignment);
3970
}
4071

4172
@Override
4273
public MemorySegment.Scope scope() {
43-
return null;
74+
return underlying.scope();
4475
}
4576

46-
@Override
47-
public void close() {
48-
77+
public void checkValid() throws RuntimeException {
78+
if (this.owner != null && this.owner != Thread.currentThread()) {
79+
throw new WrongThreadException("ConfinedSwift arena is confined to %s but was closed from %s!".formatted(this.owner, Thread.currentThread()));
80+
} else if (this.state.get() < ACTIVE) {
81+
throw new RuntimeException("Arena is already closed!");
82+
}
4983
}
5084

5185
@Override
52-
public void release(MemorySegment segment) {
53-
SwiftKit.release(segment);
86+
public void register(SwiftHeapObject object) {
87+
this.resources.add(new SwiftHeapObjectCleanup(object.$memorySegment()));
5488
}
5589

5690
@Override
57-
public void retain(MemorySegment segment) {
58-
SwiftKit.retain(segment);
91+
public void register(SwiftValue value) {
92+
this.resources.add(new SwiftHeapObjectCleanup(value.$memorySegment()));
5993
}
6094

6195
@Override
62-
public long retainCount(MemorySegment segment) {
63-
return SwiftKit.retainCount(segment);
96+
public void close() {
97+
checkValid();
98+
99+
// Cleanup all resources
100+
if (this.state.compareAndExchange(ACTIVE, CLOSED) == ACTIVE) {
101+
this.resources.cleanup();
102+
} // else, was already closed; do nothing
103+
104+
105+
// Those the underlying arena
106+
this.underlying.close();
107+
108+
// After this method returns normally, the scope must be not alive anymore
109+
assert (!this.scope().isAlive());
110+
}
111+
112+
/**
113+
* Represents a list of resources that need a cleanup, e.g. allocated classes/structs.
114+
*/
115+
static abstract class SwiftResourceList implements Runnable {
116+
// TODO: Could use intrusive linked list to avoid one indirection here
117+
final List<SwiftMemoryResourceCleanup> resourceCleanups = new LinkedList<>();
118+
119+
abstract void add(SwiftMemoryResourceCleanup cleanup);
120+
121+
public abstract void cleanup();
122+
123+
public final void run() {
124+
cleanup(); // cleaner interop
125+
}
126+
}
127+
128+
static final class ConfinedResourceList extends SwiftResourceList {
129+
@Override
130+
void add(SwiftMemoryResourceCleanup cleanup) {
131+
resourceCleanups.add(cleanup);
132+
}
133+
134+
@Override
135+
public void cleanup() {
136+
for (SwiftMemoryResourceCleanup cleanup : resourceCleanups) {
137+
cleanup.run();
138+
}
139+
}
140+
}
141+
}
142+
143+
final class UnexpectedRetainCountException extends RuntimeException {
144+
public UnexpectedRetainCountException(MemorySegment resource, long retainCount, int expectedRetainCount) {
145+
super(("Attempting to cleanup managed memory segment %s, but it's retain count was different than [%d] (was %d)! " +
146+
"This would result in destroying a swift object that is still retained by other code somewhere."
147+
).formatted(resource, expectedRetainCount, retainCount));
64148
}
65149
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import java.lang.foreign.MemorySegment;
1818

19-
public interface SwiftHeapObject {
20-
MemorySegment $self();
19+
/**
20+
* Represents a wrapper around a Swift heap object, e.g. a {@code class} or an {@code actor}.
21+
*/
22+
public interface SwiftHeapObject extends SwiftMemoryResource {
2123
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 java.lang.foreign.MemorySegment;
18+
19+
public interface SwiftMemoryResource {
20+
MemorySegment $memorySegment();
21+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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 java.lang.foreign.MemorySegment;
18+
19+
/**
20+
* A Swift memory resource cleanup, e.g. count-down a reference count and destroy a class, or destroy struct/enum etc.
21+
*/
22+
interface SwiftMemoryResourceCleanup extends Runnable {
23+
}
24+
25+
record SwiftHeapObjectCleanup(MemorySegment resource) implements SwiftMemoryResourceCleanup {
26+
27+
@Override
28+
public void run() {
29+
long retainedCount = SwiftKit.retainCount(this.resource);
30+
if (retainedCount > 1) {
31+
throw new UnexpectedRetainCountException(this.resource, retainedCount, 1);
32+
}
33+
34+
}
35+
}
36+
37+
record SwiftValueCleanup(MemorySegment resource) implements SwiftMemoryResourceCleanup {
38+
@Override
39+
public void run() {
40+
SwiftKit.retainCount(this.resource);
41+
}
42+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
public interface SwiftValue extends SwiftMemoryResource {
18+
}

0 commit comments

Comments
 (0)