From 2e1c8d8b3a982232436ebf9364fb974712ea0dd2 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 3 Dec 2025 12:55:05 +0900 Subject: [PATCH 01/12] samples: move more tests from sample app to test files --- .../com/example/swift/HelloJava2Swift.java | 4 +-- .../com/example/swift/WithBufferTest.java | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java index cecb12311..a13125478 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java @@ -52,9 +52,7 @@ static void examples() { CallTraces.trace("getGlobalBuffer().byteSize()=" + MySwiftLibrary.getGlobalBuffer().byteSize()); - MySwiftLibrary.withBuffer((buf) -> { - CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); - }); + // Example of using an arena; MyClass.deinit is run at end of scope try (var arena = AllocatingSwiftArena.ofConfined()) { MySwiftClass obj = MySwiftClass.init(2222, 7777, arena); diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java new file mode 100644 index 000000000..86069aae3 --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.*; +import org.swift.swiftkit.ffm.*; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.atomic.AtomicLong; + +public class WithBufferTest { + @Test + void test_withBuffer() { + AtomicLong bufferSize = new AtomicLong(); + MySwiftLibrary.withBuffer((buf) -> { + CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + bufferSize.set(buf.byteSize()); + }); + + assertEquals(124, bufferSize.get()); + } +} From 7ebccdd527cc61a34c38b2497972fb38a5a58b31 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 3 Dec 2025 13:47:21 +0900 Subject: [PATCH 02/12] add another test that uses MemorySegment to pass an array copy to Swift (ffm) --- .../MySwiftLibrary/MySwiftLibrary.swift | 18 ++++++++++++++ .../com/example/swift/WithBufferTest.java | 24 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index cdd61c122..9929f888a 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -63,6 +63,24 @@ public func withBuffer(body: (UnsafeRawBufferPointer) -> Void) { body(globalBuffer) } +public func getArray() -> [UInt8] { + return [1, 2, 3] +} + +public func sumAllByteArrayElements(actuallyAnArray: UnsafeRawPointer, count: Int) -> Int { + let bufferPointer = UnsafeRawBufferPointer(start: actuallyAnArray, count: count) + let array = Array(bufferPointer) + return Int(array.reduce(0, { partialResult, element in partialResult + element })) +} + +public func sumAllByteArrayElements(array: [UInt8]) -> Int { + return Int(array.reduce(0, { partialResult, element in partialResult + element })) +} + +public func withArray(body: ([UInt8]) -> Void) { + body([1, 2, 3]) +} + public func globalReceiveSomeDataProtocol(data: some DataProtocol) -> Int { p(Array(data).description) return data.count diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java index 86069aae3..54206423c 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java @@ -20,7 +20,10 @@ import static org.junit.jupiter.api.Assertions.*; +import java.lang.foreign.ValueLayout; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; public class WithBufferTest { @Test @@ -33,4 +36,25 @@ void test_withBuffer() { assertEquals(124, bufferSize.get()); } + + @Test + void test_sumAllByteArrayElements_throughMemorySegment() { + byte[] bytes = new byte[124]; + Arrays.fill(bytes, (byte) 1); + + try (var arena = AllocatingSwiftArena.ofConfined()) { + // NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed segment and therefore cannot pass into native: + // java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 } + // MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!) + // MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length); + + var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes); + var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length); + + System.out.println("swiftSideSum = " + swiftSideSum); + + int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum(); + assertEquals(javaSideSum, swiftSideSum); + } + } } From b7c8b2e35c8d9e3a2b13da9b2c8ae0d6bff0bda1 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 3 Dec 2025 16:43:12 +0900 Subject: [PATCH 03/12] jextract/ffm: support [UInt8] parameters, although it causes copying --- .../r_empty-definition_16-24-26-611.md | 91 ++++++++++++++++++ .metals/metals.lock.db | 6 ++ .metals/metals.mv.db | Bin 0 -> 40960 bytes .../java/com/example/swift/FFMArraysTest.java | 63 ++++++++++++ .../com/example/swift/WithBufferTest.java | 23 +---- .../FFM/CDeclLowering/CRepresentation.swift | 4 +- ...Swift2JavaGenerator+FunctionLowering.swift | 49 ++++++++++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 37 +++++-- ...MSwift2JavaGenerator+JavaTranslation.swift | 62 +++++++++--- .../SwiftTypes/SwiftKnownTypeDecls.swift | 2 +- .../SwiftTypes/SwiftKnownTypes.swift | 1 + 11 files changed, 293 insertions(+), 45 deletions(-) create mode 100644 .metals/.reports/metals-full/2025-12-03/r_empty-definition_16-24-26-611.md create mode 100644 .metals/metals.lock.db create mode 100644 .metals/metals.mv.db create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java diff --git a/.metals/.reports/metals-full/2025-12-03/r_empty-definition_16-24-26-611.md b/.metals/.reports/metals-full/2025-12-03/r_empty-definition_16-24-26-611.md new file mode 100644 index 000000000..32d5488d2 --- /dev/null +++ b/.metals/.reports/metals-full/2025-12-03/r_empty-definition_16-24-26-611.md @@ -0,0 +1,91 @@ +error id: file:///Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java:_empty_/MySwiftLibrary#sumAllByteArrayElements# +file:///Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java +empty definition using pc, found symbol in pc: _empty_/MySwiftLibrary#sumAllByteArrayElements# +empty definition using semanticdb +empty definition using fallback +non-local guesses: + +offset: 2471 +uri: file:///Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java +text: +```scala +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of Swift.org project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +package com.example.swift; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.*; +import org.swift.swiftkit.ffm.*; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.foreign.ValueLayout; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; + +public class WithBufferTest { + @Test + void test_withBuffer() { + AtomicLong bufferSize = new AtomicLong(); + MySwiftLibrary.withBuffer((buf) -> { + CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + bufferSize.set(buf.byteSize()); + }); + + assertEquals(124, bufferSize.get()); + } + + @Test + void test_sumAllByteArrayElements_throughMemorySegment() { + byte[] bytes = new byte[124]; + Arrays.fill(bytes, (byte) 1); + + try (var arena = AllocatingSwiftArena.ofConfined()) { + // NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed segment and therefore cannot pass into native: + // java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 } + // MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!) + // MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length); + + var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes); + var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length); + + System.out.println("swiftSideSum = " + swiftSideSum); + + int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum(); + assertEquals(javaSideSum, swiftSideSum); + } + } + + @Test + void test_sumAllByteArrayElements_arrayCopy() { + byte[] bytes = new byte[124]; + Arrays.fill(bytes, (byte) 1); + + var swiftSideSum = MySwiftLibrary.su@@mAllByteArrayElements(bytes); + + System.out.println("swiftSideSum = " + swiftSideSum); + + int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum(); + assertEquals(javaSideSum, swiftSideSum); + } +} + +``` + + +#### Short summary: + +empty definition using pc, found symbol in pc: _empty_/MySwiftLibrary#sumAllByteArrayElements# \ No newline at end of file diff --git a/.metals/metals.lock.db b/.metals/metals.lock.db new file mode 100644 index 000000000..8b9998f69 --- /dev/null +++ b/.metals/metals.lock.db @@ -0,0 +1,6 @@ +#FileLock +#Wed Dec 03 15:50:01 JST 2025 +hostName=localhost +id=19ae2f9a02b142bf1411bbc8ae7065f1d038919ce32 +method=file +server=localhost\:64724 diff --git a/.metals/metals.mv.db b/.metals/metals.mv.db new file mode 100644 index 0000000000000000000000000000000000000000..4f5defc0bf8ec44745d4bc6a4aa1e8d03fba2024 GIT binary patch literal 40960 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 s>GJy^?=FM8IAmi5hh!@M Self { + .call(step, base: nil, function: function, withArena: withArena) + } - // Apply a method on the placeholder. - // If `withArena` is true, `arena$` argument is added. + /// Apply a method on the placeholder. + /// If `withArena` is true, `arena$` argument is added. indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) + + /// Fetch a property from the placeholder. + /// Similar to 'method', however for a property i.e. without adding the '()' after the name + indirect case property(JavaConversionStep, propertyName: String) - // Call 'new \(Type)(\(placeholder), swiftArena$)'. + /// Call 'new \(Type)(\(placeholder), swiftArena$)'. indirect case constructSwiftValue(JavaConversionStep, JavaType) /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type indirect case wrapMemoryAddressUnsafe(JavaConversionStep, JavaType) - // Construct the type using the placeholder as arguments. + /// Construct the type using the placeholder as arguments. indirect case construct(JavaConversionStep, JavaType) - // Casting the placeholder to the certain type. + /// Casting the placeholder to the certain type. indirect case cast(JavaConversionStep, JavaType) - // Convert the results of the inner steps to a comma separated list. + /// Convert the results of the inner steps to a comma separated list. indirect case commaSeparated([JavaConversionStep]) - // Refer an exploded argument suffixed with `_\(name)`. + /// Refer an exploded argument suffixed with `_\(name)`. indirect case readMemorySegment(JavaConversionStep, as: JavaType) var isPlaceholder: Bool { diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift index 363663217..5401f0fab 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypeDecls.swift @@ -30,8 +30,8 @@ enum SwiftKnownTypeDeclKind: String, Hashable { case float = "Swift.Float" case double = "Swift.Double" case unsafeRawPointer = "Swift.UnsafeRawPointer" - case unsafeMutableRawPointer = "Swift.UnsafeMutableRawPointer" case unsafeRawBufferPointer = "Swift.UnsafeRawBufferPointer" + case unsafeMutableRawPointer = "Swift.UnsafeMutableRawPointer" case unsafeMutableRawBufferPointer = "Swift.UnsafeMutableRawBufferPointer" case unsafePointer = "Swift.UnsafePointer" case unsafeMutablePointer = "Swift.UnsafeMutablePointer" diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index 25b1135ae..9eb05b8be 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -33,6 +33,7 @@ struct SwiftKnownTypes { var float: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.float])) } var double: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.double])) } var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } + var unsafeRawBufferPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawBufferPointer])) } var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } var foundationDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationDataProtocol])) } From 91ff2c53ef1155a2f24a9411814b70528914c6aa Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 3 Dec 2025 16:52:19 +0900 Subject: [PATCH 04/12] add source gen test --- .gitignore | 1 + .../r_empty-definition_16-24-26-611.md | 91 ------------------ .metals/metals.lock.db | 6 -- .metals/metals.mv.db | Bin 40960 -> 0 bytes Tests/JExtractSwiftTests/ByteArrayTests.swift | 83 ++++++++++++++++ .../FFM/FFMSubscriptsTests.swift | 3 - 6 files changed, 84 insertions(+), 100 deletions(-) delete mode 100644 .metals/.reports/metals-full/2025-12-03/r_empty-definition_16-24-26-611.md delete mode 100644 .metals/metals.lock.db delete mode 100644 .metals/metals.mv.db create mode 100644 Tests/JExtractSwiftTests/ByteArrayTests.swift diff --git a/.gitignore b/.gitignore index d4e1c5ec4..02563c8e2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .sdkmanrc .DS_Store +.metals .build .idea .vscode diff --git a/.metals/.reports/metals-full/2025-12-03/r_empty-definition_16-24-26-611.md b/.metals/.reports/metals-full/2025-12-03/r_empty-definition_16-24-26-611.md deleted file mode 100644 index 32d5488d2..000000000 --- a/.metals/.reports/metals-full/2025-12-03/r_empty-definition_16-24-26-611.md +++ /dev/null @@ -1,91 +0,0 @@ -error id: file:///Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java:_empty_/MySwiftLibrary#sumAllByteArrayElements# -file:///Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java -empty definition using pc, found symbol in pc: _empty_/MySwiftLibrary#sumAllByteArrayElements# -empty definition using semanticdb -empty definition using fallback -non-local guesses: - -offset: 2471 -uri: file:///Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java -text: -```scala -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Swift.org project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -package com.example.swift; - -import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.*; -import org.swift.swiftkit.ffm.*; - -import static org.junit.jupiter.api.Assertions.*; - -import java.lang.foreign.ValueLayout; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.IntStream; - -public class WithBufferTest { - @Test - void test_withBuffer() { - AtomicLong bufferSize = new AtomicLong(); - MySwiftLibrary.withBuffer((buf) -> { - CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); - bufferSize.set(buf.byteSize()); - }); - - assertEquals(124, bufferSize.get()); - } - - @Test - void test_sumAllByteArrayElements_throughMemorySegment() { - byte[] bytes = new byte[124]; - Arrays.fill(bytes, (byte) 1); - - try (var arena = AllocatingSwiftArena.ofConfined()) { - // NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed segment and therefore cannot pass into native: - // java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 } - // MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!) - // MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length); - - var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes); - var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length); - - System.out.println("swiftSideSum = " + swiftSideSum); - - int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum(); - assertEquals(javaSideSum, swiftSideSum); - } - } - - @Test - void test_sumAllByteArrayElements_arrayCopy() { - byte[] bytes = new byte[124]; - Arrays.fill(bytes, (byte) 1); - - var swiftSideSum = MySwiftLibrary.su@@mAllByteArrayElements(bytes); - - System.out.println("swiftSideSum = " + swiftSideSum); - - int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum(); - assertEquals(javaSideSum, swiftSideSum); - } -} - -``` - - -#### Short summary: - -empty definition using pc, found symbol in pc: _empty_/MySwiftLibrary#sumAllByteArrayElements# \ No newline at end of file diff --git a/.metals/metals.lock.db b/.metals/metals.lock.db deleted file mode 100644 index 8b9998f69..000000000 --- a/.metals/metals.lock.db +++ /dev/null @@ -1,6 +0,0 @@ -#FileLock -#Wed Dec 03 15:50:01 JST 2025 -hostName=localhost -id=19ae2f9a02b142bf1411bbc8ae7065f1d038919ce32 -method=file -server=localhost\:64724 diff --git a/.metals/metals.mv.db b/.metals/metals.mv.db deleted file mode 100644 index 4f5defc0bf8ec44745d4bc6a4aa1e8d03fba2024..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40960 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 s>GJy^?=FM8IAmi5hh!@M Date: Wed, 3 Dec 2025 20:32:45 +0900 Subject: [PATCH 05/12] jextract/ffm: make sure uint arrays are marked @unsigned --- .../Common/TypeAnnotations.swift | 11 +++++++-- ...Swift2JavaGenerator+FunctionLowering.swift | 24 +++++++++---------- .../JExtractSwiftLib/FFM/ConversionStep.swift | 2 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 2 +- Tests/JExtractSwiftTests/ByteArrayTests.swift | 12 +++++----- .../JExtractSwiftTests/DataImportTests.swift | 4 ++-- .../FuncCallbackImportTests.swift | 4 ++-- .../FunctionLoweringTests.swift | 16 ++++++------- 8 files changed, 41 insertions(+), 34 deletions(-) diff --git a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift index 0896e4be6..2cb8a0a1d 100644 --- a/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift +++ b/Sources/JExtractSwiftLib/Common/TypeAnnotations.swift @@ -18,8 +18,15 @@ import SwiftJavaConfigurationShared /// Determine if the given type needs any extra annotations that should be included /// in Java sources when the corresponding Java type is rendered. func getTypeAnnotations(swiftType: SwiftType, config: Configuration) -> [JavaAnnotation] { - if swiftType.isUnsignedInteger, config.effectiveUnsignedNumbersMode == .annotate { - return [JavaAnnotation.unsigned] + if config.effectiveUnsignedNumbersMode == .annotate { + switch swiftType { + case .array(let wrapped) where wrapped.isUnsignedInteger: + return [JavaAnnotation.unsigned] + case _ where swiftType.isUnsignedInteger: + return [JavaAnnotation.unsigned] + default: + break + } } return [] diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index fdfb3454b..c934edd30 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -208,11 +208,11 @@ struct CdeclLowering { return LoweredParameter( cdeclParameters: [ SwiftParameter( - convention: .byValue, parameterName: "\(parameterName)_pointer", + convention: .byValue, parameterName: "\(parameterName)$pointer", type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer ), SwiftParameter( - convention: .byValue, parameterName: "\(parameterName)_count", + convention: .byValue, parameterName: "\(parameterName)$count", type: knownTypes.int ), ], conversion: .initialize( @@ -237,11 +237,11 @@ struct CdeclLowering { cdeclParameters: [ SwiftParameter( convention: .byValue, - parameterName: "\(parameterName)_pointer", + parameterName: "\(parameterName)$pointer", type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) ), SwiftParameter( - convention: .byValue, parameterName: "\(parameterName)_count", + convention: .byValue, parameterName: "\(parameterName)$count", type: knownTypes.int ) ], @@ -355,8 +355,8 @@ struct CdeclLowering { ] // Create parameter names with consistent naming convention - let pointerParameterName = "\(parameterName)_pointer" - let countParameterName = "\(parameterName)_count" + let pointerParameterName = "\(parameterName)$pointer" + let countParameterName = "\(parameterName)$count" // Build C declaration parameters for pointer and count let cdeclParameters = [ @@ -542,12 +542,12 @@ struct CdeclLowering { cdeclParameters: [ SwiftParameter( convention: .byValue, - parameterName: "\(parameterName)_pointer", + parameterName: "\(parameterName)$pointer", type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) ), SwiftParameter( convention: .byValue, - parameterName: "\(parameterName)_count", + parameterName: "\(parameterName)$count", type: knownTypes.int ), ], @@ -632,24 +632,24 @@ struct CdeclLowering { cdeclOutParameters: [ SwiftParameter( convention: .byValue, - parameterName: "\(outParameterName)_pointer", + parameterName: "\(outParameterName)$pointer", type: knownTypes.unsafeMutablePointer( .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) ) ), SwiftParameter( convention: .byValue, - parameterName: "\(outParameterName)_count", + parameterName: "\(outParameterName)$count", type: knownTypes.unsafeMutablePointer(knownTypes.int) ), ], conversion: .aggregate([ .populatePointer( - name: "\(outParameterName)_pointer", + name: "\(outParameterName)$pointer", to: .member(.placeholder, member: "baseAddress") ), .populatePointer( - name: "\(outParameterName)_count", + name: "\(outParameterName)$count", to: .member(.placeholder, member: "count") ) ], name: outParameterName) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index f59f739dd..15f1dbeb9 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -99,7 +99,7 @@ enum ConversionStep: Equatable { return "\(raw: placeholder)" case .explodedComponent(let step, component: let component): - return step.asExprSyntax(placeholder: "\(placeholder)_\(component)", bodyItems: &bodyItems) + return step.asExprSyntax(placeholder: "\(placeholder)$\(component)", bodyItems: &bodyItems) case .unsafeCastPointer(let step, swiftType: let swiftType): let untypedExpr = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 5042f6c68..6150bafc2 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -479,7 +479,7 @@ extension FFMSwift2JavaGenerator { case .array(let wrapped) where wrapped == knownTypes.uint8: return TranslatedParameter( javaParameters: [ - JavaParameter(name: parameterName, type: .array(.byte)), + JavaParameter(name: parameterName, type: .array(.byte), annotations: parameterAnnotations), ], conversion: .commaSeparated([ diff --git a/Tests/JExtractSwiftTests/ByteArrayTests.swift b/Tests/JExtractSwiftTests/ByteArrayTests.swift index 3d651d16a..f369dfdb2 100644 --- a/Tests/JExtractSwiftTests/ByteArrayTests.swift +++ b/Tests/JExtractSwiftTests/ByteArrayTests.swift @@ -34,13 +34,13 @@ final class ByteArrayTests { """ /** * {@snippet lang=c : - * void swiftjava_SwiftModule_acceptArray_array(const void *array_pointer, ptrdiff_t array_count) + * void swiftjava_SwiftModule_acceptArray_array(const void *array$pointer, ptrdiff_t array$count) * } */ private static class swiftjava_SwiftModule_acceptArray_array { private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* array_pointer: */SwiftValueLayout.SWIFT_POINTER, - /* array_count: */SwiftValueLayout.SWIFT_INT + /* array$pointer: */SwiftValueLayout.SWIFT_POINTER, + /* array$count: */SwiftValueLayout.SWIFT_INT ); """, """ @@ -50,7 +50,7 @@ final class ByteArrayTests { * public func acceptArray(array: [UInt8]) * } */ - public static void acceptArray(byte[] array) { + public static void acceptArray(@Unsigned byte[] array) { try(var arena$ = Arena.ofConfined()) { swiftjava_SwiftModule_acceptArray_array.call(arena$.allocateFrom(ValueLayout.JAVA_BYTE, array), array.length); } @@ -61,8 +61,8 @@ final class ByteArrayTests { [ """ @_cdecl("swiftjava_SwiftModule_acceptArray_array") - public func swiftjava_SwiftModule_acceptArray_array(_ array_pointer: UnsafeRawPointer, _ array_count: Int) { - acceptArray(array: [UInt8](UnsafeRawBufferPointer(start: array_pointer, count: array_count))) + public func swiftjava_SwiftModule_acceptArray_array(_ array$pointer: UnsafeRawPointer, _ array$count: Int) { + acceptArray(array: [UInt8](UnsafeRawBufferPointer(start: array$pointer, count: array$count))) } """ ] diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index bad4174db..38bbfc514 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -345,8 +345,8 @@ final class DataImportTests { void apply(java.lang.foreign.MemorySegment _0); } private static MemorySegment $toUpcallStub(body fi, Arena arena) { - return swiftjava_SwiftModule_Data_withUnsafeBytes__.$body.toUpcallStub((_0_pointer, _0_count) -> { - fi.apply(_0_pointer.reinterpret(_0_count)); + return swiftjava_SwiftModule_Data_withUnsafeBytes__.$body.toUpcallStub((_0$pointer, _0$count) -> { + fi.apply(_0$pointer.reinterpret(_0$count)); }, arena); } } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 79b51c197..4688e208e 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -318,8 +318,8 @@ final class FuncCallbackImportTests { long apply(java.lang.foreign.MemorySegment _0); } private static MemorySegment $toUpcallStub(body fi, Arena arena) { - return swiftjava___FakeModule_withBuffer_body.$body.toUpcallStub((_0_pointer, _0_count) -> { - return fi.apply(_0_pointer.reinterpret(_0_count)); + return swiftjava___FakeModule_withBuffer_body.$body.toUpcallStub((_0$pointer, _0$count) -> { + return fi.apply(_0$pointer.reinterpret(_0$count)); }, arena); } } diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index ba1aad064..336c6bf4a 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -25,11 +25,11 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_f") - public func c_f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) { - f(x: x, y: y, z: UnsafeBufferPointer(start: z_pointer.assumingMemoryBound(to: Bool.self), count: z_count)) + public func c_f(_ x: Int, _ y: Float, _ z$pointer: UnsafeRawPointer, _ z$count: Int) { + f(x: x, y: y, z: UnsafeBufferPointer(start: z$pointer.assumingMemoryBound(to: Bool.self), count: z$count)) } """, - expectedCFunction: "void c_f(ptrdiff_t x, float y, const void *z_pointer, ptrdiff_t z_count)" + expectedCFunction: "void c_f(ptrdiff_t x, float y, const void *z$pointer, ptrdiff_t z$count)" ) } @@ -323,13 +323,13 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_swapRawBufferPointer") - public func c_swapRawBufferPointer(_ buffer_pointer: UnsafeRawPointer?, _ buffer_count: Int, _ _result_pointer: UnsafeMutablePointer, _ _result_count: UnsafeMutablePointer) { - let _result = swapRawBufferPointer(buffer: UnsafeRawBufferPointer(start: buffer_pointer, count: buffer_count)) - _result_pointer.initialize(to: _result.baseAddress) - _result_count.initialize(to: _result.count) + public func c_swapRawBufferPointer(_ buffer$pointer: UnsafeRawPointer?, _ buffer$count: Int, _ _result$pointer: UnsafeMutablePointer, _ _result$count: UnsafeMutablePointer) { + let _result = swapRawBufferPointer(buffer: UnsafeRawBufferPointer(start: buffer$pointer, count: buffer$count)) + _result$pointer.initialize(to: _result.baseAddress) + _result$count.initialize(to: _result.count) } """, - expectedCFunction: "void c_swapRawBufferPointer(const void *buffer_pointer, ptrdiff_t buffer_count, void **_result_pointer, ptrdiff_t *_result_count)" + expectedCFunction: "void c_swapRawBufferPointer(const void *buffer$pointer, ptrdiff_t buffer$count, void **_result$pointer, ptrdiff_t *_result$count)" ) } From e44364aa9557e0fcacbf0a67561c1bcd6fc717d0 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 3 Dec 2025 21:05:14 +0900 Subject: [PATCH 06/12] undo the _ -> $ change, we'll do it separately --- ...Swift2JavaGenerator+FunctionLowering.swift | 35 ++++------ .../JExtractSwiftLib/FFM/ConversionStep.swift | 2 +- Tests/JExtractSwiftTests/ByteArrayTests.swift | 67 ++++++++++++++++--- .../JExtractSwiftTests/DataImportTests.swift | 4 +- .../FuncCallbackImportTests.swift | 4 +- .../FunctionLoweringTests.swift | 16 ++--- 6 files changed, 82 insertions(+), 46 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index c934edd30..2c5f1085d 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -208,11 +208,11 @@ struct CdeclLowering { return LoweredParameter( cdeclParameters: [ SwiftParameter( - convention: .byValue, parameterName: "\(parameterName)$pointer", + convention: .byValue, parameterName: "\(parameterName)_pointer", type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer ), SwiftParameter( - convention: .byValue, parameterName: "\(parameterName)$count", + convention: .byValue, parameterName: "\(parameterName)_count", type: knownTypes.int ), ], conversion: .initialize( @@ -237,11 +237,11 @@ struct CdeclLowering { cdeclParameters: [ SwiftParameter( convention: .byValue, - parameterName: "\(parameterName)$pointer", + parameterName: "\(parameterName)_pointer", type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) ), SwiftParameter( - convention: .byValue, parameterName: "\(parameterName)$count", + convention: .byValue, parameterName: "\(parameterName)_count", type: knownTypes.int ) ], @@ -348,26 +348,17 @@ struct CdeclLowering { case .composite: throw LoweringError.unhandledType(type) - case .array(.nominal(let nominal)): + case .array(let wrapped) where wrapped == knownTypes.uint8: // Lower an array as 'address' raw pointer and 'count' integer - var parameters: [SwiftParameter] = [ - - ] - - // Create parameter names with consistent naming convention - let pointerParameterName = "\(parameterName)$pointer" - let countParameterName = "\(parameterName)$count" - - // Build C declaration parameters for pointer and count let cdeclParameters = [ SwiftParameter( convention: .byValue, - parameterName: pointerParameterName, + parameterName: "\(parameterName)_pointer", type: knownTypes.unsafeRawPointer ), SwiftParameter( convention: .byValue, - parameterName: countParameterName, + parameterName: "\(parameterName)_count", type: knownTypes.int ), ] @@ -542,12 +533,12 @@ struct CdeclLowering { cdeclParameters: [ SwiftParameter( convention: .byValue, - parameterName: "\(parameterName)$pointer", + parameterName: "\(parameterName)_pointer", type: .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) ), SwiftParameter( convention: .byValue, - parameterName: "\(parameterName)$count", + parameterName: "\(parameterName)_count", type: knownTypes.int ), ], @@ -632,24 +623,24 @@ struct CdeclLowering { cdeclOutParameters: [ SwiftParameter( convention: .byValue, - parameterName: "\(outParameterName)$pointer", + parameterName: "\(outParameterName)_pointer", type: knownTypes.unsafeMutablePointer( .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) ) ), SwiftParameter( convention: .byValue, - parameterName: "\(outParameterName)$count", + parameterName: "\(outParameterName)_count", type: knownTypes.unsafeMutablePointer(knownTypes.int) ), ], conversion: .aggregate([ .populatePointer( - name: "\(outParameterName)$pointer", + name: "\(outParameterName)_pointer", to: .member(.placeholder, member: "baseAddress") ), .populatePointer( - name: "\(outParameterName)$count", + name: "\(outParameterName)_count", to: .member(.placeholder, member: "count") ) ], name: outParameterName) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index 15f1dbeb9..f59f739dd 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -99,7 +99,7 @@ enum ConversionStep: Equatable { return "\(raw: placeholder)" case .explodedComponent(let step, component: let component): - return step.asExprSyntax(placeholder: "\(placeholder)$\(component)", bodyItems: &bodyItems) + return step.asExprSyntax(placeholder: "\(placeholder)_\(component)", bodyItems: &bodyItems) case .unsafeCastPointer(let step, swiftType: let swiftType): let untypedExpr = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) diff --git a/Tests/JExtractSwiftTests/ByteArrayTests.swift b/Tests/JExtractSwiftTests/ByteArrayTests.swift index f369dfdb2..5b7bd01e8 100644 --- a/Tests/JExtractSwiftTests/ByteArrayTests.swift +++ b/Tests/JExtractSwiftTests/ByteArrayTests.swift @@ -17,11 +17,6 @@ import SwiftJavaConfigurationShared import Testing final class ByteArrayTests { - let text = - """ - public func acceptArray(array: [UInt8]) - """ - @Test( "Import: accept [UInt8] array", @@ -34,13 +29,13 @@ final class ByteArrayTests { """ /** * {@snippet lang=c : - * void swiftjava_SwiftModule_acceptArray_array(const void *array$pointer, ptrdiff_t array$count) + * void swiftjava_SwiftModule_acceptArray_array(const void *array_pointer, ptrdiff_t array_count) * } */ private static class swiftjava_SwiftModule_acceptArray_array { private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* array$pointer: */SwiftValueLayout.SWIFT_POINTER, - /* array$count: */SwiftValueLayout.SWIFT_INT + /* array_pointer: */SwiftValueLayout.SWIFT_POINTER, + /* array_count: */SwiftValueLayout.SWIFT_INT ); """, """ @@ -61,8 +56,8 @@ final class ByteArrayTests { [ """ @_cdecl("swiftjava_SwiftModule_acceptArray_array") - public func swiftjava_SwiftModule_acceptArray_array(_ array$pointer: UnsafeRawPointer, _ array$count: Int) { - acceptArray(array: [UInt8](UnsafeRawBufferPointer(start: array$pointer, count: array$count))) + public func swiftjava_SwiftModule_acceptArray_array(_ array_pointer: UnsafeRawPointer, _ array_count: Int) { + acceptArray(array: [UInt8](UnsafeRawBufferPointer(start: array_pointer, count: array_count))) } """ ] @@ -70,6 +65,11 @@ final class ByteArrayTests { ] ) func func_accept_array_uint8(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws { + let text = + """ + public func acceptArray(array: [UInt8]) + """ + try assertOutput( input: text, mode, .java, @@ -79,5 +79,50 @@ final class ByteArrayTests { input: text, mode, .swift, expectedChunks: expectedSwiftChunks) - } + } + + // @Test( + // "Import: return [UInt8] array", + // arguments: [ + // // TODO: implement JNI mode here + // ( + // JExtractGenerationMode.ffm, + // /* expected Java chunks */ + // [ + // """ + // NEIN + // NEIN + // NEIN + // NEIN + // """ + // ], + // /* expected Swift chunks */ + // [ + // """ + // NEIN + // NEIN + // NEIN + // NEIN + // NEIN + // """ + // ] + // ) + // ] + // ) + // func func_return_array_uint8(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws { + // let text = + // """ + // public func acceptArray() -> [UInt8] + // """ + + // try assertOutput( + // input: text, + // mode, .java, + // expectedChunks: expectedJavaChunks) + + // try assertOutput( + // input: text, + // mode, .swift, + // expectedChunks: expectedSwiftChunks) + // } } \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index 38bbfc514..bad4174db 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -345,8 +345,8 @@ final class DataImportTests { void apply(java.lang.foreign.MemorySegment _0); } private static MemorySegment $toUpcallStub(body fi, Arena arena) { - return swiftjava_SwiftModule_Data_withUnsafeBytes__.$body.toUpcallStub((_0$pointer, _0$count) -> { - fi.apply(_0$pointer.reinterpret(_0$count)); + return swiftjava_SwiftModule_Data_withUnsafeBytes__.$body.toUpcallStub((_0_pointer, _0_count) -> { + fi.apply(_0_pointer.reinterpret(_0_count)); }, arena); } } diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift index 4688e208e..79b51c197 100644 --- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift +++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift @@ -318,8 +318,8 @@ final class FuncCallbackImportTests { long apply(java.lang.foreign.MemorySegment _0); } private static MemorySegment $toUpcallStub(body fi, Arena arena) { - return swiftjava___FakeModule_withBuffer_body.$body.toUpcallStub((_0$pointer, _0$count) -> { - return fi.apply(_0$pointer.reinterpret(_0$count)); + return swiftjava___FakeModule_withBuffer_body.$body.toUpcallStub((_0_pointer, _0_count) -> { + return fi.apply(_0_pointer.reinterpret(_0_count)); }, arena); } } diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 336c6bf4a..ba1aad064 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -25,11 +25,11 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_f") - public func c_f(_ x: Int, _ y: Float, _ z$pointer: UnsafeRawPointer, _ z$count: Int) { - f(x: x, y: y, z: UnsafeBufferPointer(start: z$pointer.assumingMemoryBound(to: Bool.self), count: z$count)) + public func c_f(_ x: Int, _ y: Float, _ z_pointer: UnsafeRawPointer, _ z_count: Int) { + f(x: x, y: y, z: UnsafeBufferPointer(start: z_pointer.assumingMemoryBound(to: Bool.self), count: z_count)) } """, - expectedCFunction: "void c_f(ptrdiff_t x, float y, const void *z$pointer, ptrdiff_t z$count)" + expectedCFunction: "void c_f(ptrdiff_t x, float y, const void *z_pointer, ptrdiff_t z_count)" ) } @@ -323,13 +323,13 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_swapRawBufferPointer") - public func c_swapRawBufferPointer(_ buffer$pointer: UnsafeRawPointer?, _ buffer$count: Int, _ _result$pointer: UnsafeMutablePointer, _ _result$count: UnsafeMutablePointer) { - let _result = swapRawBufferPointer(buffer: UnsafeRawBufferPointer(start: buffer$pointer, count: buffer$count)) - _result$pointer.initialize(to: _result.baseAddress) - _result$count.initialize(to: _result.count) + public func c_swapRawBufferPointer(_ buffer_pointer: UnsafeRawPointer?, _ buffer_count: Int, _ _result_pointer: UnsafeMutablePointer, _ _result_count: UnsafeMutablePointer) { + let _result = swapRawBufferPointer(buffer: UnsafeRawBufferPointer(start: buffer_pointer, count: buffer_count)) + _result_pointer.initialize(to: _result.baseAddress) + _result_count.initialize(to: _result.count) } """, - expectedCFunction: "void c_swapRawBufferPointer(const void *buffer$pointer, ptrdiff_t buffer$count, void **_result$pointer, ptrdiff_t *_result$count)" + expectedCFunction: "void c_swapRawBufferPointer(const void *buffer_pointer, ptrdiff_t buffer_count, void **_result_pointer, ptrdiff_t *_result_count)" ) } From 67154d5c7f08914c723afd0249f0eb5513eba85c Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 4 Dec 2025 21:06:28 +0900 Subject: [PATCH 07/12] snapshot, new version of swift side for returning array --- .../MySwiftLibrary/MySwiftLibrary.swift | 10 ++ .../com/example/swift/WithBufferTest.java | 19 +++ ...Swift2JavaGenerator+FunctionLowering.swift | 69 ++++++-- .../JExtractSwiftLib/FFM/ConversionStep.swift | 37 +++++ ...MSwift2JavaGenerator+JavaTranslation.swift | 27 ++++ .../SwiftTypes/SwiftKnownTypes.swift | 15 +- Tests/JExtractSwiftTests/ByteArrayTests.swift | 147 +++++++++++++----- 7 files changed, 266 insertions(+), 58 deletions(-) diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index 9929f888a..cfd6899c1 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -76,6 +76,16 @@ public func sumAllByteArrayElements(actuallyAnArray: UnsafeRawPointer, count: In public func sumAllByteArrayElements(array: [UInt8]) -> Int { return Int(array.reduce(0, { partialResult, element in partialResult + element })) } +public func returnSwiftArray() -> [UInt8] { + return [1, 2, 3, 4] +} + +// public func swiftjava_MySwiftLibrary_getArray(_ _result_initialize: (UnsafeMutablePointer, UnsafeMutablePointer) -> ()) { +// let _result = returnSwiftArray() +// _result.withUnsafeBufferPointer { buf in +// _result_initialize(buf.baseAddress, buf.count) +// } +// } public func withArray(body: ([UInt8]) -> Void) { body([1, 2, 3]) diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java index 11a7e3206..d4f740700 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java @@ -20,12 +20,31 @@ import static org.junit.jupiter.api.Assertions.*; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; public class WithBufferTest { + + public static byte[] returnArray() { + try (var arena$ = Arena.ofConfined()) { + MemorySegment _result_pointer = arena$.allocate(SwiftValueLayout.SWIFT_POINTER); + MemorySegment _result_count = arena$.allocate(SwiftValueLayout.SWIFT_INT64); + // swiftjava_SwiftModule_returnArray.call(_result_pointer, _result_count); +// return _result_pointer +// .get(SwiftValueLayout.SWIFT_POINTER, 0) +// .reinterpret(_result_count.get(SwiftValueLayout.SWIFT_INT64, 0)); + MemorySegment memorySegment = _result_pointer + .get(SwiftValueLayout.SWIFT_POINTER, 0); + long newSize = _result_count.get(SwiftValueLayout.SWIFT_INT64, 0); + MemorySegment arraySegment = memorySegment.reinterpret(newSize); + return arraySegment.toArray(ValueLayout.JAVA_BYTE); + } + } + @Test void test_withBuffer() { AtomicLong bufferSize = new AtomicLong(); diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 2c5f1085d..fc4ab8f01 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -363,7 +363,6 @@ struct CdeclLowering { ), ] - // Initialize a UnsafeRawBufferPointer using the 'address' and 'count' let bufferPointerInit = ConversionStep.initialize( knownTypes.unsafeRawBufferPointer, arguments: [ @@ -565,6 +564,24 @@ struct CdeclLowering { } } + /// Create "out" parameter names when we're returning an array-like result. + fileprivate func makeBufferIndirectReturnParameters(_ outParameterName: String, isMutable: Bool) -> [SwiftParameter] { + [ + SwiftParameter( + convention: .byValue, + parameterName: "\(outParameterName)_pointer", + type: knownTypes.unsafeMutablePointer( + .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) + ) + ), + SwiftParameter( + convention: .byValue, + parameterName: "\(outParameterName)_count", + type: knownTypes.unsafeMutablePointer(knownTypes.int) + ), + ] + } + /// Lower a Swift result type to cdecl out parameters and return type. /// /// - Parameters: @@ -620,20 +637,7 @@ struct CdeclLowering { let isMutable = knownType == .unsafeMutableRawBufferPointer return LoweredResult( cdeclResultType: .void, - cdeclOutParameters: [ - SwiftParameter( - convention: .byValue, - parameterName: "\(outParameterName)_pointer", - type: knownTypes.unsafeMutablePointer( - .optional(isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer) - ) - ), - SwiftParameter( - convention: .byValue, - parameterName: "\(outParameterName)_count", - type: knownTypes.unsafeMutablePointer(knownTypes.int) - ), - ], + cdeclOutParameters: makeBufferIndirectReturnParameters(outParameterName, isMutable: isMutable), conversion: .aggregate([ .populatePointer( name: "\(outParameterName)_pointer", @@ -712,6 +716,41 @@ struct CdeclLowering { cdeclOutParameters: parameters, conversion: .tupleExplode(conversions, name: outParameterName) ) + + case .array(let wrapped) where wrapped == knownTypes.uint8: + let resultName = "_result" + + return LoweredResult( + cdeclResultType: .void, // we call into the _result_initialize instead + cdeclOutParameters: [ + SwiftParameter( + convention: .byValue, + parameterName: "\(outParameterName)_initialize", + type: knownTypes.functionInitializeByteBuffer + ) + ], + conversion: .aggregate([ + .method(base: resultName, methodName: "withUnsafeBufferPointer", arguments: [ + .init(argument: + .closureLowering( + parameters: [.placeholder], + result: .method( + base: "\(outParameterName)_initialize", + methodName: nil, // just `(...)` apply the closure + arguments: [ + .init(label: nil, argument: .member(.constant("_0"), member: "baseAddress")), + .init(label: nil, argument: .member(.constant("_0"), member: "count")), + ] + // arguments: [ + // .init(label: nil, argument: .member(.placeholder, member: "baseAddress")), + // .init(label: nil, argument: .member(.placeholder, member: "count")), + // ] + ) + ) + ) + ]) + ], name: resultName) + ) case .genericParameter, .function, .optional, .existential, .opaque, .composite, .array: throw LoweringError.unhandledType(type) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index f59f739dd..5d6ee9b6c 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -21,6 +21,9 @@ import SwiftSyntaxBuilder enum ConversionStep: Equatable { /// The value being lowered. case placeholder + + /// FIXME: Workaround for picking a specific placeholder value; We should resolve how method() works with lowered closures instead + case constant(String) /// A reference to a component in a value that has been exploded, such as /// a tuple element or part of a buffer pointer. @@ -60,8 +63,12 @@ enum ConversionStep: Equatable { indirect case closureLowering(parameters: [ConversionStep], result: ConversionStep) + /// Access a member of the target, e.g. `.member` indirect case member(ConversionStep, member: String) + /// Call a method with provided parameters. + indirect case method(base: String?, methodName: String?, arguments: [LabeledArgument]) + indirect case optionalChain(ConversionStep) /// Count the number of times that the placeholder occurs within this @@ -77,8 +84,12 @@ enum ConversionStep: Equatable { inner.placeholderCount case .initialize(_, arguments: let arguments): arguments.reduce(0) { $0 + $1.argument.placeholderCount } + case .method(_, _, let arguments): + arguments.reduce(0) { $0 + $1.argument.placeholderCount } case .placeholder, .tupleExplode, .closureLowering: 1 + case .constant: + 0 case .tuplify(let elements), .aggregate(let elements, _): elements.reduce(0) { $0 + $1.placeholderCount } } @@ -98,6 +109,9 @@ enum ConversionStep: Equatable { case .placeholder: return "\(raw: placeholder)" + case .constant(let name): + return "\(raw: name)" + case .explodedComponent(let step, component: let component): return step.asExprSyntax(placeholder: "\(placeholder)_\(component)", bodyItems: &bodyItems) @@ -162,6 +176,29 @@ enum ConversionStep: Equatable { let inner = step.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) return "\(inner).\(raw: member)" + case .method(let base, let methodName, let arguments): + // TODO: this is duplicated, try to dedupe it a bit + let renderedArguments: [String] = arguments.map { labeledArgument in + let argExpr = labeledArgument.argument.asExprSyntax(placeholder: placeholder, bodyItems: &bodyItems) + return LabeledExprSyntax(label: labeledArgument.label, expression: argExpr!).description + } + + // FIXME: Should be able to use structured initializers here instead of splatting out text. + let renderedArgumentList = renderedArguments.joined(separator: ", ") + + let methodApply: String = + if let methodName { + ".\(methodName)" + } else { + "" // this is equivalent to calling `base(...)` + } + + if let base { + return "\(raw: base)\(raw: methodApply)(\(raw: renderedArgumentList))" + } else { + return "\(raw: methodApply)(\(raw: renderedArgumentList))" + } + case .aggregate(let steps, let name): let toExplode: String if let name { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 6150bafc2..61918ff0e 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -714,6 +714,33 @@ extension FFMSwift2JavaGenerator { // TODO: Implement. throw JavaTranslationError.unhandledType(swiftType) + case .array(let wrapped) where wrapped == knownTypes.uint8: + return TranslatedResult( + javaResultType: + .array(.byte), + annotations: [.unsigned], + outParameters: [ + JavaParameter(name: "pointer", type: .javaForeignMemorySegment), + JavaParameter(name: "count", type: .long), + ], + conversion: + .method( + .method( + .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), + methodName: "reinterpret", + arguments: [ + .readMemorySegment(.explodedName(component: "count"), as: .long) + ], + withArena: false + ), + methodName: "toArray", + arguments: [ + .constant("ValueLayout.JAVA_BYTE") + ], + withArena: false + ) + ) + case .genericParameter, .optional, .function, .existential, .opaque, .composite, .array: throw JavaTranslationError.unhandledType(swiftType) } diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift index 9eb05b8be..56c6db464 100644 --- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift +++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift @@ -35,11 +35,24 @@ struct SwiftKnownTypes { var unsafeRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawPointer])) } var unsafeRawBufferPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeRawBufferPointer])) } var unsafeMutableRawPointer: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.unsafeMutableRawPointer])) } - + var foundationDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationDataProtocol])) } var foundationData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.foundationData])) } var essentialsDataProtocol: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsDataProtocol])) } var essentialsData: SwiftType { .nominal(SwiftNominalType(nominalTypeDecl: symbolTable[.essentialsData])) } + + /// `(UnsafeRawPointer, Long) -> ()` function type. + /// + /// Commonly used to initialize a buffer using the passed bytes and length. + var functionInitializeByteBuffer: SwiftType { + .function(SwiftFunctionType( + convention: .c, + parameters: [ + SwiftParameter(convention: .byValue, parameterName: nil, type: self.unsafeRawPointer), // array base pointer + SwiftParameter(convention: .byValue, parameterName: nil, type: self.int), // array length + ], + resultType: .void)) + } func unsafePointer(_ pointeeType: SwiftType) -> SwiftType { .nominal( diff --git a/Tests/JExtractSwiftTests/ByteArrayTests.swift b/Tests/JExtractSwiftTests/ByteArrayTests.swift index 5b7bd01e8..6a34a9e4a 100644 --- a/Tests/JExtractSwiftTests/ByteArrayTests.swift +++ b/Tests/JExtractSwiftTests/ByteArrayTests.swift @@ -81,48 +81,111 @@ final class ByteArrayTests { expectedChunks: expectedSwiftChunks) } - // @Test( - // "Import: return [UInt8] array", - // arguments: [ - // // TODO: implement JNI mode here - // ( - // JExtractGenerationMode.ffm, - // /* expected Java chunks */ - // [ - // """ - // NEIN - // NEIN - // NEIN - // NEIN - // """ - // ], - // /* expected Swift chunks */ - // [ - // """ - // NEIN - // NEIN - // NEIN - // NEIN - // NEIN - // """ - // ] - // ) - // ] - // ) - // func func_return_array_uint8(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws { - // let text = - // """ - // public func acceptArray() -> [UInt8] - // """ + @Test( + "Import: return [UInt8] array", + arguments: [ + // TODO: implement JNI mode here + ( + JExtractGenerationMode.ffm, + /* expected Java chunks */ + [ + // """ + // /** + // * {snippet lang=c : + // * void (*)(void) + // * } + // */ + // private static class $callback { + // @FunctionalInterface + // public interface Function { + // void apply(); + // } + // private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); + // private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); + // private static MemorySegment toUpcallStub(Function fi, Arena arena) { + // return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + // } + // } + // """, + // """ + // public static class _result_initialize { + // @FunctionalInterface + // public interface callback extends swiftjava___FakeModule_callMe_callback.$callback.Function {} + // private static MemorySegment $toUpcallStub(callback fi, Arena arena) { + // return swiftjava___FakeModule_callMe_callback.$callback.toUpcallStub(fi, arena); + // } + // } + // """, + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func returnArray() -> [UInt8] + * } + */ + @Unsigned + public static byte[] returnArray() { + try(var arena$ = Arena.ofConfined()) { + _result_initialize callback = (buf, count) -> { - // try assertOutput( - // input: text, - // mode, .java, - // expectedChunks: expectedJavaChunks) + }; + swiftjava___FakeModule_returnArray.call(_result_initialize.$toUpcallStub(callback, arena$)); + swiftjava_SwiftModule_returnArray.call(_result_pointer, _result_count); + } + } + """ + ], + // [ + // """ + // /** + // * Downcall to Swift: + // * {@snippet lang=swift : + // * public func returnArray() -> [UInt8] + // * } + // */ + // @Unsigned + // public static byte[] returnArray() { + // try(var arena$ = Arena.ofConfined()) { + // MemorySegment _result_pointer = arena$.allocate(SwiftValueLayout.SWIFT_POINTER); + // MemorySegment _result_count = arena$.allocate(SwiftValueLayout.SWIFT_INT64); + // swiftjava_SwiftModule_returnArray.call(_result_pointer, _result_count); + // return _result_pointer.get(SwiftValueLayout.SWIFT_POINTER, 0).reinterpret(_result_count.get(SwiftValueLayout.SWIFT_INT64, 0)).toArray(ValueLayout.JAVA_BYTE); + // } + // } + // """ + // ], + /* expected Swift chunks */ + [ + """ + @_cdecl("swiftjava_SwiftModule_returnArray") + public func swiftjava_SwiftModule_returnArray(_ _result_initialize: @convention(c) (UnsafeRawPointer, Int) -> ()) { + let _result = returnArray() + _result.withUnsafeBufferPointer({ (_0) in + return _result_initialize(_0.baseAddress, _0.count) + }) + } + """ + ] + ) + ] + ) + func func_return_array_uint8(mode: JExtractGenerationMode, expectedJavaChunks: [String], expectedSwiftChunks: [String]) throws { + let text = + """ + public func returnArray() -> [UInt8] + """ + + var config = Configuration() + config.logLevel = .trace + + try assertOutput( + input: text, + mode, .java, + expectedChunks: expectedJavaChunks) - // try assertOutput( - // input: text, - // mode, .swift, - // expectedChunks: expectedSwiftChunks) - // } + try assertOutput( + input: text, + mode, .swift, + expectedChunks: expectedSwiftChunks) + } } \ No newline at end of file From 6716f4ee1385de467d076816d9676f4077d2b83d Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 5 Dec 2025 17:54:56 +0900 Subject: [PATCH 08/12] almost getting the upcall passed --- .../com/example/swift/WithBufferTest.java | 86 +++++++++---- Samples/untitled/.gitignore | 32 +++++ Samples/untitled/src/Main.kt | 14 +++ Samples/untitled/untitled.iml | 15 +++ .../Convenience/String+Extensions.swift | 10 ++ ...t2JavaGenerator+JavaBindingsPrinting.swift | 115 +++++++++++++++--- ...MSwift2JavaGenerator+JavaTranslation.swift | 75 ++++++++---- Sources/JExtractSwiftLib/JavaParameter.swift | 16 +++ .../Asserts/TextAssertions.swift | 7 ++ Tests/JExtractSwiftTests/ByteArrayTests.swift | 87 +++++++------ 10 files changed, 346 insertions(+), 111 deletions(-) create mode 100644 Samples/untitled/.gitignore create mode 100644 Samples/untitled/src/Main.kt create mode 100644 Samples/untitled/untitled.iml diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java index d4f740700..fdbdf2cb2 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java @@ -20,39 +20,75 @@ import static org.junit.jupiter.api.Assertions.*; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; public class WithBufferTest { - public static byte[] returnArray() { - try (var arena$ = Arena.ofConfined()) { - MemorySegment _result_pointer = arena$.allocate(SwiftValueLayout.SWIFT_POINTER); - MemorySegment _result_count = arena$.allocate(SwiftValueLayout.SWIFT_INT64); - // swiftjava_SwiftModule_returnArray.call(_result_pointer, _result_count); -// return _result_pointer -// .get(SwiftValueLayout.SWIFT_POINTER, 0) -// .reinterpret(_result_count.get(SwiftValueLayout.SWIFT_INT64, 0)); - MemorySegment memorySegment = _result_pointer - .get(SwiftValueLayout.SWIFT_POINTER, 0); - long newSize = _result_count.get(SwiftValueLayout.SWIFT_INT64, 0); - MemorySegment arraySegment = memorySegment.reinterpret(newSize); - return arraySegment.toArray(ValueLayout.JAVA_BYTE); + /** + * {@snippet lang = c: + * void swiftjava_SwiftModule_returnArray(void (*_result_initialize)(const void *, ptrdiff_t)) + *} + */ + private static class swiftjava_SwiftModule_returnArray { + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _result_initialize: */SwiftValueLayout.SWIFT_POINTER + ); + private static final MemorySegment ADDR = null; + // SwiftModule.findOrThrow("swiftjava_SwiftModule_returnArray"); + private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + + public static void call(java.lang.foreign.MemorySegment _result_initialize) { + try { + if (CallTraces.TRACE_DOWNCALLS) { + CallTraces.traceDowncall(_result_initialize); + } + HANDLE.invokeExact(_result_initialize); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } } - } - @Test - void test_withBuffer() { - AtomicLong bufferSize = new AtomicLong(); - MySwiftLibrary.withBuffer((buf) -> { - CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); - bufferSize.set(buf.byteSize()); - }); + /** + * {snippet lang=c : + * void (*)(const void *, ptrdiff_t) + * } + */ + private static class $_result_initialize { + public static final class Function { + byte[] result = null; + + void apply(java.lang.foreign.MemorySegment _0, long _1) { + this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); + } + } - assertEquals(124, bufferSize.get()); + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _0: */SwiftValueLayout.SWIFT_POINTER, + /* _1: */SwiftValueLayout.SWIFT_INT + ); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); + + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } } + + +} + +@Test +void test_withBuffer() { + AtomicLong bufferSize = new AtomicLong(); + MySwiftLibrary.withBuffer((buf) -> { + CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + bufferSize.set(buf.byteSize()); + }); + + assertEquals(124, bufferSize.get()); +} } diff --git a/Samples/untitled/.gitignore b/Samples/untitled/.gitignore new file mode 100644 index 000000000..3ddbf4c44 --- /dev/null +++ b/Samples/untitled/.gitignore @@ -0,0 +1,32 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Kotlin ### +.kotlin + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/Samples/untitled/src/Main.kt b/Samples/untitled/src/Main.kt new file mode 100644 index 000000000..72654654a --- /dev/null +++ b/Samples/untitled/src/Main.kt @@ -0,0 +1,14 @@ +//TIP To Run code, press or +// click the icon in the gutter. +fun main() { + val name = "Kotlin" + //TIP Press with your caret at the highlighted text + // to see how IntelliJ IDEA suggests fixing it. + println("Hello, " + name + "!") + + for (i in 1..5) { + //TIP Press to start debugging your code. We have set one breakpoint + // for you, but you can always add more by pressing . + println("i = $i") + } +} \ No newline at end of file diff --git a/Samples/untitled/untitled.iml b/Samples/untitled/untitled.iml new file mode 100644 index 000000000..43dd653dc --- /dev/null +++ b/Samples/untitled/untitled.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift index 5641c7f23..87578c142 100644 --- a/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift +++ b/Sources/JExtractSwiftLib/Convenience/String+Extensions.swift @@ -79,3 +79,13 @@ extension String { return .class(package: javaPackageName, name: javaClassName) } } + +extension Array where Element == String { + func joinedJavaStatements(indent: Int) -> String { + if self.count == 1 { + return "\(self.first!);" + } + let indentation = String(repeating: " ", count: indent) + return self.joined(separator: ";\n\(indentation)") + } +} diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 1bc9987ca..7e08addd2 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -63,7 +63,11 @@ extension FFMSwift2JavaGenerator { """ ) printJavaBindingDowncallMethod(&printer, cFunc) - printParameterDescriptorClasses(&printer, cFunc) + if let outCallback = translated.translatedSignature.result.outCallback { + printUpcallParameterDescriptorClasses(&printer, outCallback) + } else { // FIXME: not an "else" + printParameterDescriptorClasses(&printer, cFunc) + } } } @@ -147,11 +151,19 @@ extension FFMSwift2JavaGenerator { switch param.type { case .pointer(.function): let name = "$\(param.name!)" - printFunctionPointerParameterDescriptorClass(&printer, name, param.type) + printFunctionPointerParameterDescriptorClass(&printer, name, param.type, impl: nil) default: continue } } + } + + func printUpcallParameterDescriptorClasses( + _ printer: inout CodePrinter, + _ outCallback: OutCallback + ) { + let name = outCallback.name + printFunctionPointerParameterDescriptorClass(&printer, name, outCallback.cFunc.functionType, impl: outCallback) } /// Print a class describing a function pointer parameter type. @@ -169,13 +181,25 @@ extension FFMSwift2JavaGenerator { /// } /// } /// ``` + /// + /// If a `functionBody` is provided the `Function` becomes a `final static class`, + /// with the specified implementation as the implementation of the `apply` method. func printFunctionPointerParameterDescriptorClass( _ printer: inout CodePrinter, _ name: String, - _ cType: CType + _ cType: CType, + impl: OutCallback? ) { - guard case .pointer(.function(let cResultType, let cParameterTypes, variadic: false)) = cType else { - preconditionFailure("must be a C function pointer type; name=\(name), cType=\(cType)") + let cResultType: CType + let cParameterTypes: [CType] + if case .pointer(.function(let _cResultType, let _cParameterTypes, variadic: false)) = cType { + cResultType = _cResultType + cParameterTypes = _cParameterTypes + } else if case .function(let _cResultType, let _cParameterTypes, variadic: false) = cType { + cResultType = _cResultType + cParameterTypes = _cParameterTypes + } else { + fatalError("must be a C function (pointer) type; name=\(name), cType=\(cType)") } let cParams = cParameterTypes.enumerated().map { i, ty in @@ -193,14 +217,27 @@ extension FFMSwift2JavaGenerator { private static class \(name) """ ) { printer in - printer.print( - """ - @FunctionalInterface - public interface Function { - \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", "))); - } - """ - ) + if let impl { + printer.print( + """ + public final static class Function { + \(impl.members.joinedJavaStatements(indent: 2)) + \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", "))) { + \(impl.body) + } + } + """ + ) + } else { + printer.print( + """ + @FunctionalInterface + public interface Function { + \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", "))); + } + """ + ) + } printFunctionDescriptorDefinition(&printer, cResultType, cParams) printer.print( """ @@ -425,20 +462,37 @@ extension FFMSwift2JavaGenerator { //=== Part 4: Convert the return value. if translatedSignature.result.javaResultType == .void { + // Trivial downcall with no conversion needed, no callback either printer.print("\(downCall);") } else { let placeholder: String - if translatedSignature.result.outParameters.isEmpty { + let placeholderForDowncall: String? + + if let outCallback = translatedSignature.result.outCallback { + print("[swift] has out callback") + placeholder = "\(outCallback.name)" // the result will be read out from the _result_initialize java class + placeholderForDowncall = "\(downCall)" + print("[swift] has out callback = placeholder \(placeholder)") + print("[swift] has out callback = placeholderForDowncall \(placeholderForDowncall)") + } else if translatedSignature.result.outParameters.isEmpty { placeholder = downCall + placeholderForDowncall = nil } else { // FIXME: Support cdecl thunk returning a value while populating the out parameters. printer.print("\(downCall);") + placeholderForDowncall = nil placeholder = "_result" } - let result = translatedSignature.result.conversion.render(&printer, placeholder) + let result = translatedSignature.result.conversion.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) if translatedSignature.result.javaResultType != .void { - printer.print("return \(result);") + switch translatedSignature.result.conversion { + case .initializeResultWithUpcall(_, let extractResult): + printer.print("\(result);") // the result in the callback situation is a series of setup steps + printer.print("return \(extractResult.render(&printer, placeholder));") // extract the actual result + default: + printer.print("return \(result);") + } } else { printer.print("\(result);") } @@ -465,13 +519,16 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .explodedName, .constant, .readMemorySegment: + case .placeholder, .placeholderForDowncall, .explodedName, .constant, .readMemorySegment: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: return true case .temporaryArena: return true + case .initializeResultWithUpcall(let steps, let result): + return steps.contains { $0.requiresSwiftArena } || result.requiresSwiftArena + case .call(let inner, let base, _, _): return inner.requiresSwiftArena || (base?.requiresSwiftArena == true) @@ -490,12 +547,14 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { /// Whether the conversion uses temporary Arena. var requiresTemporaryArena: Bool { switch self { - case .placeholder, .explodedName, .constant: + case .placeholder, .placeholderForDowncall, .explodedName, .constant: return false case .temporaryArena: return true case .readMemorySegment: return true + case .initializeResultWithUpcall: + return true case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), @@ -514,13 +573,21 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { } /// Returns the conversion string applied to the placeholder. - func render(_ printer: inout CodePrinter, _ placeholder: String) -> String { + func render(_ printer: inout CodePrinter, _ placeholder: String, placeholderForDowncall: String? = nil) -> String { + print("render: \(self)") // NOTE: 'printer' is used if the conversion wants to cause side-effects. // E.g. storing a temporary values into a variable. switch self { case .placeholder: return placeholder + case .placeholderForDowncall: + if let placeholderForDowncall { + return "\(placeholderForDowncall)" + } else { + return "/*placeholderForDowncall undefined!*/" + } + case .explodedName(let component): return "\(placeholder)_\(component)" @@ -530,6 +597,15 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { case .temporaryArena: return "arena$" + case .initializeResultWithUpcall(let steps, _): + return steps.map { step in + var printer = CodePrinter() + var out = "" + out += step.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + out += printer.contents + return out + }.joined(separator: ";\n") + case .call(let inner, let base, let function, let withArena): let inner = inner.render(&printer, placeholder) let arenaArg = withArena ? ", arena$" : "" @@ -541,6 +617,7 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { } return "\(baseStr)\(function)(\(inner)\(arenaArg))" + // TODO: deduplicate with 'method' case .method(let inner, let methodName, let arguments, let withArena): let inner = inner.render(&printer, placeholder) let args = arguments.map { $0.render(&printer, placeholder) } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 61918ff0e..983d190fc 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -63,13 +63,26 @@ extension FFMSwift2JavaGenerator { /// 'JavaParameter.name' is the suffix for the receiver variable names. For example /// /// var _result_pointer = MemorySegment.allocate(...) - /// var _result_count = MemroySegment.allocate(...) + /// var _result_count = MemorySegment.allocate(...) /// downCall(_result_pointer, _result_count) /// return constructResult(_result_pointer, _result_count) /// /// This case, there're two out parameter, named '_pointer' and '_count'. var outParameters: [JavaParameter] + /// Similar to out parameters, but instead of parameters we "fill in" in native, + /// we create an upcall handle before the downcall and pass it to the downcall. + /// Swift then invokes the upcall in order to populate some data in Java (our callback). + /// + /// After the call is made, we may need to further extact the result from the called-back-into + /// Java function class, for example: + /// + /// var _result_initialize = new $result_initialize.Function(); + /// downCall($result_initialize.toUpcallHandle(_result_initialize, arena)) + /// return _result_initialize.result + /// + var outCallback: OutCallback? + /// Describes how to construct the Java result from the foreign function return /// value and/or the out parameters. var conversion: JavaConversionStep @@ -298,7 +311,7 @@ extension FFMSwift2JavaGenerator { } // Result. - let result = try self.translate( + let result = try self.translateResult( swiftResult: swiftSignature.result, loweredResult: loweredFunctionSignature.result ) @@ -612,7 +625,7 @@ extension FFMSwift2JavaGenerator { } /// Translate a Swift API result to the user-facing Java API result. - func translate( + func translateResult( swiftResult: SwiftResult, loweredResult: LoweredResult ) throws -> TranslatedResult { @@ -719,26 +732,31 @@ extension FFMSwift2JavaGenerator { javaResultType: .array(.byte), annotations: [.unsigned], - outParameters: [ - JavaParameter(name: "pointer", type: .javaForeignMemorySegment), - JavaParameter(name: "count", type: .long), - ], - conversion: - .method( - .method( - .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), - methodName: "reinterpret", - arguments: [ - .readMemorySegment(.explodedName(component: "count"), as: .long) - ], - withArena: false - ), - methodName: "toArray", - arguments: [ - .constant("ValueLayout.JAVA_BYTE") - ], - withArena: false - ) + outParameters: [], // no out parameters, but we do an "out" callback + outCallback: OutCallback( + name: "$_result_initialize", + members: [ + "byte[] result = null" + ], + parameters: [ + JavaParameter(name: "pointer", type: .javaForeignMemorySegment), + JavaParameter(name: "count", type: .long), + ], + cFunc: CFunction( + resultType: .void, + name: "apply", + parameters: [ + CParameter(type: .pointer(.void)), + CParameter(type: .integral(.size_t)), + ], + isVariadic: false), + body: "this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); // copy native Swift array to Java heap array" + ), + conversion: .initializeResultWithUpcall([ + .constant("var _result_initialize = new swiftjava_SwiftModule_returnArray.$_result_initialize.Function()"), + .placeholderForDowncall, // perform the downcall here + ], + extractResult: .property(.constant("_result_initialize"), propertyName: "result")) ) case .genericParameter, .optional, .function, .existential, .opaque, .composite, .array: @@ -761,6 +779,10 @@ extension FFMSwift2JavaGenerator { enum JavaConversionStep { /// The input case placeholder + + /// The "downcall", e.g. `swiftjava_SwiftModule_returnArray.call(...)`. + /// This can be used in combination with aggregate conversion steps to prepare a setup and processing of the downcall. + case placeholderForDowncall /// The temporary `arena$` that is necessary to complete the conversion steps. case temporaryArena @@ -771,6 +793,12 @@ extension FFMSwift2JavaGenerator { /// A fixed value case constant(String) + /// The result of the function will be initialized with a callback to Java (an upcall). + /// + /// The `extractResult` is used for the actual `return ...` statement, because we need to extract + /// the return value from the called back into class, e.g. `return _result_initialize.result`. + indirect case initializeResultWithUpcall([JavaConversionStep], extractResult: JavaConversionStep) + /// 'value.$memorySegment()' indirect case swiftValueSelfSegment(JavaConversionStep) @@ -786,6 +814,7 @@ extension FFMSwift2JavaGenerator { .call(step, base: nil, function: function, withArena: withArena) } + // TODO: just use call instead? /// Apply a method on the placeholder. /// If `withArena` is true, `arena$` argument is added. indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) diff --git a/Sources/JExtractSwiftLib/JavaParameter.swift b/Sources/JExtractSwiftLib/JavaParameter.swift index 0b243b3a4..34f3b254e 100644 --- a/Sources/JExtractSwiftLib/JavaParameter.swift +++ b/Sources/JExtractSwiftLib/JavaParameter.swift @@ -85,3 +85,19 @@ struct JavaParameter { return "\(annotationsStr) \(type) \(name)" } } + +struct OutCallback { + /// Name of the "function" that we'll use as the out callback / upcall, e.g. "$_upcall_initialize" + var name: String { + willSet { + precondition("\(newValue)".starts(with: "$"), "OutCallback class names should stat with $") + } + } + var members: [String + ] + var parameters: [JavaParameter] + // FIXME: compute this instead + var cFunc: CFunction + var body: String + +} \ No newline at end of file diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift index 67671548b..301524bbb 100644 --- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift +++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift @@ -159,6 +159,13 @@ func assertOutput( currentGotLine += 1 } else { + guard gotLines.count > currentGotLine else { + print("WARNING: index out of bounds when asserting text: \(currentGotLine)") + print("got lines ======") + print(gotLines.joined(separator: "\n").red) + currentExpectedLine += 1 + continue + } let gottenLine = gotLines[currentGotLine].trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if gottenLine.commonPrefix(with: expectedLine) != expectedLine { diffLineNumbers.append(currentExpectedLine + matchingOutputOffset) diff --git a/Tests/JExtractSwiftTests/ByteArrayTests.swift b/Tests/JExtractSwiftTests/ByteArrayTests.swift index 6a34a9e4a..0ed1bab06 100644 --- a/Tests/JExtractSwiftTests/ByteArrayTests.swift +++ b/Tests/JExtractSwiftTests/ByteArrayTests.swift @@ -91,69 +91,68 @@ final class ByteArrayTests { [ // """ // /** - // * {snippet lang=c : - // * void (*)(void) + // * {@snippet lang=c : + // * void swiftjava_SwiftModule_returnArray(void (*_result_initialize)(const void *, ptrdiff_t)) // * } // */ - // private static class $callback { - // @FunctionalInterface - // public interface Function { - // void apply(); - // } - // private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid(); - // private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); - // private static MemorySegment toUpcallStub(Function fi, Arena arena) { - // return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + // private static class swiftjava_SwiftModule_returnArray { + // private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + // /* _result_initialize: */SwiftValueLayout.SWIFT_POINTER + // ); + // private static final MemorySegment ADDR = + // SwiftModule.findOrThrow("swiftjava_SwiftModule_returnArray"); + // private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + // public static void call(java.lang.foreign.MemorySegment _result_initialize) { + // try { + // if (CallTraces.TRACE_DOWNCALLS) { + // CallTraces.traceDowncall(_result_initialize); + // } + // HANDLE.invokeExact(_result_initialize); + // } catch (Throwable ex$) { + // throw new AssertionError("should not reach here", ex$); + // } // } // } // """, // """ - // public static class _result_initialize { - // @FunctionalInterface - // public interface callback extends swiftjava___FakeModule_callMe_callback.$callback.Function {} - // private static MemorySegment $toUpcallStub(callback fi, Arena arena) { - // return swiftjava___FakeModule_callMe_callback.$callback.toUpcallStub(fi, arena); + // /** + // * {snippet lang=c : + // * void (*)(const void *, ptrdiff_t) + // * } + // */ + // private static class $_result_initialize { + // final static class Function { + // byte[] result; + // void apply(java.lang.foreign.MemorySegment _0, long _1) { + // this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); + // } + // } + // private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + // /* _0: */SwiftValueLayout.SWIFT_POINTER, + // /* _1: */SwiftValueLayout.SWIFT_INT + // ); + // private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); + // private static MemorySegment toUpcallStub(Function fi, Arena arena) { + // return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); // } // } // """, """ /** * Downcall to Swift: - * {@snippet lang=swift : + * {@snippet lang = swift: * public func returnArray() -> [UInt8] - * } + *} */ - @Unsigned public static byte[] returnArray() { - try(var arena$ = Arena.ofConfined()) { - _result_initialize callback = (buf, count) -> { - - }; - swiftjava___FakeModule_returnArray.call(_result_initialize.$toUpcallStub(callback, arena$)); - swiftjava_SwiftModule_returnArray.call(_result_pointer, _result_count); + try (var arena$ = Arena.ofAuto()) { + var _result_initialize = new swiftjava_SwiftModule_returnArray.$_result_initialize.Function(); + swiftjava_SwiftModule_returnArray.call(swiftjava_SwiftModule_returnArray.$_result_initialize.toUpcallStub(_result_initialize, arena$)); + return _result_initialize.result; } } """ ], - // [ - // """ - // /** - // * Downcall to Swift: - // * {@snippet lang=swift : - // * public func returnArray() -> [UInt8] - // * } - // */ - // @Unsigned - // public static byte[] returnArray() { - // try(var arena$ = Arena.ofConfined()) { - // MemorySegment _result_pointer = arena$.allocate(SwiftValueLayout.SWIFT_POINTER); - // MemorySegment _result_count = arena$.allocate(SwiftValueLayout.SWIFT_INT64); - // swiftjava_SwiftModule_returnArray.call(_result_pointer, _result_count); - // return _result_pointer.get(SwiftValueLayout.SWIFT_POINTER, 0).reinterpret(_result_count.get(SwiftValueLayout.SWIFT_INT64, 0)).toArray(ValueLayout.JAVA_BYTE); - // } - // } - // """ - // ], /* expected Swift chunks */ [ """ From da42a87a9d57d84d72fe41b30b0304309a36d502 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 5 Dec 2025 18:07:25 +0900 Subject: [PATCH 09/12] Done implementing returning byte[] in ffm mode --- .../MySwiftLibrary/MySwiftLibrary.swift | 7 -- Samples/untitled/.gitignore | 32 -------- Samples/untitled/src/Main.kt | 14 ---- Samples/untitled/untitled.iml | 15 ---- ...t2JavaGenerator+JavaBindingsPrinting.swift | 14 +++- Tests/JExtractSwiftTests/ByteArrayTests.swift | 80 +++++++------------ 6 files changed, 39 insertions(+), 123 deletions(-) delete mode 100644 Samples/untitled/.gitignore delete mode 100644 Samples/untitled/src/Main.kt delete mode 100644 Samples/untitled/untitled.iml diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift index cfd6899c1..c830e9f6c 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/MySwiftLibrary.swift @@ -80,13 +80,6 @@ public func returnSwiftArray() -> [UInt8] { return [1, 2, 3, 4] } -// public func swiftjava_MySwiftLibrary_getArray(_ _result_initialize: (UnsafeMutablePointer, UnsafeMutablePointer) -> ()) { -// let _result = returnSwiftArray() -// _result.withUnsafeBufferPointer { buf in -// _result_initialize(buf.baseAddress, buf.count) -// } -// } - public func withArray(body: ([UInt8]) -> Void) { body([1, 2, 3]) } diff --git a/Samples/untitled/.gitignore b/Samples/untitled/.gitignore deleted file mode 100644 index 3ddbf4c44..000000000 --- a/Samples/untitled/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -### IntelliJ IDEA ### -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### Kotlin ### -.kotlin - -### Eclipse ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ - -### Mac OS ### -.DS_Store \ No newline at end of file diff --git a/Samples/untitled/src/Main.kt b/Samples/untitled/src/Main.kt deleted file mode 100644 index 72654654a..000000000 --- a/Samples/untitled/src/Main.kt +++ /dev/null @@ -1,14 +0,0 @@ -//TIP To Run code, press or -// click the icon in the gutter. -fun main() { - val name = "Kotlin" - //TIP Press with your caret at the highlighted text - // to see how IntelliJ IDEA suggests fixing it. - println("Hello, " + name + "!") - - for (i in 1..5) { - //TIP Press to start debugging your code. We have set one breakpoint - // for you, but you can always add more by pressing . - println("i = $i") - } -} \ No newline at end of file diff --git a/Samples/untitled/untitled.iml b/Samples/untitled/untitled.iml deleted file mode 100644 index 43dd653dc..000000000 --- a/Samples/untitled/untitled.iml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 7e08addd2..40c8cd77c 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -456,6 +456,17 @@ extension FFMSwift2JavaGenerator { downCallArguments.append(varName) } + if let outCallback = translatedSignature.result.outCallback { + let funcName = outCallback.name + assert(funcName.first == "$", "OutCallback names must start with $") + let varName = funcName.dropFirst() + downCallArguments.append( + """ + swiftjava_SwiftModule_returnArray.\(outCallback.name).toUpcallStub(\(varName), arena$) + """ + ) + } + //=== Part 3: Downcall. let thunkName = thunkNameRegistry.functionThunkName(decl: decl) let downCall = "\(thunkName).call(\(downCallArguments.joined(separator: ", ")))" @@ -469,11 +480,8 @@ extension FFMSwift2JavaGenerator { let placeholderForDowncall: String? if let outCallback = translatedSignature.result.outCallback { - print("[swift] has out callback") placeholder = "\(outCallback.name)" // the result will be read out from the _result_initialize java class placeholderForDowncall = "\(downCall)" - print("[swift] has out callback = placeholder \(placeholder)") - print("[swift] has out callback = placeholderForDowncall \(placeholderForDowncall)") } else if translatedSignature.result.outParameters.isEmpty { placeholder = downCall placeholderForDowncall = nil diff --git a/Tests/JExtractSwiftTests/ByteArrayTests.swift b/Tests/JExtractSwiftTests/ByteArrayTests.swift index 0ed1bab06..df29131d1 100644 --- a/Tests/JExtractSwiftTests/ByteArrayTests.swift +++ b/Tests/JExtractSwiftTests/ByteArrayTests.swift @@ -89,69 +89,45 @@ final class ByteArrayTests { JExtractGenerationMode.ffm, /* expected Java chunks */ [ - // """ - // /** - // * {@snippet lang=c : - // * void swiftjava_SwiftModule_returnArray(void (*_result_initialize)(const void *, ptrdiff_t)) - // * } - // */ - // private static class swiftjava_SwiftModule_returnArray { - // private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - // /* _result_initialize: */SwiftValueLayout.SWIFT_POINTER - // ); - // private static final MemorySegment ADDR = - // SwiftModule.findOrThrow("swiftjava_SwiftModule_returnArray"); - // private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); - // public static void call(java.lang.foreign.MemorySegment _result_initialize) { - // try { - // if (CallTraces.TRACE_DOWNCALLS) { - // CallTraces.traceDowncall(_result_initialize); - // } - // HANDLE.invokeExact(_result_initialize); - // } catch (Throwable ex$) { - // throw new AssertionError("should not reach here", ex$); - // } - // } - // } - // """, - // """ - // /** - // * {snippet lang=c : - // * void (*)(const void *, ptrdiff_t) - // * } - // */ - // private static class $_result_initialize { - // final static class Function { - // byte[] result; - // void apply(java.lang.foreign.MemorySegment _0, long _1) { - // this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); - // } - // } - // private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - // /* _0: */SwiftValueLayout.SWIFT_POINTER, - // /* _1: */SwiftValueLayout.SWIFT_INT - // ); - // private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); - // private static MemorySegment toUpcallStub(Function fi, Arena arena) { - // return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); - // } - // } - // """, + """ + /** + * {snippet lang=c : + * void (void *, size_t) + * } + */ + private static class $_result_initialize { + public final static class Function { + byte[] result = null; + void apply(java.lang.foreign.MemorySegment _0, long _1) { + this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); + } + } + private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + /* _0: */SwiftValueLayout.SWIFT_POINTER, + /* _1: */SwiftValueLayout.SWIFT_INT + ); + private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); + private static MemorySegment toUpcallStub(Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); + } + } + """, """ /** * Downcall to Swift: - * {@snippet lang = swift: + * {@snippet lang=swift : * public func returnArray() -> [UInt8] - *} + * } */ + @Unsigned public static byte[] returnArray() { - try (var arena$ = Arena.ofAuto()) { + try(var arena$ = Arena.ofConfined()) { var _result_initialize = new swiftjava_SwiftModule_returnArray.$_result_initialize.Function(); swiftjava_SwiftModule_returnArray.call(swiftjava_SwiftModule_returnArray.$_result_initialize.toUpcallStub(_result_initialize, arena$)); return _result_initialize.result; } } - """ + """, ], /* expected Swift chunks */ [ From 6df1b4128610176ebe3f25247f71da6340506498 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 5 Dec 2025 18:15:53 +0900 Subject: [PATCH 10/12] cleanups --- .../com/example/swift/WithBufferTest.java | 75 ++++--------------- ...Swift2JavaGenerator+FunctionLowering.swift | 6 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 1 - 3 files changed, 16 insertions(+), 66 deletions(-) diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java index fdbdf2cb2..8ee87bafe 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java @@ -28,67 +28,22 @@ public class WithBufferTest { - /** - * {@snippet lang = c: - * void swiftjava_SwiftModule_returnArray(void (*_result_initialize)(const void *, ptrdiff_t)) - *} - */ - private static class swiftjava_SwiftModule_returnArray { - private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* _result_initialize: */SwiftValueLayout.SWIFT_POINTER - ); - private static final MemorySegment ADDR = null; - // SwiftModule.findOrThrow("swiftjava_SwiftModule_returnArray"); - private static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); - - public static void call(java.lang.foreign.MemorySegment _result_initialize) { - try { - if (CallTraces.TRACE_DOWNCALLS) { - CallTraces.traceDowncall(_result_initialize); - } - HANDLE.invokeExact(_result_initialize); - } catch (Throwable ex$) { - throw new AssertionError("should not reach here", ex$); - } - } - - /** - * {snippet lang=c : - * void (*)(const void *, ptrdiff_t) - * } - */ - private static class $_result_initialize { - public static final class Function { - byte[] result = null; - - void apply(java.lang.foreign.MemorySegment _0, long _1) { - this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); - } - } - - private static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - /* _0: */SwiftValueLayout.SWIFT_POINTER, - /* _1: */SwiftValueLayout.SWIFT_INT - ); - private static final MethodHandle HANDLE = SwiftRuntime.upcallHandle(Function.class, "apply", DESC); - - private static MemorySegment toUpcallStub(Function fi, Arena arena) { - return Linker.nativeLinker().upcallStub(HANDLE.bindTo(fi), DESC, arena); - } - } + @Test + void test_withBuffer() { + AtomicLong bufferSize = new AtomicLong(); + MySwiftLibrary.withBuffer((buf) -> { + CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); + bufferSize.set(buf.byteSize()); + }); + + assertEquals(124, bufferSize.get()); } + @Test + void test_getArray() { + AtomicLong bufferSize = new AtomicLong(); + byte[] javaBytes = MySwiftLibrary.getArray() -} - -@Test -void test_withBuffer() { - AtomicLong bufferSize = new AtomicLong(); - MySwiftLibrary.withBuffer((buf) -> { - CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize()); - bufferSize.set(buf.byteSize()); - }); - - assertEquals(124, bufferSize.get()); -} + assertEquals({1, 2, 3}, bufferSize.get()); + } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index fc4ab8f01..8e09c0b0c 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -738,13 +738,9 @@ struct CdeclLowering { base: "\(outParameterName)_initialize", methodName: nil, // just `(...)` apply the closure arguments: [ - .init(label: nil, argument: .member(.constant("_0"), member: "baseAddress")), + .init(label: nil, argument: .member(.constant("_0"), member: "baseAddress!")), .init(label: nil, argument: .member(.constant("_0"), member: "count")), ] - // arguments: [ - // .init(label: nil, argument: .member(.placeholder, member: "baseAddress")), - // .init(label: nil, argument: .member(.placeholder, member: "count")), - // ] ) ) ) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 40c8cd77c..e546a8fa7 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -582,7 +582,6 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { /// Returns the conversion string applied to the placeholder. func render(_ printer: inout CodePrinter, _ placeholder: String, placeholderForDowncall: String? = nil) -> String { - print("render: \(self)") // NOTE: 'printer' is used if the conversion wants to cause side-effects. // E.g. storing a temporary values into a variable. switch self { From a4fd03ff0672139c86d44d5867bfa51b47810f84 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Fri, 5 Dec 2025 20:20:52 +0900 Subject: [PATCH 11/12] final boilerplate additions for ffm array returns --- .../java/com/example/swift/FFMArraysTest.java | 12 ++- .../com/example/swift/WithBufferTest.java | 7 -- ...t2JavaGenerator+JavaBindingsPrinting.swift | 88 ++++++++++++------- ...MSwift2JavaGenerator+JavaTranslation.swift | 36 ++++++-- Tests/JExtractSwiftTests/ByteArrayTests.swift | 12 ++- 5 files changed, 104 insertions(+), 51 deletions(-) diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java index 13ecf7b67..c195f11bf 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java @@ -41,8 +41,6 @@ void test_sumAllByteArrayElements_throughMemorySegment() { var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes); var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length); - System.out.println("swiftSideSum = " + swiftSideSum); - int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum(); assertEquals(javaSideSum, swiftSideSum); } @@ -55,9 +53,15 @@ void test_sumAllByteArrayElements_arrayCopy() { var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytes); - System.out.println("swiftSideSum = " + swiftSideSum); - int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum(); assertEquals(javaSideSum, swiftSideSum); } + + @Test + void test_getArray() { + AtomicLong bufferSize = new AtomicLong(); + byte[] javaBytes = MySwiftLibrary.getArray(); // automatically converted [UInt8] to byte[] + + assertArrayEquals(new byte[]{1, 2, 3}, javaBytes); + } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java index 8ee87bafe..9e0654767 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java @@ -39,11 +39,4 @@ void test_withBuffer() { assertEquals(124, bufferSize.get()); } - @Test - void test_getArray() { - AtomicLong bufferSize = new AtomicLong(); - byte[] javaBytes = MySwiftLibrary.getArray() - - assertEquals({1, 2, 3}, bufferSize.get()); - } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index e546a8fa7..b1a879515 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -182,8 +182,7 @@ extension FFMSwift2JavaGenerator { /// } /// ``` /// - /// If a `functionBody` is provided the `Function` becomes a `final static class`, - /// with the specified implementation as the implementation of the `apply` method. + /// If a `functionBody` is provided, a `Function$Impl` class will be emitted as well. func printFunctionPointerParameterDescriptorClass( _ printer: inout CodePrinter, _ name: String, @@ -217,27 +216,28 @@ extension FFMSwift2JavaGenerator { private static class \(name) """ ) { printer in + printer.print( + """ + @FunctionalInterface + public interface Function { + \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", "))); + } + """ + ) + if let impl { printer.print( """ - public final static class Function { + public final static class Function$Impl implements Function { \(impl.members.joinedJavaStatements(indent: 2)) - \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", "))) { + public \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", "))) { \(impl.body) } } """ ) - } else { - printer.print( - """ - @FunctionalInterface - public interface Function { - \(cResultType.javaType) apply(\(paramDecls.joined(separator: ", "))); - } - """ - ) - } + } + printFunctionDescriptorDefinition(&printer, cResultType, cParams) printer.print( """ @@ -456,19 +456,20 @@ extension FFMSwift2JavaGenerator { downCallArguments.append(varName) } + let thunkName = thunkNameRegistry.functionThunkName(decl: decl) + if let outCallback = translatedSignature.result.outCallback { let funcName = outCallback.name assert(funcName.first == "$", "OutCallback names must start with $") let varName = funcName.dropFirst() downCallArguments.append( """ - swiftjava_SwiftModule_returnArray.\(outCallback.name).toUpcallStub(\(varName), arena$) + \(thunkName).\(outCallback.name).toUpcallStub(\(varName), arena$) """ ) } //=== Part 3: Downcall. - let thunkName = thunkNameRegistry.functionThunkName(decl: decl) let downCall = "\(thunkName).call(\(downCallArguments.joined(separator: ", ")))" //=== Part 4: Convert the return value. @@ -527,7 +528,9 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { /// Whether the conversion uses SwiftArena. var requiresSwiftArena: Bool { switch self { - case .placeholder, .placeholderForDowncall, .explodedName, .constant, .readMemorySegment: + case .placeholder, .placeholderForDowncall, .placeholderForSwiftThunkName: + return false + case .explodedName, .constant, .readMemorySegment, .javaNew: return false case .constructSwiftValue, .wrapMemoryAddressUnsafe: return true @@ -536,6 +539,8 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { case .initializeResultWithUpcall(let steps, let result): return steps.contains { $0.requiresSwiftArena } || result.requiresSwiftArena + case .introduceVariable(_, let value): + return value.requiresSwiftArena case .call(let inner, let base, _, _): return inner.requiresSwiftArena || (base?.requiresSwiftArena == true) @@ -547,7 +552,7 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { .swiftValueSelfSegment(let inner): return inner.requiresSwiftArena - case .commaSeparated(let list): + case .commaSeparated(let list, _): return list.contains(where: { $0.requiresSwiftArena }) } } @@ -555,7 +560,9 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { /// Whether the conversion uses temporary Arena. var requiresTemporaryArena: Bool { switch self { - case .placeholder, .placeholderForDowncall, .explodedName, .constant: + case .placeholder, .placeholderForDowncall, .placeholderForSwiftThunkName: + return false + case .explodedName, .constant, .javaNew: return false case .temporaryArena: return true @@ -563,6 +570,8 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { return true case .initializeResultWithUpcall: return true + case .introduceVariable(_, let value): + return value.requiresTemporaryArena case .cast(let inner, _), .construct(let inner, _), .constructSwiftValue(let inner, _), @@ -575,7 +584,7 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { return withArena || inner.requiresTemporaryArena || args.contains(where: { $0.requiresTemporaryArena }) case .property(let inner, _): return inner.requiresTemporaryArena - case .commaSeparated(let list): + case .commaSeparated(let list, _): return list.contains(where: { $0.requiresTemporaryArena }) } } @@ -594,6 +603,16 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { } else { return "/*placeholderForDowncall undefined!*/" } + case .placeholderForSwiftThunkName: + if let placeholderForDowncall { + let downcall = "\(placeholderForDowncall)" + return String(downcall[..<(downcall.firstIndex(of: ".") ?? downcall.endIndex)]) // . separates thunk name from the `.call` + } else { + return "/*placeholderForDowncall undefined!*/" + } + + case .temporaryArena: + return "arena$" case .explodedName(let component): return "\(placeholder)_\(component)" @@ -601,10 +620,11 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { case .swiftValueSelfSegment: return "\(placeholder).$memorySegment()" - case .temporaryArena: - return "arena$" + case .javaNew(let value): + return "new \(value.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall))" case .initializeResultWithUpcall(let steps, _): + // TODO: could we use the printing to introduce the upcall handle instead? return steps.map { step in var printer = CodePrinter() var out = "" @@ -626,33 +646,39 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { // TODO: deduplicate with 'method' case .method(let inner, let methodName, let arguments, let withArena): - let inner = inner.render(&printer, placeholder) - let args = arguments.map { $0.render(&printer, placeholder) } + let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let args = arguments.map { $0.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) } let argsStr = (args + (withArena ? ["arena$"] : [])).joined(separator: " ,") return "\(inner).\(methodName)(\(argsStr))" case .property(let inner, let propertyName): - let inner = inner.render(&printer, placeholder) + let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "\(inner).\(propertyName)" case .constructSwiftValue(let inner, let javaType): - let inner = inner.render(&printer, placeholder) + let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "new \(javaType.className!)(\(inner), swiftArena$)" case .wrapMemoryAddressUnsafe(let inner, let javaType): - let inner = inner.render(&printer, placeholder) + let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "\(javaType.className!).wrapMemoryAddressUnsafe(\(inner), swiftArena$)" case .construct(let inner, let javaType): - let inner = inner.render(&printer, placeholder) + let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "new \(javaType)(\(inner))" + + case .introduceVariable(let name, let value): + let value = value.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + return "var \(name) = \(value);" case .cast(let inner, let javaType): - let inner = inner.render(&printer, placeholder) + let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "(\(javaType)) \(inner)" - case .commaSeparated(let list): - return list.map({ $0.render(&printer, placeholder)}).joined(separator: ", ") + case .commaSeparated(let list, let separator): + return list.map({ + $0.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + }).joined(separator: separator) case .constant(let value): return value diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 983d190fc..d3a2626bb 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -753,7 +753,15 @@ extension FFMSwift2JavaGenerator { body: "this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); // copy native Swift array to Java heap array" ), conversion: .initializeResultWithUpcall([ - .constant("var _result_initialize = new swiftjava_SwiftModule_returnArray.$_result_initialize.Function()"), + .introduceVariable( + name: "_result_initialize", + initializeWith: .javaNew(.commaSeparated([ + // We need to refer to the nested class that is created for this function. + // The class that contains all the related functional interfaces is called the same + // as the downcall function, so we use the thunk name to find this class/ + .placeholderForSwiftThunkName, .constant("$_result_initialize.Function$Impl()") + ], separator: "."))), + // .constant("var = new \(.placeholderForDowncallThunkName).."), .placeholderForDowncall, // perform the downcall here ], extractResult: .property(.constant("_result_initialize"), propertyName: "result")) @@ -783,8 +791,16 @@ extension FFMSwift2JavaGenerator { /// The "downcall", e.g. `swiftjava_SwiftModule_returnArray.call(...)`. /// This can be used in combination with aggregate conversion steps to prepare a setup and processing of the downcall. case placeholderForDowncall + + /// Placeholder for Swift thunk name, e.g. "swiftjava_SwiftModule_returnArray". + /// + /// This is derived from the placeholderForDowncall substitution - could be done more cleanly, + /// however this has the benefit of not needing to pass the name substituion separately. + case placeholderForSwiftThunkName /// The temporary `arena$` that is necessary to complete the conversion steps. + /// + /// This is distinct from just a constant 'arena$' string, since it forces the creation of a temporary arena. case temporaryArena /// The input exploded into components. @@ -814,7 +830,7 @@ extension FFMSwift2JavaGenerator { .call(step, base: nil, function: function, withArena: withArena) } - // TODO: just use call instead? + // TODO: just use make call more powerful and use it instead? /// Apply a method on the placeholder. /// If `withArena` is true, `arena$` argument is added. indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) @@ -826,17 +842,27 @@ extension FFMSwift2JavaGenerator { /// Call 'new \(Type)(\(placeholder), swiftArena$)'. indirect case constructSwiftValue(JavaConversionStep, JavaType) + /// Construct the type using the placeholder as arguments. + indirect case construct(JavaConversionStep, JavaType) + /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type indirect case wrapMemoryAddressUnsafe(JavaConversionStep, JavaType) - /// Construct the type using the placeholder as arguments. - indirect case construct(JavaConversionStep, JavaType) + /// Introduce a local variable, e.g. `var result = new Something()` + indirect case introduceVariable(name: String, initializeWith: JavaConversionStep) /// Casting the placeholder to the certain type. indirect case cast(JavaConversionStep, JavaType) + + /// Prefix the conversion step with a java `new`. + /// + /// This is useful if constructing the value is complex and we use + /// a combination of separated values and constants to do so; Generally prefer using `construct` + /// if you only want to construct a "wrapper" for the current `.placeholder`. + indirect case javaNew(JavaConversionStep) /// Convert the results of the inner steps to a comma separated list. - indirect case commaSeparated([JavaConversionStep]) + indirect case commaSeparated([JavaConversionStep], separator: String = ", ") /// Refer an exploded argument suffixed with `_\(name)`. indirect case readMemorySegment(JavaConversionStep, as: JavaType) diff --git a/Tests/JExtractSwiftTests/ByteArrayTests.swift b/Tests/JExtractSwiftTests/ByteArrayTests.swift index df29131d1..052157281 100644 --- a/Tests/JExtractSwiftTests/ByteArrayTests.swift +++ b/Tests/JExtractSwiftTests/ByteArrayTests.swift @@ -96,9 +96,13 @@ final class ByteArrayTests { * } */ private static class $_result_initialize { - public final static class Function { + @FunctionalInterface + public interface Function { + void apply(java.lang.foreign.MemorySegment _0, long _1); + } + public final static class Function$Impl implements Function { byte[] result = null; - void apply(java.lang.foreign.MemorySegment _0, long _1) { + public void apply(java.lang.foreign.MemorySegment _0, long _1) { this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); } } @@ -122,7 +126,7 @@ final class ByteArrayTests { @Unsigned public static byte[] returnArray() { try(var arena$ = Arena.ofConfined()) { - var _result_initialize = new swiftjava_SwiftModule_returnArray.$_result_initialize.Function(); + var _result_initialize = new swiftjava_SwiftModule_returnArray.$_result_initialize.Function$Impl(); swiftjava_SwiftModule_returnArray.call(swiftjava_SwiftModule_returnArray.$_result_initialize.toUpcallStub(_result_initialize, arena$)); return _result_initialize.result; } @@ -136,7 +140,7 @@ final class ByteArrayTests { public func swiftjava_SwiftModule_returnArray(_ _result_initialize: @convention(c) (UnsafeRawPointer, Int) -> ()) { let _result = returnArray() _result.withUnsafeBufferPointer({ (_0) in - return _result_initialize(_0.baseAddress, _0.count) + return _result_initialize(_0.baseAddress!, _0.count) }) } """ From 0a94a34e81359af4453a3d61ed9cd73aed827769 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sat, 6 Dec 2025 05:27:44 +0900 Subject: [PATCH 12/12] Update Sources/JExtractSwiftLib/FFM/ConversionStep.swift --- Sources/JExtractSwiftLib/FFM/ConversionStep.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift index 5d6ee9b6c..a295676d5 100644 --- a/Sources/JExtractSwiftLib/FFM/ConversionStep.swift +++ b/Sources/JExtractSwiftLib/FFM/ConversionStep.swift @@ -22,7 +22,6 @@ enum ConversionStep: Equatable { /// The value being lowered. case placeholder - /// FIXME: Workaround for picking a specific placeholder value; We should resolve how method() works with lowered closures instead case constant(String) /// A reference to a component in a value that has been exploded, such as