From a720acb325e211b507ed35a5cd49009bd91ba986 Mon Sep 17 00:00:00 2001 From: Leonid Liadveikin Date: Tue, 25 Aug 2026 08:49:44 +0200 Subject: [PATCH] JNI: fix non-compiling async thunks for protocol boxes and generic types (#905) --- .../MySwiftLibrary/UnsafeBufferPointer.swift | 47 ++++ .../UnsafeRawBufferPointer.swift | 16 ++ .../swift/UnsafeBufferPointerTest.java | 93 +++++++ .../swift/UnsafeRawBufferPointerTest.java | 84 ++++++ .../MySwiftLibrary/UnsafeBufferPointer.swift | 49 ++++ .../UnsafeRawBufferPointer.swift | 18 +- .../swift/UnsafeBufferPointerTest.java | 70 +++++ .../swift/UnsafeRawBufferPointerTest.java | 19 ++ ...Swift2JavaGenerator+FunctionLowering.swift | 29 ++- ...MSwift2JavaGenerator+JavaTranslation.swift | 50 +++- ...ISwift2JavaGenerator+JavaTranslation.swift | 57 ++++ ...wift2JavaGenerator+NativeTranslation.swift | 90 +++++++ .../JavaTypes/JavaType+SwiftKit.swift | 10 + .../JNIMethodIDCaches.swift | 58 +++++ .../core/SwiftUnsafeBufferPointer.java | 71 +++++ .../core/SwiftUnsafeMutableBufferPointer.java | 71 +++++ .../swift/swiftkit/ffm/BufferPointers.java | 93 +++++++ .../swiftkit/ffm/BufferPointersTest.java | 99 +++++++ .../FunctionLoweringTests.swift | 8 +- .../JNI/JNIPointerTests.swift | 246 ++++++++++++++++++ .../MethodImportTests.swift | 189 ++++++++++++++ 21 files changed, 1445 insertions(+), 22 deletions(-) create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/UnsafeBufferPointer.swift create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsafeBufferPointerTest.java create mode 100644 Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeBufferPointer.swift create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeBufferPointerTest.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftUnsafeBufferPointer.java create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftUnsafeMutableBufferPointer.java create mode 100644 SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/BufferPointers.java create mode 100644 SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/BufferPointersTest.java create mode 100644 Tests/JExtractSwiftTests/JNI/JNIPointerTests.swift diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/UnsafeBufferPointer.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/UnsafeBufferPointer.swift new file mode 100644 index 000000000..c96744ac3 --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/UnsafeBufferPointer.swift @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 +// +//===----------------------------------------------------------------------===// + +private let int32BufferStorage: [Int32] = [10, 20, 30, 40] +private let emptyInt32BufferStorage: [Int32] = [] +private var mutableInt32BufferStorage: [Int32] = [5, 10, 15] + +public func makeInt32Buffer() -> UnsafeBufferPointer { + int32BufferStorage.withUnsafeBufferPointer { $0 } +} + +public func makeEmptyInt32Buffer() -> UnsafeBufferPointer { + emptyInt32BufferStorage.withUnsafeBufferPointer { $0 } +} + +public func sumInt32Buffer(data: UnsafeBufferPointer) -> Int64 { + data.reduce(0) { $0 + Int64($1) } +} + +public func makeMutableInt32Buffer() -> UnsafeMutableBufferPointer { + mutableInt32BufferStorage.withUnsafeMutableBufferPointer { $0 } +} + +public func sumMutableInt32Buffer(data: UnsafeMutableBufferPointer) -> Int64 { + data.reduce(0) { $0 + Int64($1) } +} + +public func mutableInt32BufferElement(data: UnsafeMutableBufferPointer, index: Int32) -> Int32 { + data[Int(index)] +} + +public func incrementMutableInt32Buffer(data: UnsafeMutableBufferPointer) { + for index in data.indices { + data[index] += 1 + } +} diff --git a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift index 76ef5d9eb..b46997a54 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift +++ b/Samples/SwiftJavaExtractFFMSampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift @@ -29,3 +29,19 @@ public func sumOfBytes(data: UnsafeRawBufferPointer) -> Int64 { public func bufferCount(data: UnsafeRawBufferPointer) -> Int64 { Int64(data.count) } + +private let rawBufferStorage: [UInt8] = [10, 20, 30, 40] +private let emptyRawBufferStorage: [UInt8] = [] +private var mutableRawBufferStorage: [UInt8] = [5, 10, 15] + +public func makeRawBuffer() -> UnsafeRawBufferPointer { + rawBufferStorage.withUnsafeBytes { $0 } +} + +public func makeEmptyRawBuffer() -> UnsafeRawBufferPointer { + emptyRawBufferStorage.withUnsafeBytes { $0 } +} + +public func makeMutableRawBuffer() -> UnsafeMutableRawBufferPointer { + mutableRawBufferStorage.withUnsafeMutableBytes { $0 } +} diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsafeBufferPointerTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsafeBufferPointerTest.java new file mode 100644 index 000000000..6f4b8008a --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsafeBufferPointerTest.java @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UnsafeBufferPointerTest { + @Test + void nullMemorySegment_isEmptyTypedBuffer() { + assertEquals(0, MySwiftLibrary.sumInt32Buffer(MemorySegment.NULL)); + } + + @Test + void sumInt32Buffer() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment input = arena.allocateFrom(ValueLayout.JAVA_INT, new int[] {10, 20, 30, 40}); + + assertEquals(100, MySwiftLibrary.sumInt32Buffer(input)); + } + } + + @Test + void sumInt32Buffer_empty() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment input = arena.allocate(0, ValueLayout.JAVA_INT.byteAlignment()); + + assertEquals(0, MySwiftLibrary.sumInt32Buffer(input)); + } + } + + @Test + void returnInt32Buffer() { + MemorySegment result = MySwiftLibrary.makeInt32Buffer(); + + assertEquals(16, result.byteSize()); + assertEquals(10, result.get(ValueLayout.JAVA_INT, 0)); + assertEquals(40, result.get(ValueLayout.JAVA_INT, 12)); + } + + @Test + void returnEmptyInt32Buffer() { + assertEquals(0, MySwiftLibrary.makeEmptyInt32Buffer().byteSize()); + } + + @Test + void returnMutableInt32Buffer() { + MemorySegment result = MySwiftLibrary.makeMutableInt32Buffer(); + + assertEquals(12, result.byteSize()); + assertEquals(5, result.get(ValueLayout.JAVA_INT, 0)); + assertEquals(15, result.get(ValueLayout.JAVA_INT, 8)); + } + + @Test + void mutableInt32Buffer_isInitialized() { + MemorySegment buffer = MySwiftLibrary.makeMutableInt32Buffer(); + + assertEquals(5, buffer.get(ValueLayout.JAVA_INT, 0)); + assertEquals(10, buffer.get(ValueLayout.JAVA_INT, 4)); + assertEquals(15, buffer.get(ValueLayout.JAVA_INT, 8)); + } + + @Test + void incrementMutableInt32Buffer_modifiesElements() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment buffer = arena.allocateFrom(ValueLayout.JAVA_INT, new int[] {5, 10, 15}); + + MySwiftLibrary.incrementMutableInt32Buffer(buffer); + + assertEquals(6, buffer.get(ValueLayout.JAVA_INT, 0)); + assertEquals(11, buffer.get(ValueLayout.JAVA_INT, 4)); + assertEquals(16, buffer.get(ValueLayout.JAVA_INT, 8)); + } + } +} diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java new file mode 100644 index 000000000..5a23bd950 --- /dev/null +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java @@ -0,0 +1,84 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class UnsafeRawBufferPointerTest { + @Test + void sumOfBytes() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment input = arena.allocateFrom(ValueLayout.JAVA_BYTE, new byte[] {1, 2, 3, 4, 5}); + + assertEquals(15, MySwiftLibrary.sumOfBytes(input)); + } + } + + @Test + void sumOfBytes_empty() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment input = arena.allocate(0, 1); + + assertEquals(0, MySwiftLibrary.sumOfBytes(input)); + } + } + + @Test + void bufferCount() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment input = arena.allocateFrom(ValueLayout.JAVA_BYTE, new byte[] {10, 20, 30, 40}); + + assertEquals(4, MySwiftLibrary.bufferCount(input)); + } + } + + @Test + void bufferCount_empty() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment input = arena.allocate(0, 1); + + assertEquals(0, MySwiftLibrary.bufferCount(input)); + } + } + + @Test + void returnRawBuffer() { + MemorySegment result = MySwiftLibrary.makeRawBuffer(); + + assertEquals(4, result.byteSize()); + assertEquals(10, result.get(ValueLayout.JAVA_BYTE, 0)); + assertEquals(40, result.get(ValueLayout.JAVA_BYTE, 3)); + } + + @Test + void returnEmptyRawBuffer() { + assertEquals(0, MySwiftLibrary.makeEmptyRawBuffer().byteSize()); + } + + @Test + void returnMutableRawBuffer() { + MemorySegment result = MySwiftLibrary.makeMutableRawBuffer(); + + assertEquals(3, result.byteSize()); + assertEquals(5, result.get(ValueLayout.JAVA_BYTE, 0)); + assertEquals(15, result.get(ValueLayout.JAVA_BYTE, 2)); + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeBufferPointer.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeBufferPointer.swift new file mode 100644 index 000000000..e7a74f8ce --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeBufferPointer.swift @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 +// +//===----------------------------------------------------------------------===// + +import SwiftJava + +private let int32BufferStorage: [Int32] = [10, 20, 30, 40] +private let emptyInt32BufferStorage: [Int32] = [] +private var mutableInt32BufferStorage: [Int32] = [5, 10, 15] + +public func makeInt32Buffer() -> UnsafeBufferPointer { + int32BufferStorage.withUnsafeBufferPointer { $0 } +} + +public func makeEmptyInt32Buffer() -> UnsafeBufferPointer { + emptyInt32BufferStorage.withUnsafeBufferPointer { $0 } +} + +public func sumInt32Buffer(data: UnsafeBufferPointer) -> Int64 { + data.reduce(0) { $0 + Int64($1) } +} + +public func makeMutableInt32Buffer() -> UnsafeMutableBufferPointer { + mutableInt32BufferStorage.withUnsafeMutableBufferPointer { $0 } +} + +public func sumMutableInt32Buffer(data: UnsafeMutableBufferPointer) -> Int64 { + data.reduce(0) { $0 + Int64($1) } +} + +public func mutableInt32BufferElement(data: UnsafeMutableBufferPointer, index: Int32) -> Int32 { + data[Int(index)] +} + +public func incrementMutableInt32Buffer(data: UnsafeMutableBufferPointer) { + for index in data.indices { + data[index] += 1 + } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift index a47052b68..edc2db760 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift +++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/UnsafeRawBufferPointer.swift @@ -28,4 +28,20 @@ public func sumOfBytes(data: UnsafeRawBufferPointer) -> Int64 { public func bufferCount(data: UnsafeRawBufferPointer) -> Int64 { Int64(data.count) } -// snippet.end +//snippet.end + +private let rawBufferStorage: [UInt8] = [10, 20, 30, 40] +private let emptyRawBufferStorage: [UInt8] = [] +private var mutableRawBufferStorage: [UInt8] = [5, 10, 15] + +public func makeRawBuffer() -> UnsafeRawBufferPointer { + rawBufferStorage.withUnsafeBytes { $0 } +} + +public func makeEmptyRawBuffer() -> UnsafeRawBufferPointer { + emptyRawBufferStorage.withUnsafeBytes { $0 } +} + +public func makeMutableRawBuffer() -> UnsafeMutableRawBufferPointer { + mutableRawBufferStorage.withUnsafeMutableBytes { $0 } +} diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeBufferPointerTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeBufferPointerTest.java new file mode 100644 index 000000000..753447856 --- /dev/null +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeBufferPointerTest.java @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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.SwiftUnsafeBufferPointer; +import org.swift.swiftkit.core.SwiftUnsafeMutableBufferPointer; + +import static org.junit.jupiter.api.Assertions.*; + +public class UnsafeBufferPointerTest { + @Test + void returnInt32Buffer() { + SwiftUnsafeBufferPointer buffer = MySwiftLibrary.makeInt32Buffer(); + + assertNotEquals(0, buffer.getBaseAddress()); + assertEquals(4, buffer.getCount()); + assertEquals(100, MySwiftLibrary.sumInt32Buffer(buffer)); + } + + @Test + void returnEmptyInt32Buffer() { + SwiftUnsafeBufferPointer buffer = MySwiftLibrary.makeEmptyInt32Buffer(); + assertEquals(0, buffer.getBaseAddress()); + assertEquals(0, buffer.getCount()); + assertEquals(0, MySwiftLibrary.sumInt32Buffer(buffer)); + } + + @Test + void returnMutableInt32Buffer() { + SwiftUnsafeMutableBufferPointer buffer = MySwiftLibrary.makeMutableInt32Buffer(); + + assertNotEquals(0, buffer.getBaseAddress()); + assertEquals(3, buffer.getCount()); + assertEquals(30, MySwiftLibrary.sumMutableInt32Buffer(buffer)); + } + + + @Test + void mutableInt32Buffer_isInitialized() { + SwiftUnsafeMutableBufferPointer buffer = MySwiftLibrary.makeMutableInt32Buffer(); + + assertEquals(5, MySwiftLibrary.mutableInt32BufferElement(buffer, 0)); + assertEquals(10, MySwiftLibrary.mutableInt32BufferElement(buffer, 1)); + assertEquals(15, MySwiftLibrary.mutableInt32BufferElement(buffer, 2)); + } + + @Test + void incrementMutableInt32Buffer_modifiesElements() { + SwiftUnsafeMutableBufferPointer buffer = MySwiftLibrary.makeMutableInt32Buffer(); + + MySwiftLibrary.incrementMutableInt32Buffer(buffer); + + assertEquals(6, MySwiftLibrary.mutableInt32BufferElement(buffer, 0)); + assertEquals(11, MySwiftLibrary.mutableInt32BufferElement(buffer, 1)); + assertEquals(16, MySwiftLibrary.mutableInt32BufferElement(buffer, 2)); + } +} \ No newline at end of file diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java index a7a823064..bc2614e84 100644 --- a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java +++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/UnsafeRawBufferPointerTest.java @@ -44,4 +44,23 @@ void bufferCount_empty() { byte[] input = new byte[] {}; assertEquals(0, MySwiftLibrary.bufferCount(input)); } + + @Test + void returnRawBuffer() { + byte[] expected = new byte[] { 10, 20, 30, 40 }; + + assertArrayEquals(expected, MySwiftLibrary.makeRawBuffer()); + } + + @Test + void returnEmptyRawBuffer() { + assertArrayEquals(new byte[] {}, MySwiftLibrary.makeEmptyRawBuffer()); + } + + @Test + void returnMutableRawBuffer() { + byte[] expected = new byte[] { 5, 10, 15 }; + + assertArrayEquals(expected, MySwiftLibrary.makeMutableRawBuffer()); + } } diff --git a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift index 55ea1fb77..addf5ec7d 100644 --- a/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift +++ b/Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift @@ -719,14 +719,29 @@ struct CdeclLowering { ) case .unsafeBufferPointer, .unsafeMutableBufferPointer: - // Typed pointers are lowered to (raw-pointer, count) pair. + // Typed buffer pointers are lowered to (raw-pointer, count) pair. + let isMutable = knownType.kind == .unsafeMutableBufferPointer - return try lowerResult( - .tuple([ - SwiftTupleElement(label: nil, type: isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer), - SwiftTupleElement(label: nil, type: knownTypes.int), - ]), - outParameterName: outParameterName, + let rawPointerType = isMutable ? knownTypes.unsafeMutableRawPointer : knownTypes.unsafeRawPointer + return LoweredResult( + cdeclResultType: .void, + cdeclOutParameters: makeBufferIndirectReturnParameters(outParameterName, isMutable: isMutable), + conversion: .aggregate( + [ + .populatePointer( + name: "\(outParameterName)_pointer", + to: .initialize( + rawPointerType, + arguments: [LabeledArgument(argument: .member(.placeholder, member: "baseAddress"))], + ), + ), + .populatePointer( + name: "\(outParameterName)_count", + to: .member(.placeholder, member: "count"), + ), + ], + name: outParameterName, + ), ) case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 7b6cb7e56..08a282cb6 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -398,11 +398,20 @@ extension FFMSwift2JavaGenerator { } switch knownType { case .unsafePointer, .unsafeMutablePointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - case .unsafeBufferPointer, .unsafeMutableBufferPointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .javaForeignMemorySegment), + conversion: .placeholder + ) + case .unsafeBufferPointer(let element), .unsafeMutableBufferPointer(let element): + let elementLayout = try CType(cdeclType: element).foreignValueLayout + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: .javaForeignMemorySegment), + conversion: .commaSeparated([ + .placeholder, + // Calculate the element count: buffer size / element size + .constant("\(parameterName).byteSize() / \(elementLayout.description).byteSize()"), + ]) + ) case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedParameter( @@ -739,11 +748,32 @@ extension FFMSwift2JavaGenerator { break // Implemented as wrapper case .unsafePointer, .unsafeMutablePointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) - case .unsafeBufferPointer, .unsafeMutableBufferPointer: - // FIXME: Implement - throw JavaTranslationError.unhandledType(swiftType) + + return TranslatedResult( + javaResultType: .javaForeignMemorySegment, + annotations: resultAnnotations, + outParameters: [], + conversion: .placeholder + ) + case .unsafeBufferPointer(let element), .unsafeMutableBufferPointer(let element): + let elementLayout = try CType(cdeclType: element).foreignValueLayout + return TranslatedResult( + javaResultType: .javaForeignMemorySegment, + annotations: resultAnnotations, + outParameters: [ + JavaParameter(name: "pointer", type: .javaForeignMemorySegment), + JavaParameter(name: "count", type: .long), + ], + conversion: .method( + .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), + methodName: "reinterpret", + arguments: [ + .constant("result$_count.get(SwiftValueLayout.SWIFT_INT64, 0) * \(elementLayout.description).byteSize()") + ], + withArena: false + ) + ) + case .string: return TranslatedResult( javaResultType: .javaLangString, diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift index f3e6e5189..58de8bab6 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift @@ -456,6 +456,33 @@ extension JNISwift2JavaGenerator { case .foundationDate, .essentialsDate, .foundationData, .essentialsData, .foundationURL, .essentialsURL: break // Handled as wrapped struct + case .unsafePointer, .unsafeMutablePointer: + return TranslatedParameter( + parameter: JavaParameter( + name: parameterName, + type: .long + ), + conversion: .placeholder + ) + + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + let isMutable = knownType.kind == .unsafeMutableBufferPointer + let javaBufferType: JavaType = isMutable ? .swiftUnsafeMutableBufferPointer : .swiftUnsafeBufferPointer + return TranslatedParameter( + parameter: JavaParameter(name: parameterName, type: javaBufferType), + conversion: .commaSeparated([ + .method( + .placeholder, + function: "getBaseAddress", + arguments: [] + ), + .method( + .placeholder, + function: "getCount", + arguments: [] + ), + ]) + ) case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedParameter( @@ -935,6 +962,36 @@ extension JNISwift2JavaGenerator { ), ) + case .unsafePointer, .unsafeMutablePointer: + return TranslatedResult( + javaType: .long, + nativeJavaType: .long, + annotations: resultAnnotations, + outParameters: [], + conversion: .placeholder, + ) + + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + let isMutable = knownType.kind == .unsafeMutableBufferPointer + let javaBufferType: JavaType = isMutable ? .swiftUnsafeMutableBufferPointer : .swiftUnsafeBufferPointer + return TranslatedResult( + javaType: javaBufferType, + nativeJavaType: .void, + annotations: resultAnnotations, + outParameters: [.init(name: resultName, type: javaBufferType, allocation: .new)], + conversion: .constant(resultName), + ) + //taenda todo: add support for UnsafeRawBufferPointer and UnsafeMutableRawBufferPointer + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return TranslatedResult( + javaType: .array(.byte), + nativeJavaType: .array(.byte), + annotations: resultAnnotations, + outParameters: [], + conversion: .placeholder, + ) + + default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType.kind, config: self.config) else { throw JavaTranslationError.unsupportedSwiftType(swiftType) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index a262e5fc9..dfd1c899d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -152,6 +152,35 @@ extension JNISwift2JavaGenerator { elementType: element, parameterName: parameterName ) + case .unsafePointer(let pointee), .unsafeMutablePointer(let pointee): + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .long) + ], + conversion: .extractSwiftValue(.placeholder, swiftType: pointee), + indirectConversion: nil, + conversionCheck: nil + ) + + case .unsafeBufferPointer(let element), .unsafeMutableBufferPointer(let element): + let isMutable = knownType.kind == .unsafeMutableBufferPointer + let countParameterName = "\(parameterName)_count" + return NativeParameter( + parameters: [ + JavaParameter(name: parameterName, type: .long), + JavaParameter(name: countParameterName, type: .long), + ], + conversion: .constructUnsafeBufferPointer( + base: .extractSwiftValue(.placeholder, swiftType: element, allowNil: true), + count: .labelessInitializer( + .initFromJNI(.constant(countParameterName), swiftType: self.knownTypes.int64), + swiftType: self.knownTypes.int + ), + mutable: isMutable + ), + indirectConversion: nil, + conversionCheck: nil + ) case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: let isMutable = knownType.kind == .unsafeMutableRawBufferPointer @@ -829,6 +858,27 @@ extension JNISwift2JavaGenerator { outParameters: [] ) + case .unsafePointer, .unsafeMutablePointer: + return NativeResult( + javaType: .long, + conversion: .getJNIValue(.convertToBitPattern(.placeholder)), + outParameters: [] + ) + + case .unsafeBufferPointer, .unsafeMutableBufferPointer: + let isMutable = knownType.kind == .unsafeMutableBufferPointer + let javaBufferType: JavaType = isMutable ? .swiftUnsafeMutableBufferPointer : .swiftUnsafeBufferPointer + return NativeResult( + javaType: .void, + conversion: .bufferPointerIndirectReturn(.placeholder, outArgumentName: resultName + "Out"), + outParameters: [JavaParameter(name: resultName + "Out", type: javaBufferType)] + ) + case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: + return NativeResult( + javaType: .array(.byte), + conversion: .getJNIValue(.rawBufferPointerToByteArray(.placeholder)), + outParameters: [] + ) default: guard let javaType = JNIJavaTypeTranslator.translate(knownType: knownType.kind, config: self.config), javaType.implementsJavaValue @@ -1364,6 +1414,10 @@ extension JNISwift2JavaGenerator { /// of the `Unsafe(Mutable)Pointer` types in Swift. indirect case pointee(NativeSwiftConversionStep) + indirect case convertToBitPattern(NativeSwiftConversionStep) + + indirect case constructUnsafeBufferPointer(base: NativeSwiftConversionStep, count: NativeSwiftConversionStep, mutable: Bool) + indirect case closureLowering(parameters: [NativeParameter], result: NativeResult) /// Escaping closure lowering using the protocol infrastructure. @@ -1408,6 +1462,11 @@ extension JNISwift2JavaGenerator { outArgumentName: String ) + indirect case bufferPointerIndirectReturn( + NativeSwiftConversionStep, + outArgumentName: String + ) + indirect case constructor( _ swiftType: SwiftType, arguments: [(String?, NativeSwiftConversionStep)] = [] @@ -1450,6 +1509,9 @@ extension JNISwift2JavaGenerator { /// Converts a jbyteArray to UnsafeRawBufferPointer or UnsafeMutableRawBufferPointer via GetByteArrayElements indirect case jniByteArrayToUnsafeRawBufferPointer(NativeSwiftConversionStep, name: String, mutable: Bool) + /// Converts an UnsafeRawBufferPointer or UnsafeMutableRawBufferPointer into a [UInt8] array for returned JNI byte[] values. + indirect case rawBufferPointerToByteArray(NativeSwiftConversionStep) + /// Constructs a Swift tuple from individually-converted elements. /// E.g. `(label0: conv0, conv1)` for `(label0: Int, String)` indirect case tupleConstruct(elements: [(label: String?, conversion: NativeSwiftConversionStep)]) @@ -1629,6 +1691,10 @@ extension JNISwift2JavaGenerator { ) return bitsName + case .convertToBitPattern(let inner): + let inner = inner.render(&printer, placeholder) + return "Int64(Int(bitPattern: \(inner)))" + case .allocateExistentialValue(let inner, let name, let protocolTypes): let inner = inner.render(&printer, placeholder) let existentialType = SwiftKitPrinting.renderExistentialType(protocolTypes) @@ -1667,6 +1733,12 @@ extension JNISwift2JavaGenerator { let inner = inner.render(&printer, placeholder) return "\(inner).pointee" + case .constructUnsafeBufferPointer(let base, let count, let mutable): + let base = base.render(&printer, placeholder) + let count = count.render(&printer, placeholder) + let bufferTypeName: String = mutable ? "UnsafeMutableBufferPointer" : "UnsafeBufferPointer" + return "\(bufferTypeName)(start: \(base), count: \(count))" + case .closureLowering(let parameters, let nativeResult): var printer = SwiftPrinter() @@ -1876,6 +1948,20 @@ extension JNISwift2JavaGenerator { } return "" + case .bufferPointerIndirectReturn(let inner, let outArgumentName): + let inner = inner.render(&printer, placeholder) + printer.printBraceBlock("do") { printer in + printer.print( + """ + let baseAddressBits$ = Int64(Int(bitPattern: \(inner).baseAddress)) + environment.interface.SetLongField(environment, \(outArgumentName), _JNIMethodIDCache.SwiftUnsafeBufferPointer.baseAddress, baseAddressBits$.getJNIValue(in: environment)) + let countBits$ = Int64(\(inner).count) + environment.interface.SetLongField(environment, \(outArgumentName), _JNIMethodIDCache.SwiftUnsafeBufferPointer.count, countBits$.getJNIValue(in: environment)) + """ + ) + } + return "" + case .constructor(let swiftType, let arguments): let args = arguments.map { name, value in let value = value.render(&printer, placeholder) @@ -2112,6 +2198,10 @@ extension JNISwift2JavaGenerator { ) return rbpVar + case .rawBufferPointerToByteArray(let inner): + let inner = inner.render(&printer, placeholder) + return "[UInt8](\(inner))" + case .tupleConstruct(let elements): let parts = elements.enumerated().map { idx, element in let converted = element.conversion.render(&printer, "\(placeholder)_\(idx)") diff --git a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift index 850768c25..447421d4b 100644 --- a/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift +++ b/Sources/JExtractSwiftLib/JavaTypes/JavaType+SwiftKit.swift @@ -21,4 +21,14 @@ extension JavaType { .class(package: "org.swift.swiftkit.core", name: "_OutSwiftGenericInstance") } + /// A base address and element count pair + static var swiftUnsafeBufferPointer: JavaType { + .class(package: "org.swift.swiftkit.core", name: "SwiftUnsafeBufferPointer") + } + + /// A mutable base address and element count pair + static var swiftUnsafeMutableBufferPointer: JavaType { + .class(package: "org.swift.swiftkit.core", name: "SwiftUnsafeMutableBufferPointer") + } + } diff --git a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift index b938def03..fac78ea2b 100644 --- a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift @@ -240,4 +240,62 @@ extension _JNIMethodIDCache { cache.fields[selfTypePointerField]! } } + + public enum SwiftUnsafeBufferPointer { + private static let baseAddressField = Field( + name: "baseAddress", + signature: "J" + ) + + private static let countField = Field( + name: "count", + signature: "J" + ) + + private static let cache = _JNIMethodIDCache( + className: "org/swift/swiftkit/core/SwiftUnsafeBufferPointer", + fields: [baseAddressField, countField] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var baseAddress: jfieldID { + cache.fields[baseAddressField]! + } + + public static var count: jfieldID { + cache.fields[countField]! + } + } + + public enum SwiftUnsafeMutableBufferPointer { + private static let baseAddressField = Field( + name: "baseAddress", + signature: "J" + ) + + private static let countField = Field( + name: "count", + signature: "J" + ) + + private static let cache = _JNIMethodIDCache( + className: "org/swift/swiftkit/core/SwiftUnsafeMutableBufferPointer", + fields: [baseAddressField, countField] + ) + + public static var `class`: jclass { + cache.javaClass + } + + public static var baseAddress: jfieldID { + cache.fields[baseAddressField]! + } + + public static var count: jfieldID { + cache.fields[countField]! + } + } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftUnsafeBufferPointer.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftUnsafeBufferPointer.java new file mode 100644 index 000000000..97fcd4eef --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftUnsafeBufferPointer.java @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 org.swift.swiftkit.core; + +/** + * Corresponds to Swift's {@code UnsafeBufferPointer} + */ +public final class SwiftUnsafeBufferPointer { + /** Address of the first element, as a raw pointer bit pattern. */ + private long baseAddress; + + /** Number of elements in the buffer */ + private long count; + + public SwiftUnsafeBufferPointer() { + this(0, 0); + } + + /** + * @param baseAddress address of the first element, as a raw pointer bit pattern + * @param count number of elements in the buffer + */ + public SwiftUnsafeBufferPointer(long baseAddress, long count) { + this.baseAddress = baseAddress; + this.count = count; + } + + /** + * @return address of the first element, as a raw pointer bit pattern + */ + public long getBaseAddress() { + return baseAddress; + } + + /** + * @return number of elements in the buffer + */ + public long getCount() { + return count; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof SwiftUnsafeBufferPointer)) return false; + SwiftUnsafeBufferPointer o = (SwiftUnsafeBufferPointer) other; + return this.baseAddress == o.baseAddress && this.count == o.count; + } + + @Override + public int hashCode() { + return java.util.Objects.hash(baseAddress, count); + } + + @Override + public String toString() { + return "SwiftUnsafeBufferPointer(baseAddress=0x" + Long.toHexString(baseAddress) + ", count=" + count + ")"; + } +} diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftUnsafeMutableBufferPointer.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftUnsafeMutableBufferPointer.java new file mode 100644 index 000000000..282f042f8 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftUnsafeMutableBufferPointer.java @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 org.swift.swiftkit.core; + +/** + * Corresponds to Swift's {@code UnsafeMutableBufferPointer}. + */ +public final class SwiftUnsafeMutableBufferPointer { + /** Address of the first element, as a raw pointer bit pattern. */ + private long baseAddress; + + /** Number of elements in the buffer.*/ + private long count; + + public SwiftUnsafeMutableBufferPointer() { + this(0, 0); + } + + /** + * @param baseAddress address of the first element, as a raw pointer bit pattern + * @param count number of elements in the buffer + */ + public SwiftUnsafeMutableBufferPointer(long baseAddress, long count) { + this.baseAddress = baseAddress; + this.count = count; + } + + /** + * @return address of the first element, as a raw pointer bit pattern + */ + public long getBaseAddress() { + return baseAddress; + } + + /** + * @return number of elements in the buffer + */ + public long getCount() { + return count; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof SwiftUnsafeMutableBufferPointer)) return false; + SwiftUnsafeMutableBufferPointer o = (SwiftUnsafeMutableBufferPointer) other; + return this.baseAddress == o.baseAddress && this.count == o.count; + } + + @Override + public int hashCode() { + return java.util.Objects.hash(baseAddress, count); + } + + @Override + public String toString() { + return "SwiftUnsafeMutableBufferPointer(baseAddress=0x" + Long.toHexString(baseAddress) + ", count=" + count + ")"; + } +} diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/BufferPointers.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/BufferPointers.java new file mode 100644 index 000000000..fbd299ab9 --- /dev/null +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/BufferPointers.java @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 org.swift.swiftkit.ffm; + +import org.swift.swiftkit.core.SwiftUnsafeBufferPointer; +import org.swift.swiftkit.core.SwiftUnsafeMutableBufferPointer; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.ByteBuffer; + +/** + * Conversions between {@code SwiftUnsafe(Mutable)BufferPointer} and the Java Foreign Function + * & Memory API. + *

+ * These types are zero-copy views: the {@code baseAddress} they carry is already a valid pointer + * into the current process' address space, so no data needs to be copied out of Swift to access + * it from Java. + */ +public class BufferPointers { + + /** + * Reinterprets the given buffer's {@code baseAddress} as a {@link MemorySegment} covering + * exactly {@code count * elementLayout.byteSize()} bytes. + * + *

the returned segment is valid for only as long as that memory is. + * + * @param buffer the buffer pointer to view + * @param elementLayout the layout of the buffer's element type, e.g. {@link SwiftValueLayout#SWIFT_INT32} + * @return a zero-copy {@link MemorySegment} view of the buffer's contents + */ + private static MemorySegment toMemorySegment(SwiftUnsafeBufferPointer buffer, ValueLayout elementLayout) { + long byteSize = buffer.getCount() * elementLayout.byteSize(); + if (byteSize == 0) { + return MemorySegment.NULL; + } + return MemorySegment.ofAddress(buffer.getBaseAddress()).reinterpret(byteSize); + } + + /** + * Reinterprets the given mutable buffer's {@code baseAddress} as a {@link MemorySegment} + * covering exactly {@code count * elementLayout.byteSize()} bytes. + * + *

the returned segment is valid for only as long as that memory is. + * + * @param buffer the buffer pointer to view + * @param elementLayout the layout of the buffer's element type, e.g. {@link SwiftValueLayout#SWIFT_INT32} + * @return a zero-copy {@link MemorySegment} view of the buffer's contents + */ + private static MemorySegment toMemorySegment(SwiftUnsafeMutableBufferPointer buffer, ValueLayout elementLayout) { + long byteSize = buffer.getCount() * elementLayout.byteSize(); + if (byteSize == 0) { + return MemorySegment.NULL; + } + return MemorySegment.ofAddress(buffer.getBaseAddress()).reinterpret(byteSize); + } + + /** + * A {@link ByteBuffer} view of the given buffer's contents, backed by the same native memory. + * + * @param buffer the buffer pointer to view + * @param elementLayout the layout of the buffer's element type, e.g. {@link SwiftValueLayout#SWIFT_INT32} + * @return a zero-copy {@link ByteBuffer} view of the buffer's contents + */ + public static ByteBuffer toByteBuffer(SwiftUnsafeBufferPointer buffer, ValueLayout elementLayout) { + MemorySegment segment = toMemorySegment(buffer, elementLayout); + return segment == MemorySegment.NULL ? ByteBuffer.allocateDirect(0).asReadOnlyBuffer() : segment.asReadOnly().asByteBuffer(); + } + + /** + * A {@link ByteBuffer} view of the given mutable buffer's contents, backed by the same native memory. + * + * @param buffer the buffer pointer to view + * @param elementLayout the layout of the buffer's element type, e.g. {@link SwiftValueLayout#SWIFT_INT32} + * @return a zero-copy {@link ByteBuffer} view of the buffer's contents + */ + public static ByteBuffer toByteBuffer(SwiftUnsafeMutableBufferPointer buffer, ValueLayout elementLayout) { + MemorySegment segment = toMemorySegment(buffer, elementLayout); + return segment == MemorySegment.NULL ? ByteBuffer.allocateDirect(0) : segment.asByteBuffer(); + } +} diff --git a/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/BufferPointersTest.java b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/BufferPointersTest.java new file mode 100644 index 000000000..16d8622f1 --- /dev/null +++ b/SwiftKitFFM/src/test/java/org/swift/swiftkit/ffm/BufferPointersTest.java @@ -0,0 +1,99 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 org.swift.swiftkit.ffm; + +import org.junit.jupiter.api.Test; +import org.swift.swiftkit.core.SwiftUnsafeBufferPointer; +import org.swift.swiftkit.core.SwiftUnsafeMutableBufferPointer; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.ReadOnlyBufferException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BufferPointersTest { + + @Test + public void toByteBuffer_viewsUnderlyingInt32Elements() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment native_ = arena.allocate(SwiftValueLayout.SWIFT_INT32, 3); + native_.setAtIndex(SwiftValueLayout.SWIFT_INT32, 0, 10); + native_.setAtIndex(SwiftValueLayout.SWIFT_INT32, 1, 20); + native_.setAtIndex(SwiftValueLayout.SWIFT_INT32, 2, 30); + + var buffer = new SwiftUnsafeBufferPointer(native_.address(), 3); + + ByteBuffer byteBuffer = BufferPointers.toByteBuffer(buffer, SwiftValueLayout.SWIFT_INT32); + byteBuffer.order(java.nio.ByteOrder.nativeOrder()); + + assertEquals(12, byteBuffer.capacity()); + assertEquals(10, byteBuffer.asIntBuffer().get(0)); + assertEquals(20, byteBuffer.asIntBuffer().get(1)); + assertEquals(30, byteBuffer.asIntBuffer().get(2)); + } + } + + @Test + public void toByteBuffer_emptyBuffer_isEmpty() { + var buffer = new SwiftUnsafeBufferPointer(0, 0); + ByteBuffer byteBuffer = BufferPointers.toByteBuffer(buffer, SwiftValueLayout.SWIFT_INT32); + assertEquals(0, byteBuffer.capacity()); + } + + @Test + public void toByteBuffer_mutableBuffer_isWritableThrough() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment native_ = arena.allocate(SwiftValueLayout.SWIFT_INT8, 4); + + var buffer = new SwiftUnsafeMutableBufferPointer(native_.address(), 4); + ByteBuffer byteBuffer = BufferPointers.toByteBuffer(buffer, SwiftValueLayout.SWIFT_INT8); + byteBuffer.put(0, (byte) 42); + + assertEquals(42, native_.get(SwiftValueLayout.SWIFT_INT8, 0)); + } + } + + @Test + public void toByteBuffer_immutableBuffer_throwsOnWriteAttempt() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment native_ = arena.allocate(SwiftValueLayout.SWIFT_INT32, 2); + var buffer = new SwiftUnsafeBufferPointer(native_.address(), 2); + + ByteBuffer byteBuffer = BufferPointers.toByteBuffer(buffer, SwiftValueLayout.SWIFT_INT32); + + assertTrue(byteBuffer.isReadOnly()); + assertThrows(ReadOnlyBufferException.class, () -> byteBuffer.put(0, (byte) 1)); + } + } + + @Test + public void toByteBuffer_immutableBuffer_subViewsAreAlsoReadOnly() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment native_ = arena.allocate(SwiftValueLayout.SWIFT_INT32, 2); + var buffer = new SwiftUnsafeBufferPointer(native_.address(), 2); + + ByteBuffer byteBuffer = BufferPointers.toByteBuffer(buffer, SwiftValueLayout.SWIFT_INT32); + IntBuffer intBuffer = byteBuffer.asIntBuffer(); + + assertTrue(intBuffer.isReadOnly()); + assertThrows(ReadOnlyBufferException.class, () -> intBuffer.put(0, 100)); + } + } +} diff --git a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift index 4cfb7bcf5..cf57c5f6b 100644 --- a/Tests/JExtractSwiftTests/FunctionLoweringTests.swift +++ b/Tests/JExtractSwiftTests/FunctionLoweringTests.swift @@ -322,13 +322,13 @@ final class FunctionLoweringTests { """, expectedCDecl: """ @_cdecl("c_getBufferPointer") - public func c_getBufferPointer(_ _result_0: UnsafeMutablePointer, _ _result_1: UnsafeMutablePointer) { + public func c_getBufferPointer(_ _result_pointer: UnsafeMutablePointer, _ _result_count: UnsafeMutablePointer) { let _result = getBufferPointer() - _result_0.initialize(to: _result.0) - _result_1.initialize(to: _result.1) + _result_pointer.initialize(to: UnsafeMutableRawPointer(_result.baseAddress)) + _result_count.initialize(to: _result.count) } """, - expectedCFunction: "void c_getBufferPointer(void **_result_0, ptrdiff_t *_result_1)", + expectedCFunction: "void c_getBufferPointer(void **_result_pointer, ptrdiff_t *_result_count)", ) } diff --git a/Tests/JExtractSwiftTests/JNI/JNIPointerTests.swift b/Tests/JExtractSwiftTests/JNI/JNIPointerTests.swift new file mode 100644 index 000000000..745aa1f0a --- /dev/null +++ b/Tests/JExtractSwiftTests/JNI/JNIPointerTests.swift @@ -0,0 +1,246 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 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 +// +//===----------------------------------------------------------------------===// + +import JExtractSwiftLib +import Testing + +@Suite +struct JNIPointerTests { + let pointerSource = + """ + public struct MyStruct { + public var x: Int32 = 0 + } + + public func globalTakeUnsafePointer(p: UnsafePointer) {} + """ + + let bufferPointerSource = + """ + public func globalTakeUnsafeBufferPointer(buffer: UnsafeBufferPointer) {} + """ + + let returnPointerSource = + """ + public struct MyStruct { + public var x: Int32 = 0 + } + + public func globalReturnUnsafePointer() -> UnsafePointer { + fatalError() + } + """ + + let returnBufferPointerSource = + """ + public func globalReturnUnsafeBufferPointer() -> UnsafeBufferPointer { + fatalError() + } + """ + + let rawBufferPointerSource = + """ + public func globalTakeUnsafeRawBufferPointer(buffer: UnsafeRawBufferPointer) {} + """ + + let returnRawBufferPointerSource = + """ + public func globalReturnUnsafeRawBufferPointer() -> UnsafeRawBufferPointer { + fatalError() + } + """ + + @Test + func takeUnsafePointer_javaBindings() throws { + try assertOutput( + input: pointerSource, + .jni, + .java, + expectedChunks: [ + "public static void globalTakeUnsafePointer(long p) {", + "private static native void $globalTakeUnsafePointer(long p);", + ] + ) + } + + @Test + func takeUnsafePointer_swiftThunks() throws { + try assertOutput( + input: pointerSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024globalTakeUnsafePointer__J") + public func Java_com_example_swift_SwiftModule__00024globalTakeUnsafePointer__J(environment: UnsafeMutablePointer!, thisClass: jclass, p: jlong) { + assert(p != 0, "p memory address was null") + let pBits$ = Int(Int64(fromJNI: p, in: environment)) + let p$ = UnsafeMutablePointer(bitPattern: pBits$) + guard let p$ else { + fatalError("p memory address was null in call to \\(#function)!") + } + SwiftModule.globalTakeUnsafePointer(p: p$) + } + """ + ] + ) + } + + @Test + func takeUnsafeBufferPointer_javaBindings() throws { + try assertOutput( + input: bufferPointerSource, + .jni, + .java, + expectedChunks: [ + "public static void globalTakeUnsafeBufferPointer(org.swift.swiftkit.core.SwiftUnsafeBufferPointer buffer) {", + "private static native void $globalTakeUnsafeBufferPointer(long buffer, long buffer_count);", + ] + ) + } + + @Test + func takeUnsafeBufferPointer_swiftThunks() throws { + try assertOutput( + input: bufferPointerSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024globalTakeUnsafeBufferPointer__JJ") + public func Java_com_example_swift_SwiftModule__00024globalTakeUnsafeBufferPointer__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, buffer: jlong, buffer_count: jlong) { + let bufferBits$ = Int(Int64(fromJNI: buffer, in: environment)) + let buffer$ = UnsafeMutablePointer(bitPattern: bufferBits$) + SwiftModule.globalTakeUnsafeBufferPointer(buffer: UnsafeBufferPointer(start: buffer$, count: Int(Int64(fromJNI: buffer_count, in: environment)))) + } + """ + ] + ) + } + + @Test + func returnUnsafePointer_javaBindings() throws { + try assertOutput( + input: returnPointerSource, + .jni, + .java, + expectedChunks: [ + "public static long globalReturnUnsafePointer() {", + "private static native long $globalReturnUnsafePointer();", + ] + ) + } + + @Test + func returnUnsafePointer_swiftThunks() throws { + try assertOutput( + input: returnPointerSource, + .jni, + .swift, + expectedChunks: [ + """ + @_cdecl("Java_com_example_swift_SwiftModule__00024globalReturnUnsafePointer__") + public func Java_com_example_swift_SwiftModule__00024globalReturnUnsafePointer__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jlong { + return Int64(Int(bitPattern: SwiftModule.globalReturnUnsafePointer())).getJNILocalRefValue(in: environment) + } + """ + ] + ) + } + + @Test + func returnUnsafeBufferPointer_javaBindings() throws { + try assertOutput( + input: returnBufferPointerSource, + .jni, + .java, + expectedChunks: [ + "public static org.swift.swiftkit.core.SwiftUnsafeBufferPointer globalReturnUnsafeBufferPointer() {", + "private static native void $globalReturnUnsafeBufferPointer(org.swift.swiftkit.core.SwiftUnsafeBufferPointer resultOut);", + ] + ) + } + + @Test + func returnUnsafeBufferPointer_swiftThunks() throws { + try assertOutput( + input: returnBufferPointerSource, + .jni, + .swift, + expectedChunks: [ + """ + do { + let baseAddressBits$ = Int64(Int(bitPattern: SwiftModule.globalReturnUnsafeBufferPointer().baseAddress)) + environment.interface.SetLongField(environment, resultOut, _JNIMethodIDCache.SwiftUnsafeBufferPointer.baseAddress, baseAddressBits$.getJNIValue(in: environment)) + let countBits$ = Int64(SwiftModule.globalReturnUnsafeBufferPointer().count) + environment.interface.SetLongField(environment, resultOut, _JNIMethodIDCache.SwiftUnsafeBufferPointer.count, countBits$.getJNIValue(in: environment)) + } + """ + ] + ) + } + + @Test + func takeUnsafeRawBufferPointer_javaBindings() throws { + try assertOutput( + input: rawBufferPointerSource, + .jni, + .java, + expectedChunks: [ + "public static void globalTakeUnsafeRawBufferPointer(byte[] buffer) {", + "private static native void $globalTakeUnsafeRawBufferPointer(byte[] buffer);", + ] + ) + } + + @Test + func takeUnsafeRawBufferPointer_swiftThunks() throws { + try assertOutput( + dump: false, + input: rawBufferPointerSource, + .jni, + .swift, + expectedChunks: [ + "public func Java_com_example_swift_SwiftModule__00024globalTakeUnsafeRawBufferPointer___3B(environment: UnsafeMutablePointer!, thisClass: jclass, buffer: jbyteArray?) {" + ] + ) + } + + @Test + func returnUnsafeRawBufferPointer_javaBindings() throws { + try assertOutput( + dump: false, + input: returnRawBufferPointerSource, + .jni, + .java, + expectedChunks: [ + "public static byte[] globalReturnUnsafeRawBufferPointer() {" + ] + ) + } + + @Test + func returnUnsafeRawBufferPointer_swiftThunks() throws { + try assertOutput( + dump: false, + input: returnRawBufferPointerSource, + .jni, + .swift, + expectedChunks: [ + "public func Java_com_example_swift_SwiftModule__00024globalReturnUnsafeRawBufferPointer__(environment: UnsafeMutablePointer!, thisClass: jclass) -> jbyteArray? {", + "return [UInt8](SwiftModule.globalReturnUnsafeRawBufferPointer()).getJNILocalRefValue(in: environment)", + ] + ) + } +} diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 137b7e566..b7002442c 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -49,6 +49,14 @@ final class MethodImportTests { public func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer + public func globalTakeUnsafePointer(p: UnsafePointer) + + public func globalReturnUnsafePointer() -> UnsafePointer + + public func globalTakeUnsafeBufferPointer(buffer: UnsafeBufferPointer) + + public func globalReturnUnsafeBufferPointer() -> UnsafeBufferPointer + extension MySwiftClass { public func helloMemberInExtension() } @@ -300,6 +308,187 @@ final class MethodImportTests { ) } + @Test("Import: func globalTakeUnsafePointer(p: UnsafePointer)") + func func_globalTakeUnsafePointer() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = makeSwiftJavaAnalyzer(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: class_interfaceFile) + + let funcDecl = try #require( + st.extractedGlobalFuncs.first { + $0.name == "globalTakeUnsafePointer" + } + ) + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = JavaPrinter.toString { printer in + generator.printJavaBindingWrapperMethod(&printer, funcDecl) + } + + assertOutput( + output, + expected: + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalTakeUnsafePointer(p: UnsafePointer) + * } + */ + public static void globalTakeUnsafePointer(java.lang.foreign.MemorySegment p) { + swiftjava___FakeModule_globalTakeUnsafePointer_p.call(p); + } + """ + ) + } + + @Test("Import: func globalReturnUnsafePointer() -> UnsafePointer") + func func_globalReturnUnsafePointer() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = makeSwiftJavaAnalyzer(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: class_interfaceFile) + + let funcDecl = try #require( + st.extractedGlobalFuncs.first { + $0.name == "globalReturnUnsafePointer" + } + ) + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = JavaPrinter.toString { printer in + generator.printJavaBindingWrapperMethod(&printer, funcDecl) + } + + assertOutput( + output, + expected: + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalReturnUnsafePointer() -> UnsafePointer + * } + */ + public static java.lang.foreign.MemorySegment globalReturnUnsafePointer() { + return swiftjava___FakeModule_globalReturnUnsafePointer.call(); + } + """ + ) + } + + @Test("Import: func globalTakeUnsafeBufferPointer(buffer: UnsafeBufferPointer)") + func func_globalTakeUnsafeBufferPointer() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = makeSwiftJavaAnalyzer(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: class_interfaceFile) + + let funcDecl = try #require( + st.extractedGlobalFuncs.first { + $0.name == "globalTakeUnsafeBufferPointer" + } + ) + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = JavaPrinter.toString { printer in + generator.printJavaBindingWrapperMethod(&printer, funcDecl) + } + + assertOutput( + output, + expected: + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalTakeUnsafeBufferPointer(buffer: UnsafeBufferPointer) + * } + */ + public static void globalTakeUnsafeBufferPointer(java.lang.foreign.MemorySegment buffer) { + swiftjava___FakeModule_globalTakeUnsafeBufferPointer_buffer.call(buffer, buffer.byteSize() / SwiftValueLayout.SWIFT_INT32.byteSize()); + } + """ + ) + } + + @Test("Import: func globalReturnUnsafeBufferPointer() -> UnsafeBufferPointer") + func func_globalReturnUnsafeBufferPointer() throws { + var config = Configuration() + config.swiftModule = "__FakeModule" + let st = makeSwiftJavaAnalyzer(config: config) + st.log.logLevel = .error + + try st.analyze(path: "Fake.swift", text: class_interfaceFile) + + let funcDecl = try #require( + st.extractedGlobalFuncs.first { + $0.name == "globalReturnUnsafeBufferPointer" + } + ) + + let generator = FFMSwift2JavaGenerator( + config: config, + translator: st, + javaPackage: "com.example.swift", + swiftOutputDirectory: "/fake", + javaOutputDirectory: "/fake" + ) + + let output = JavaPrinter.toString { printer in + generator.printJavaBindingWrapperMethod(&printer, funcDecl) + } + + assertOutput( + output, + expected: + """ + /** + * Downcall to Swift: + * {@snippet lang=swift : + * public func globalReturnUnsafeBufferPointer() -> UnsafeBufferPointer + * } + */ + public static java.lang.foreign.MemorySegment globalReturnUnsafeBufferPointer() { + try(var arena$ = Arena.ofConfined()) { + MemorySegment result$_pointer = arena$.allocate(SwiftValueLayout.SWIFT_POINTER); + MemorySegment result$_count = arena$.allocate(SwiftValueLayout.SWIFT_INT64); + swiftjava___FakeModule_globalReturnUnsafeBufferPointer.call(result$_pointer, result$_count); + return result$_pointer.get(SwiftValueLayout.SWIFT_POINTER, 0).reinterpret(result$_count.get(SwiftValueLayout.SWIFT_INT64, 0) * SwiftValueLayout.SWIFT_INT32.byteSize()); + } + } + """ + ) + } + @Test func method_class_helloMemberFunction() throws { var config = Configuration()