Skip to content

[JExtract] Unify mechanisms between value types and reference types #240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class JavaToSwiftBenchmark {

@State(Scope.Benchmark)
public static class BenchmarkState {
ClosableSwiftArena arena;
MySwiftClass obj;

@Setup(Level.Trial)
Expand All @@ -43,7 +44,13 @@ public void beforeALl() {
// Tune down debug statements so they don't fill up stdout
System.setProperty("jextract.trace.downcalls", "false");

obj = new MySwiftClass(1, 2);
arena = SwiftArena.ofConfined();
obj = new MySwiftClass(1, 2, arena);
}

@TearDown(Level.Trial)
public void afterAll() {
arena.close();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ static void examples() {

// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
MySwiftClass obj = new MySwiftClass(2222, 7777, arena);

// just checking retains/releases work
SwiftKit.retain(obj.$memorySegment());
SwiftKit.release(obj.$memorySegment());
SwiftKit.retain(obj);
SwiftKit.release(obj);

obj.voidMethod();
obj.takeIntMethod(42);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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;
Expand All @@ -41,8 +42,8 @@ void checkPaths(Throwable throwable) {

@Test
void test_MySwiftClass_voidMethod() {
try {
MySwiftClass o = new MySwiftClass(12, 42);
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
Expand All @@ -51,17 +52,21 @@ void test_MySwiftClass_voidMethod() {

@Test
void test_MySwiftClass_makeIntMethod() {
MySwiftClass o = new MySwiftClass(12, 42);
var got = o.makeIntMethod();
assertEquals(12, got);
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
var got = o.makeIntMethod();
assertEquals(12, got);
}
}

@Test
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
MySwiftClass o = new MySwiftClass(12, 42);
var got = o.getLen();
assertEquals(12, got);
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
var got = o.getLen();
assertEquals(12, got);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ public class MySwiftClassTest {
@Test
void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
var obj = new MySwiftClass(arena, 1, 2);
var obj = new MySwiftClass(1, 2, arena);

assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj

SwiftKit.retain(obj.$memorySegment());
assertEquals(2, SwiftKit.retainCount(obj.$memorySegment()));
SwiftKit.retain(obj);
assertEquals(2, SwiftKit.retainCount(obj));

SwiftKit.release(obj.$memorySegment());
assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
SwiftKit.release(obj);
assertEquals(1, SwiftKit.retainCount(obj));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,37 +39,15 @@ static boolean isAmd64() {
@DisabledIf("isAmd64")
public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(arena,1, 2);
var obj = new MySwiftClass(1, 2, arena);

retain(obj.$memorySegment());
assertEquals(2, retainCount(obj.$memorySegment()));
retain(obj);
assertEquals(2, retainCount(obj));

release(obj.$memorySegment());
assertEquals(1, retainCount(obj.$memorySegment()));
release(obj);
assertEquals(1, retainCount(obj));
}

// TODO: should we zero out the $memorySegment perhaps?
}

@Test
public void arena_releaseClassOnClose_class_leaked() {
String memorySegmentDescription = "<none>";

try {
try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(arena,1, 2);
memorySegmentDescription = obj.$memorySegment().toString();

// Pretend that we "leaked" the class, something still holds a reference to it while we try to destroy it
retain(obj.$memorySegment());
assertEquals(2, retainCount(obj.$memorySegment()));
}

fail("Expected exception to be thrown while the arena is closed!");
} catch (Exception ex) {
// The message should point out which objects "leaked":
assertTrue(ex.getMessage().contains(memorySegmentDescription));
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static class BenchmarkState {
@Setup(Level.Trial)
public void beforeAll() {
arena = SwiftArena.ofConfined();
obj = new MySwiftClass(arena, 1, 2);
obj = new MySwiftClass(1, 2, arena);
}

@TearDown(Level.Trial)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class StringPassingBenchmark {
@Setup(Level.Trial)
public void beforeAll() {
arena = SwiftArena.ofConfined();
obj = new MySwiftClass(arena, 1, 2);
obj = new MySwiftClass(1, 2, arena);
string = makeString(stringLen);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,19 @@ static void examples() {

// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
MySwiftClass obj = new MySwiftClass(2222, 7777, arena);

// just checking retains/releases work
SwiftKit.retain(obj.$memorySegment());
SwiftKit.release(obj.$memorySegment());
SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
SwiftKit.retain(obj);
SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));
SwiftKit.release(obj);
SwiftKit.trace("retainCount = " + SwiftKit.retainCount(obj));

obj.voidMethod();
obj.takeIntMethod(42);

MySwiftStruct swiftValue = new MySwiftStruct(arena, 2222, 1111);
MySwiftStruct swiftValue = new MySwiftStruct(2222, 1111, arena);
}

System.out.println("DONE.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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;
Expand All @@ -40,8 +41,8 @@ void checkPaths(Throwable throwable) {

@Test
void test_MySwiftClass_voidMethod() {
try {
MySwiftClass o = new MySwiftClass(12, 42);
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
o.voidMethod();
} catch (Throwable throwable) {
checkPaths(throwable);
Expand All @@ -50,17 +51,21 @@ void test_MySwiftClass_voidMethod() {

@Test
void test_MySwiftClass_makeIntMethod() {
MySwiftClass o = new MySwiftClass(12, 42);
var got = o.makeIntMethod();
assertEquals(12, got);
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
var got = o.makeIntMethod();
assertEquals(12, got);
}
}

@Test
@Disabled // TODO: Need var mangled names in interfaces
void test_MySwiftClass_property_len() {
MySwiftClass o = new MySwiftClass(12, 42);
var got = o.getLen();
assertEquals(12, got);
try(var arena = SwiftArena.ofConfined()) {
MySwiftClass o = new MySwiftClass(12, 42, arena);
var got = o.getLen();
assertEquals(12, got);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ public class MySwiftClassTest {
@Test
void call_retain_retainCount_release() {
var arena = SwiftArena.ofConfined();
var obj = new MySwiftClass(arena, 1, 2);
var obj = new MySwiftClass(1, 2, arena);

assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
assertEquals(1, SwiftKit.retainCount(obj));
// TODO: test directly on SwiftHeapObject inheriting obj

SwiftKit.retain(obj.$memorySegment());
assertEquals(2, SwiftKit.retainCount(obj.$memorySegment()));
SwiftKit.retain(obj);
assertEquals(2, SwiftKit.retainCount(obj));

SwiftKit.release(obj.$memorySegment());
assertEquals(1, SwiftKit.retainCount(obj.$memorySegment()));
SwiftKit.release(obj);
assertEquals(1, SwiftKit.retainCount(obj));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void create_struct() {
try (var arena = SwiftArena.ofConfined()) {
long cap = 12;
long len = 34;
var struct = new MySwiftStruct(arena, cap, len);
var struct = new MySwiftStruct(cap, len, arena);

assertEquals(cap, struct.getCapacity());
assertEquals(len, struct.getLength());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ static boolean isAmd64() {
@DisabledIf("isAmd64")
public void arena_releaseClassOnClose_class_ok() {
try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(arena,1, 2);
var obj = new MySwiftClass(1, 2, arena);

retain(obj.$memorySegment());
assertEquals(2, retainCount(obj.$memorySegment()));
retain(obj);
assertEquals(2, retainCount(obj));

release(obj.$memorySegment());
assertEquals(1, retainCount(obj.$memorySegment()));
release(obj);
assertEquals(1, retainCount(obj));
}
}

Expand All @@ -57,7 +57,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_class() {
MySwiftClass unsafelyEscapedOutsideArenaScope = null;

try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(arena,1, 2);
var obj = new MySwiftClass(1, 2, arena);
unsafelyEscapedOutsideArenaScope = obj;
}

Expand All @@ -76,7 +76,7 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
MySwiftStruct unsafelyEscapedOutsideArenaScope = null;

try (var arena = SwiftArena.ofConfined()) {
var s = new MySwiftStruct(arena,1, 2);
var s = new MySwiftStruct(1, 2, arena);
unsafelyEscapedOutsideArenaScope = s;
}

Expand All @@ -88,27 +88,6 @@ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
}
}

@Test
public void arena_releaseClassOnClose_class_leaked() {
String memorySegmentDescription = "<none>";

try {
try (var arena = SwiftArena.ofConfined()) {
var obj = new MySwiftClass(arena,1, 2);
memorySegmentDescription = obj.$memorySegment().toString();

// Pretend that we "leaked" the class, something still holds a reference to it while we try to destroy it
retain(obj.$memorySegment());
assertEquals(2, retainCount(obj.$memorySegment()));
}

fail("Expected exception to be thrown while the arena is closed!");
} catch (Exception ex) {
// The message should point out which objects "leaked":
assertTrue(ex.getMessage().contains(memorySegmentDescription));
}
}

@Test
public void arena_initializeWithCopy_struct() {

Expand Down
8 changes: 6 additions & 2 deletions Sources/JExtractSwift/ImportedDecls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,12 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
public var isInit: Bool = false

public var isIndirectReturn: Bool {
returnType.isValueType ||
(isInit && (parent?.isValueType ?? false))
switch returnType.originalSwiftTypeKind {
case .actor, .class, .struct, .enum:
return true
default:
return false
}
}

public init(
Expand Down
Loading