Skip to content
Open
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 @@ -230,7 +230,12 @@ extension JNISwift2JavaGenerator {
parameters: [
JavaParameter(name: parameterName, type: .long)
],
conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: type)),
conversion: .pointee(
.asyncTaskCapture(
.extractSwiftValue(.placeholder, swiftType: type),
name: NativeSwiftConversionStep.extractedSwiftValueName(for: parameterName)
)
),
indirectConversion: nil,
conversionCheck: nil
)
Expand Down Expand Up @@ -327,7 +332,10 @@ extension JNISwift2JavaGenerator {
parameters: [
JavaParameter(name: parameterName, type: .long)
],
conversion: .extractMetatypeValue(.placeholder),
conversion: .asyncTaskCapture(
.extractMetatypeValue(.placeholder),
name: NativeSwiftConversionStep.extractedSwiftValueName(for: parameterName)
),
indirectConversion: nil,
conversionCheck: nil
)
Expand Down Expand Up @@ -441,15 +449,18 @@ extension JNISwift2JavaGenerator {
parameters: [
JavaParameter(name: parameterName, type: .javaLangObject)
],
conversion: .interfaceToSwiftObject(
.placeholder,
swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterWrapperClassName(
methodName: methodName,
parameterName: parameterName,
parentName: parentName
conversion: .asyncTaskCapture(
.interfaceToSwiftObject(
.placeholder,
swiftWrapperClassName: JNISwift2JavaGenerator.protocolParameterWrapperClassName(
methodName: methodName,
parameterName: parameterName,
parentName: parentName
),
protocolTypes: protocolTypes,
allowsJavaImplementations: allowsJavaImplementations
),
protocolTypes: protocolTypes,
allowsJavaImplementations: allowsJavaImplementations
name: NativeSwiftConversionStep.swiftObjectName(for: parameterName)
),
indirectConversion: nil,
conversionCheck: nil
Expand Down Expand Up @@ -521,10 +532,13 @@ extension JNISwift2JavaGenerator {
parameters: [JavaParameter(name: parameterName, type: .long)],
conversion: .pointee(
.optionalChain(
.extractSwiftValue(
.placeholder,
swiftType: swiftType,
allowNil: true
.asyncTaskCapture(
.extractSwiftValue(
.placeholder,
swiftType: swiftType,
allowNil: true
),
name: NativeSwiftConversionStep.extractedSwiftValueName(for: parameterName)
)
)
),
Expand Down Expand Up @@ -1219,6 +1233,18 @@ extension JNISwift2JavaGenerator {
/// Destructures a Swift tuple result and writes each element to an out-parameter.
indirect case tupleDestructure(elements: [(index: Int, label: String?, conversion: NativeSwiftConversionStep, outParamName: String, javaType: JavaType)])

/// Marks a temporary value produced by the inner conversion as captured by
/// the async task body.
indirect case asyncTaskCapture(NativeSwiftConversionStep, name: String)

static func extractedSwiftValueName(for name: String) -> String {
"\(name)$"
}

static func swiftObjectName(for name: String) -> String {
"\(name)swiftObject$"
}

/// Promotes the outermost `.getJNIValue` to `.getJNILocalRefValue`.
/// Used for `@_cdecl` return positions to ensure the local ref survives
/// ARC destruction of temporary `JavaObject`s.
Expand All @@ -1231,6 +1257,22 @@ extension JNISwift2JavaGenerator {
}
}

var asyncTaskCaptureNames: [String] {
switch self {
case .asyncTaskCapture(let inner, let name):
return [name] + inner.asyncTaskCaptureNames

case .pointee(let inner), .optionalChain(let inner):
return inner.asyncTaskCaptureNames

case .tupleConstruct(let elements):
return elements.flatMap { $0.conversion.asyncTaskCaptureNames }

default:
return []
}
}

/// Returns the conversion string applied to the placeholder.
func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
// NOTE: 'printer' is used if the conversion wants to cause side-effects.
Expand Down Expand Up @@ -1268,7 +1310,7 @@ extension JNISwift2JavaGenerator {
let allowsJavaImplementations
):
let inner = inner.render(&printer, placeholder)
let variableName = "\(inner)swiftObject$"
let variableName = Self.swiftObjectName(for: inner)
let existentialType = SwiftKitPrinting.renderExistentialType(protocolTypes)
printer.print("let \(variableName): \(existentialType)")

Expand Down Expand Up @@ -1344,7 +1386,7 @@ extension JNISwift2JavaGenerator {

case .extractSwiftValue(let inner, let swiftType, let allowNil, let convertLongFromJNI):
let inner = inner.render(&printer, placeholder)
let pointerName = "\(inner)$"
let pointerName = Self.extractedSwiftValueName(for: inner)
if !allowNil {
printer.print(#"assert(\#(inner) != 0, "\#(inner) memory address was null")"#)
}
Expand All @@ -1367,7 +1409,7 @@ extension JNISwift2JavaGenerator {

case .extractMetatypeValue(let inner):
let inner = inner.render(&printer, placeholder)
let pointerName = "\(inner)$"
let pointerName = Self.extractedSwiftValueName(for: inner)
printer.print(
"""
let \(inner)Bits$ = Int(Int64(fromJNI: \(inner), in: environment))
Expand Down Expand Up @@ -1629,36 +1671,52 @@ extension JNISwift2JavaGenerator {
let completeExceptionallyMethodID
):
var globalRefs: [String] = ["globalFuture"]
var asyncTaskCaptureNames: [String] = []

func appendAsyncTaskCaptureNames(_ names: [String]) {
for name in names where !asyncTaskCaptureNames.contains(name) {
asyncTaskCaptureNames.append(name)
}
}

appendAsyncTaskCaptureNames(nativeFunctionSignature.selfParameter?.conversion.asyncTaskCaptureNames ?? [])
appendAsyncTaskCaptureNames(nativeFunctionSignature.selfTypeParameter?.conversion.asyncTaskCaptureNames ?? [])
for parameter in nativeFunctionSignature.parameters {
appendAsyncTaskCaptureNames(parameter.conversion.asyncTaskCaptureNames)
}

printer.print(
"""
struct _SwiftJavaUncheckedSendable<T>: @unchecked Sendable {
let value: T
}
"""
)

// Global ref all indirect returns
for outParameter in nativeFunctionSignature.result.outParameters {
printer.print(
"nonisolated(unsafe) let \(outParameter.name) = environment.interface.NewGlobalRef(environment, \(outParameter.name))"
"let \(outParameter.name)Sendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, \(outParameter.name)))"
)
globalRefs.append(outParameter.name)
}

// We also need to global ref any objects passed in
for parameter in nativeFunctionSignature.parameters.flatMap(\.parameters) where !parameter.type.isPrimitive {
printer.print("nonisolated(unsafe) let \(parameter.name) = environment.interface.NewGlobalRef(environment, \(parameter.name))")
printer.print(
"let \(parameter.name)Sendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, \(parameter.name)))"
)
globalRefs.append(parameter.name)
}

printer.print(
"""
nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future)
let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future))
"""
)

if let selfParameter = nativeFunctionSignature.selfParameter {
for parameter in selfParameter.parameters {
printer.print("nonisolated(unsafe) let \(parameter.name)Sendable$ = \(parameter.name)$")
}
}
if let selfTypeParameter = nativeFunctionSignature.selfTypeParameter {
for parameter in selfTypeParameter.parameters {
printer.print("nonisolated(unsafe) let \(parameter.name)Sendable$ = \(parameter.name)$")
}
for name in asyncTaskCaptureNames {
printer.print("let \(name)Sendable$ = _SwiftJavaUncheckedSendable(value: \(name))")
}

func printDo(printer: inout CodePrinter) {
Expand Down Expand Up @@ -1703,15 +1761,11 @@ extension JNISwift2JavaGenerator {
}

func printTaskBody(printer: inout CodePrinter) {
if let selfParameter = nativeFunctionSignature.selfParameter {
for parameter in selfParameter.parameters {
printer.print("let \(parameter.name)$ = \(parameter.name)Sendable$")
}
for globalRef in globalRefs {
printer.print("let \(globalRef) = \(globalRef)Sendable$.value")
}
if let selfTypeParameter = nativeFunctionSignature.selfTypeParameter {
for parameter in selfTypeParameter.parameters {
printer.print("let \(parameter.name)$ = \(parameter.name)Sendable$")
}
for name in asyncTaskCaptureNames {
printer.print("let \(name) = \(name)Sendable$.value")
}
printer.printBraceBlock("defer") { printer in
// Defer might on any thread, so we need to attach environment.
Expand Down Expand Up @@ -1839,6 +1893,9 @@ extension JNISwift2JavaGenerator {
}
}
return ""

case .asyncTaskCapture(let inner, _):
return inner.render(&printer, placeholder)
}
}
}
Expand Down
40 changes: 34 additions & 6 deletions Tests/JExtractSwiftTests/JNI/JNIAsyncTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,16 @@ struct JNIAsyncTests {
"""
@_cdecl("Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2")
public func Java_com_example_swift_SwiftModule__00024asyncVoid__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, result_future: jobject?) {
nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future)
struct _SwiftJavaUncheckedSendable<T>: @unchecked Sendable {
let value: T
}
let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future))
var task: Task<Void, Never>? = nil
#if swift(>=6.2)
if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) {
task = Task.immediate {
var environment = try! JavaVirtualMachine.shared().environment()
let globalFuture = globalFutureSendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand All @@ -80,6 +84,7 @@ struct JNIAsyncTests {
if task == nil {
task = Task {
var environment = try! JavaVirtualMachine.shared().environment()
let globalFuture = globalFutureSendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand Down Expand Up @@ -137,12 +142,16 @@ struct JNIAsyncTests {
"""
@_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2")
public func Java_com_example_swift_SwiftModule__00024async__Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, result_future: jobject?) {
nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future)
struct _SwiftJavaUncheckedSendable<T>: @unchecked Sendable {
let value: T
}
let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future))
var task: Task<Void, Never>? = nil
#if swift(>=6.2)
if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) {
task = Task.immediate {
var environment = try! JavaVirtualMachine.shared().environment()
let globalFuture = globalFutureSendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand All @@ -163,6 +172,7 @@ struct JNIAsyncTests {
if task == nil {
task = Task {
var environment = try! JavaVirtualMachine.shared().environment()
let globalFuture = globalFutureSendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand Down Expand Up @@ -227,12 +237,16 @@ struct JNIAsyncTests {
"""
@_cdecl("Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2")
public func Java_com_example_swift_SwiftModule__00024async__JLjava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, i: jlong, result_future: jobject?) {
nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future)
struct _SwiftJavaUncheckedSendable<T>: @unchecked Sendable {
let value: T
}
let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future))
var task: Task<Void, Never>? = nil
#if swift(>=6.2)
if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) {
task = Task.immediate {
var environment = try! JavaVirtualMachine.shared().environment()
let globalFuture = globalFutureSendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand All @@ -247,6 +261,7 @@ struct JNIAsyncTests {
if task == nil {
task = Task {
var environment = try! JavaVirtualMachine.shared().environment()
let globalFuture = globalFutureSendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand Down Expand Up @@ -320,12 +335,18 @@ struct JNIAsyncTests {
guard let c$ else {
fatalError("c memory address was null in call to \\(#function)!")
}
nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future)
struct _SwiftJavaUncheckedSendable<T>: @unchecked Sendable {
let value: T
}
let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future))
let c$Sendable$ = _SwiftJavaUncheckedSendable(value: c$)
var task: Task<Void, Never>? = nil
#if swift(>=6.2)
if #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, *) {
task = Task.immediate {
var environment = try! JavaVirtualMachine.shared().environment()
let globalFuture = globalFutureSendable$.value
let c$ = c$Sendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand All @@ -343,6 +364,8 @@ struct JNIAsyncTests {
if task == nil {
task = Task {
var environment = try! JavaVirtualMachine.shared().environment()
let globalFuture = globalFutureSendable$.value
let c$ = c$Sendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand Down Expand Up @@ -403,9 +426,14 @@ struct JNIAsyncTests {
"""
@_cdecl("Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2")
public func Java_com_example_swift_SwiftModule__00024async__Ljava_lang_String_2Ljava_util_concurrent_CompletableFuture_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, s: jstring?, result_future: jobject?) {
nonisolated(unsafe) let s = environment.interface.NewGlobalRef(environment, s)
nonisolated(unsafe) let globalFuture = environment.interface.NewGlobalRef(environment, result_future)
struct _SwiftJavaUncheckedSendable<T>: @unchecked Sendable {
let value: T
}
let sSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, s))
let globalFutureSendable$ = _SwiftJavaUncheckedSendable(value: environment.interface.NewGlobalRef(environment, result_future))
...
let globalFuture = globalFutureSendable$.value
let s = sSendable$.value
defer {
let deferEnvironment = try! JavaVirtualMachine.shared().environment()
deferEnvironment.interface.DeleteGlobalRef(deferEnvironment, globalFuture)
Expand Down
Loading