Skip to content
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

jextract: Indirect returns and more value type support #211

Merged
merged 14 commits into from
Jan 28, 2025
Merged
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ DerivedData/
*.class
bin/
BuildLogic/out/
.index-build
.build-vscode

# Ignore gradle build artifacts
.gradle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@

public struct MySwiftStruct {

public var number: Int
private var cap: Int
private var len: Int

public init(number: Int) {
self.number = number
public init(cap: Int, len: Int) {
self.cap = cap
self.len = len
}

public func voidMethod() {
Expand All @@ -38,6 +40,20 @@ public struct MySwiftStruct {
return 12
}

public func getCapacity() -> Int {
self.cap
}

public func getLength() -> Int {
self.len
}

public mutating func increaseCap(by value: Int) -> Int {
precondition(value > 0)
self.cap += value
return self.cap
}

public func makeRandomIntMethod() -> Int {
return Int.random(in: 1..<256)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@
import org.swift.swiftkit.SwiftKit;
import org.swift.swiftkit.SwiftValueWitnessTable;

import java.util.Arrays;

public class HelloJava2Swift {

public static void main(String[] args) {
boolean traceDowncalls = Boolean.getBoolean("jextract.trace.downcalls");
System.out.println("Property: jextract.trace.downcalls = " + traceDowncalls);

System.out.print("Property: java.library.path = " +SwiftKit.getJavaLibraryPath());
System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath());

examples();
}
Expand All @@ -44,23 +42,23 @@ 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);

// just checking retains/releases work
SwiftKit.retain(obj.$memorySegment());
SwiftKit.release(obj.$memorySegment());
MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);

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

MySwiftStruct swiftValue = new MySwiftStruct(12);
obj.voidMethod();
obj.takeIntMethod(42);

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

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

public static native long jniWriteString(String str);

public static native long jniGetInt();

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public class MySwiftClassTest {
void checkPaths(Throwable throwable) {
var paths = SwiftKit.getJavaLibraryPath().split(":");
for (var path : paths) {
System.out.println("CHECKING PATH: " + path);
Stream.of(new File(path).listFiles())
.filter(file -> !file.isDirectory())
.forEach((file) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,24 @@
//
//===----------------------------------------------------------------------===//

package com.example.swift;
package org.swift.swiftkit;

import org.junit.jupiter.api.Disabled;
import com.example.swift.MySwiftStruct;
import org.junit.jupiter.api.Test;
import org.swift.swiftkit.SwiftArena;
import org.swift.swiftkit.SwiftKit;

import java.io.File;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class MySwiftStructTest {

@Test
void test_MySwiftClass_voidMethod() {
void create_struct() {
try (var arena = SwiftArena.ofConfined()) {
MySwiftStruct o = new MySwiftStruct(12);
// o.voidMethod();
long cap = 12;
long len = 34;
var struct = new MySwiftStruct(arena, cap, len);

assertEquals(cap, struct.getCapacity());
assertEquals(len, struct.getLength());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.swift.swiftkit;

import com.example.swift.MySwiftClass;
import com.example.swift.MySwiftStruct;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
Expand Down Expand Up @@ -47,8 +48,44 @@ public void arena_releaseClassOnClose_class_ok() {
release(obj.$memorySegment());
assertEquals(1, retainCount(obj.$memorySegment()));
}
}

// FIXME: The destroy witness table call hangs on x86_64 platforms during the destroy witness table call
// See: https://github.com/swiftlang/swift-java/issues/97
@Test
public void arena_markAsDestroyed_preventUseAfterFree_class() {
MySwiftClass unsafelyEscapedOutsideArenaScope = null;

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

try {
unsafelyEscapedOutsideArenaScope.echoIntMethod(1);
fail("Expected exception to be thrown! Object was suposed to be dead.");
} catch (IllegalStateException ex) {
return;
}
}

// FIXME: The destroy witness table call hangs on x86_64 platforms during the destroy witness table call
// See: https://github.com/swiftlang/swift-java/issues/97
@Test
public void arena_markAsDestroyed_preventUseAfterFree_struct() {
MySwiftStruct unsafelyEscapedOutsideArenaScope = null;

// TODO: should we zero out the $memorySegment perhaps?
try (var arena = SwiftArena.ofConfined()) {
var s = new MySwiftStruct(arena,1, 2);
unsafelyEscapedOutsideArenaScope = s;
}

try {
unsafelyEscapedOutsideArenaScope.echoIntMethod(1);
fail("Expected exception to be thrown! Object was suposed to be dead.");
} catch (IllegalStateException ex) {
return;
}
}

@Test
Expand Down
26 changes: 16 additions & 10 deletions Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,22 @@ extension ImplicitlyUnwrappedOptionalTypeSyntax {
}
}

extension TypeSyntax {
fileprivate var isActorSystem: Bool {
self.trimmedDescription == "ActorSystem"
extension SyntaxProtocol {

var asNominalTypeKind: NominalTypeKind {
if isClass {
.class
} else if isActor {
.actor
} else if isStruct {
.struct
} else if isEnum {
.enum
} else {
fatalError("Unknown nominal kind: \(self)")
}
}
}

extension DeclSyntaxProtocol {
var isClass: Bool {
return self.is(ClassDeclSyntax.self)
}
Expand All @@ -79,11 +88,8 @@ extension DeclSyntaxProtocol {
extension DeclModifierSyntax {
var isAccessControl: Bool {
switch self.name.tokenKind {
case .keyword(.private): fallthrough
case .keyword(.fileprivate): fallthrough
case .keyword(.internal): fallthrough
case .keyword(.package): fallthrough
case .keyword(.public):
case .keyword(.private), .keyword(.fileprivate), .keyword(.internal), .keyword(.package),
.keyword(.public):
return true
default:
return false
Expand Down
62 changes: 50 additions & 12 deletions Sources/JExtractSwift/ImportedDecls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,19 @@ public struct ImportedNominalType: ImportedDecl {
TranslatedType(
cCompatibleConvention: .direct,
originalSwiftType: "\(raw: swiftTypeName)",
originalSwiftTypeKind: self.kind,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: we'll want to remove this and instead pass around a SwiftNominalTypeDeclaration instead, and base the name and kind off from that.

cCompatibleSwiftType: "UnsafeRawPointer",
cCompatibleJavaMemoryLayout: .heapObject,
javaType: javaType
)
}

public var isReferenceType: Bool {
switch self.kind {
case .class, .actor:
return true
case .enum, .struct:
return false
true
default:
false
}
}

Expand All @@ -68,11 +69,40 @@ public struct ImportedNominalType: ImportedDecl {
}
}

// TODO: replace this with `SwiftNominalTypeDeclaration.Kind`
public enum NominalTypeKind {
case `actor`
case `class`
case `enum`
case `struct`
case `void` // TODO: NOT NOMINAL, BUT...
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll work on removing this NominalTypeKind so we replace it with SwiftNominalTypeDeclaration.Kind but we'll have to do a bit of a dance to pull it off -- perhaps make it optional somehow or enum over "nominal or not nominal" etc

case function // TODO: NOT NOMINAL, BUT...
case primitive // TODO: NOT NOMINAL, BUT...

var isReferenceType: Bool {
switch self {
case .actor, .class: true
case .enum, .struct: false
case .void, .function, .primitive: false
}
}

var isValueType: Bool {
switch self {
case .actor, .class: false
case .enum, .struct: true
case .void, .function, .primitive: false
}
}

var isVoid: Bool {
switch self {
case .actor, .class: false
case .enum, .struct: false
case .void: true
case .function, .primitive: false
}
}
}

public struct ImportedParam {
Expand All @@ -99,7 +129,7 @@ public struct ImportedParam {
var effectiveName: String? {
firstName ?? secondName
}

var effectiveValueName: String {
secondName ?? firstName ?? "_"
}
Expand Down Expand Up @@ -199,13 +229,13 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
case nil, .wrapper:
break

case .pointer:
case .pointer where !isInit:
let selfParam: FunctionParameterSyntax = "self$: $swift_pointer"
params.append(
ImportedParam(syntax: selfParam, type: parent)
)

case .memorySegment:
case .memorySegment where !isInit:
let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment"
var parentForSelf = parent
parentForSelf.javaType = .javaForeignMemorySegment
Expand All @@ -215,6 +245,9 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {

case .swiftThunkSelf:
break

default:
break
}

// TODO: add any metadata for generics and other things we may need to add here
Expand All @@ -233,6 +266,11 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {

public var isInit: Bool = false

public var isIndirectReturn: Bool {
returnType.isValueType ||
(isInit && (parent?.isValueType ?? false))
}

public init(
module: String,
decl: any DeclSyntaxProtocol,
Expand Down Expand Up @@ -269,9 +307,9 @@ extension ImportedFunc: Hashable {
self.swiftDecl.id.hash(into: &hasher)
}

public static func ==(lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool {
lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id &&
lhs.swiftDecl.id == rhs.swiftDecl.id
public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool {
lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id
&& lhs.swiftDecl.id == rhs.swiftDecl.id
}
}

Expand Down Expand Up @@ -394,8 +432,8 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible {
)

case nil,
.wrapper,
.swiftThunkSelf:
.wrapper,
.swiftThunkSelf:
break
}
}
Expand Down
Loading
Loading