Skip to content

Commit b7c8b2e

Browse files
committed
jextract/ffm: support [UInt8] parameters, although it causes copying
1 parent 7ebccdd commit b7c8b2e

File tree

11 files changed

+293
-45
lines changed

11 files changed

+293
-45
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
error id: file://<WORKSPACE>/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java:_empty_/MySwiftLibrary#sumAllByteArrayElements#
2+
file://<WORKSPACE>/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java
3+
empty definition using pc, found symbol in pc: _empty_/MySwiftLibrary#sumAllByteArrayElements#
4+
empty definition using semanticdb
5+
empty definition using fallback
6+
non-local guesses:
7+
8+
offset: 2471
9+
uri: file://<WORKSPACE>/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java
10+
text:
11+
```scala
12+
//===----------------------------------------------------------------------===//
13+
//
14+
// This source file is part of the Swift.org open source project
15+
//
16+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
17+
// Licensed under Apache License v2.0
18+
//
19+
// See LICENSE.txt for license information
20+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
21+
//
22+
// SPDX-License-Identifier: Apache-2.0
23+
//
24+
//===----------------------------------------------------------------------===//
25+
26+
package com.example.swift;
27+
28+
import org.junit.jupiter.api.Test;
29+
import org.swift.swiftkit.core.*;
30+
import org.swift.swiftkit.ffm.*;
31+
32+
import static org.junit.jupiter.api.Assertions.*;
33+
34+
import java.lang.foreign.ValueLayout;
35+
import java.util.Arrays;
36+
import java.util.concurrent.atomic.AtomicLong;
37+
import java.util.stream.IntStream;
38+
39+
public class WithBufferTest {
40+
@Test
41+
void test_withBuffer() {
42+
AtomicLong bufferSize = new AtomicLong();
43+
MySwiftLibrary.withBuffer((buf) -> {
44+
CallTraces.trace("withBuffer{$0.byteSize()}=" + buf.byteSize());
45+
bufferSize.set(buf.byteSize());
46+
});
47+
48+
assertEquals(124, bufferSize.get());
49+
}
50+
51+
@Test
52+
void test_sumAllByteArrayElements_throughMemorySegment() {
53+
byte[] bytes = new byte[124];
54+
Arrays.fill(bytes, (byte) 1);
55+
56+
try (var arena = AllocatingSwiftArena.ofConfined()) {
57+
// NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed segment and therefore cannot pass into native:
58+
// java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 }
59+
// MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!)
60+
// MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length);
61+
62+
var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes);
63+
var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length);
64+
65+
System.out.println("swiftSideSum = " + swiftSideSum);
66+
67+
int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum();
68+
assertEquals(javaSideSum, swiftSideSum);
69+
}
70+
}
71+
72+
@Test
73+
void test_sumAllByteArrayElements_arrayCopy() {
74+
byte[] bytes = new byte[124];
75+
Arrays.fill(bytes, (byte) 1);
76+
77+
var swiftSideSum = MySwiftLibrary.su@@mAllByteArrayElements(bytes);
78+
79+
System.out.println("swiftSideSum = " + swiftSideSum);
80+
81+
int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum();
82+
assertEquals(javaSideSum, swiftSideSum);
83+
}
84+
}
85+
86+
```
87+
88+
89+
#### Short summary:
90+
91+
empty definition using pc, found symbol in pc: _empty_/MySwiftLibrary#sumAllByteArrayElements#

.metals/metals.lock.db

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#FileLock
2+
#Wed Dec 03 15:50:01 JST 2025
3+
hostName=localhost
4+
id=19ae2f9a02b142bf1411bbc8ae7065f1d038919ce32
5+
method=file
6+
server=localhost\:64724

.metals/metals.mv.db

40 KB
Binary file not shown.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
package com.example.swift;
16+
17+
import org.junit.jupiter.api.Test;
18+
import org.swift.swiftkit.core.*;
19+
import org.swift.swiftkit.ffm.*;
20+
21+
import static org.junit.jupiter.api.Assertions.*;
22+
23+
import java.lang.foreign.ValueLayout;
24+
import java.util.Arrays;
25+
import java.util.concurrent.atomic.AtomicLong;
26+
import java.util.stream.IntStream;
27+
28+
public class FFMArraysTest {
29+
30+
@Test
31+
void test_sumAllByteArrayElements_throughMemorySegment() {
32+
byte[] bytes = new byte[124];
33+
Arrays.fill(bytes, (byte) 1);
34+
35+
try (var arena = AllocatingSwiftArena.ofConfined()) {
36+
// NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed segment and therefore cannot pass into native:
37+
// java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 }
38+
// MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!)
39+
// MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length);
40+
41+
var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes);
42+
var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length);
43+
44+
System.out.println("swiftSideSum = " + swiftSideSum);
45+
46+
int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum();
47+
assertEquals(javaSideSum, swiftSideSum);
48+
}
49+
}
50+
51+
@Test
52+
void test_sumAllByteArrayElements_arrayCopy() {
53+
byte[] bytes = new byte[124];
54+
Arrays.fill(bytes, (byte) 1);
55+
56+
var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytes);
57+
58+
System.out.println("swiftSideSum = " + swiftSideSum);
59+
60+
int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum();
61+
assertEquals(javaSideSum, swiftSideSum);
62+
}
63+
}

Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,4 @@ void test_withBuffer() {
3636

3737
assertEquals(124, bufferSize.get());
3838
}
39-
40-
@Test
41-
void test_sumAllByteArrayElements_throughMemorySegment() {
42-
byte[] bytes = new byte[124];
43-
Arrays.fill(bytes, (byte) 1);
44-
45-
try (var arena = AllocatingSwiftArena.ofConfined()) {
46-
// NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed segment and therefore cannot pass into native:
47-
// java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 }
48-
// MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!)
49-
// MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length);
50-
51-
var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes);
52-
var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length);
53-
54-
System.out.println("swiftSideSum = " + swiftSideSum);
55-
56-
int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum();
57-
assertEquals(javaSideSum, swiftSideSum);
58-
}
59-
}
60-
}
39+
}

Sources/JExtractSwiftLib/FFM/CDeclLowering/CRepresentation.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,12 @@ extension SwiftKnownTypeDeclKind {
124124
case .unsafeRawPointer: .pointer(
125125
.qualified(const: true, volatile: false, type: .void)
126126
)
127+
case .array:
128+
.pointer(.qualified(const: false, volatile: false, type: .void))
127129
case .void: .void
128130
case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
129131
.unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol,
130-
.essentialsData, .essentialsDataProtocol, .optional, .array:
132+
.essentialsData, .essentialsDataProtocol, .optional:
131133
nil
132134
}
133135
}

Sources/JExtractSwiftLib/FFM/CDeclLowering/FFMSwift2JavaGenerator+FunctionLowering.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,55 @@ struct CdeclLowering {
348348
case .composite:
349349
throw LoweringError.unhandledType(type)
350350

351+
case .array(.nominal(let nominal)):
352+
// Lower an array as 'address' raw pointer and 'count' integer
353+
var parameters: [SwiftParameter] = [
354+
355+
]
356+
357+
// Create parameter names with consistent naming convention
358+
let pointerParameterName = "\(parameterName)_pointer"
359+
let countParameterName = "\(parameterName)_count"
360+
361+
// Build C declaration parameters for pointer and count
362+
let cdeclParameters = [
363+
SwiftParameter(
364+
convention: .byValue,
365+
parameterName: pointerParameterName,
366+
type: knownTypes.unsafeRawPointer
367+
),
368+
SwiftParameter(
369+
convention: .byValue,
370+
parameterName: countParameterName,
371+
type: knownTypes.int
372+
),
373+
]
374+
375+
// Initialize a UnsafeRawBufferPointer using the 'address' and 'count'
376+
let bufferPointerInit = ConversionStep.initialize(
377+
knownTypes.unsafeRawBufferPointer,
378+
arguments: [
379+
LabeledArgument(
380+
label: "start",
381+
argument: .explodedComponent(.placeholder, component: "pointer")
382+
),
383+
LabeledArgument(
384+
label: "count",
385+
argument: .explodedComponent(.placeholder, component: "count")
386+
),
387+
]
388+
)
389+
390+
let arrayInit = ConversionStep.initialize(
391+
type,
392+
arguments: [LabeledArgument(argument: bufferPointerInit)]
393+
)
394+
395+
return LoweredParameter(
396+
cdeclParameters: cdeclParameters,
397+
conversion: arrayInit
398+
)
399+
351400
case .array:
352401
throw LoweringError.unhandledType(type)
353402
}

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -469,9 +469,17 @@ extension FFMSwift2JavaGenerator.JavaConversionStep {
469469
return false
470470
case .constructSwiftValue, .wrapMemoryAddressUnsafe:
471471
return true
472+
case .temporaryArena:
473+
return true
474+
475+
case .call(let inner, let base, _, _):
476+
return inner.requiresSwiftArena || (base?.requiresSwiftArena == true)
472477

473-
case .call(let inner, _, _), .cast(let inner, _), .construct(let inner, _),
474-
.method(let inner, _, _, _), .swiftValueSelfSegment(let inner):
478+
case .cast(let inner, _),
479+
.construct(let inner, _),
480+
.method(let inner, _, _, _),
481+
.property(let inner, _),
482+
.swiftValueSelfSegment(let inner):
475483
return inner.requiresSwiftArena
476484

477485
case .commaSeparated(let list):
@@ -484,6 +492,8 @@ extension FFMSwift2JavaGenerator.JavaConversionStep {
484492
switch self {
485493
case .placeholder, .explodedName, .constant:
486494
return false
495+
case .temporaryArena:
496+
return true
487497
case .readMemorySegment:
488498
return true
489499
case .cast(let inner, _),
@@ -492,10 +502,12 @@ extension FFMSwift2JavaGenerator.JavaConversionStep {
492502
.swiftValueSelfSegment(let inner),
493503
.wrapMemoryAddressUnsafe(let inner, _):
494504
return inner.requiresSwiftArena
495-
case .call(let inner, _, let withArena):
496-
return withArena || inner.requiresTemporaryArena
505+
case .call(let inner, let base, _, let withArena):
506+
return withArena || (base?.requiresTemporaryArena == true) || inner.requiresTemporaryArena
497507
case .method(let inner, _, let args, let withArena):
498508
return withArena || inner.requiresTemporaryArena || args.contains(where: { $0.requiresTemporaryArena })
509+
case .property(let inner, _):
510+
return inner.requiresTemporaryArena
499511
case .commaSeparated(let list):
500512
return list.contains(where: { $0.requiresTemporaryArena })
501513
}
@@ -514,18 +526,31 @@ extension FFMSwift2JavaGenerator.JavaConversionStep {
514526

515527
case .swiftValueSelfSegment:
516528
return "\(placeholder).$memorySegment()"
529+
530+
case .temporaryArena:
531+
return "arena$"
517532

518-
case .call(let inner, let function, let withArena):
533+
case .call(let inner, let base, let function, let withArena):
519534
let inner = inner.render(&printer, placeholder)
520535
let arenaArg = withArena ? ", arena$" : ""
521-
return "\(function)(\(inner)\(arenaArg))"
536+
let baseStr : String =
537+
if let base {
538+
base.render(&printer, placeholder) + "."
539+
} else {
540+
""
541+
}
542+
return "\(baseStr)\(function)(\(inner)\(arenaArg))"
522543

523544
case .method(let inner, let methodName, let arguments, let withArena):
524545
let inner = inner.render(&printer, placeholder)
525546
let args = arguments.map { $0.render(&printer, placeholder) }
526547
let argsStr = (args + (withArena ? ["arena$"] : [])).joined(separator: " ,")
527548
return "\(inner).\(methodName)(\(argsStr))"
528549

550+
case .property(let inner, let propertyName):
551+
let inner = inner.render(&printer, placeholder)
552+
return "\(inner).\(propertyName)"
553+
529554
case .constructSwiftValue(let inner, let javaType):
530555
let inner = inner.render(&printer, placeholder)
531556
return "new \(javaType.className!)(\(inner), swiftArena$)"

0 commit comments

Comments
 (0)