Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.sdkmanrc

.DS_Store
.metals
.build
.idea
.vscode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//===----------------------------------------------------------------------===//
//
// 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 FFMArraysTest {

@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.sumAllByteArrayElements(bytes);

System.out.println("swiftSideSum = " + swiftSideSum);

int javaSideSum = IntStream.range(0, bytes.length).map(i -> bytes[i]).sum();
assertEquals(javaSideSum, swiftSideSum);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
//
// 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,12 @@ extension SwiftKnownTypeDeclKind {
case .unsafeRawPointer: .pointer(
.qualified(const: true, volatile: false, type: .void)
)
case .array:
.pointer(.qualified(const: false, volatile: false, type: .void))
case .void: .void
case .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer, .string, .foundationData, .foundationDataProtocol,
.essentialsData, .essentialsDataProtocol, .optional, .array:
.essentialsData, .essentialsDataProtocol, .optional:
nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,55 @@ struct CdeclLowering {
case .composite:
throw LoweringError.unhandledType(type)

case .array(.nominal(let nominal)):
// 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,
type: knownTypes.unsafeRawPointer
),
SwiftParameter(
convention: .byValue,
parameterName: countParameterName,
type: knownTypes.int
),
]

// Initialize a UnsafeRawBufferPointer using the 'address' and 'count'
let bufferPointerInit = ConversionStep.initialize(
knownTypes.unsafeRawBufferPointer,
arguments: [
LabeledArgument(
label: "start",
argument: .explodedComponent(.placeholder, component: "pointer")
),
LabeledArgument(
label: "count",
argument: .explodedComponent(.placeholder, component: "count")
),
]
)

let arrayInit = ConversionStep.initialize(
type,
arguments: [LabeledArgument(argument: bufferPointerInit)]
)

return LoweredParameter(
cdeclParameters: cdeclParameters,
conversion: arrayInit
)

case .array:
throw LoweringError.unhandledType(type)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,9 +469,17 @@ extension FFMSwift2JavaGenerator.JavaConversionStep {
return false
case .constructSwiftValue, .wrapMemoryAddressUnsafe:
return true
case .temporaryArena:
return true

case .call(let inner, let base, _, _):
return inner.requiresSwiftArena || (base?.requiresSwiftArena == true)

case .call(let inner, _, _), .cast(let inner, _), .construct(let inner, _),
.method(let inner, _, _, _), .swiftValueSelfSegment(let inner):
case .cast(let inner, _),
.construct(let inner, _),
.method(let inner, _, _, _),
.property(let inner, _),
.swiftValueSelfSegment(let inner):
return inner.requiresSwiftArena

case .commaSeparated(let list):
Expand All @@ -484,6 +492,8 @@ extension FFMSwift2JavaGenerator.JavaConversionStep {
switch self {
case .placeholder, .explodedName, .constant:
return false
case .temporaryArena:
return true
case .readMemorySegment:
return true
case .cast(let inner, _),
Expand All @@ -492,10 +502,12 @@ extension FFMSwift2JavaGenerator.JavaConversionStep {
.swiftValueSelfSegment(let inner),
.wrapMemoryAddressUnsafe(let inner, _):
return inner.requiresSwiftArena
case .call(let inner, _, let withArena):
return withArena || inner.requiresTemporaryArena
case .call(let inner, let base, _, let withArena):
return withArena || (base?.requiresTemporaryArena == true) || inner.requiresTemporaryArena
case .method(let inner, _, let args, let withArena):
return withArena || inner.requiresTemporaryArena || args.contains(where: { $0.requiresTemporaryArena })
case .property(let inner, _):
return inner.requiresTemporaryArena
case .commaSeparated(let list):
return list.contains(where: { $0.requiresTemporaryArena })
}
Expand All @@ -514,18 +526,31 @@ extension FFMSwift2JavaGenerator.JavaConversionStep {

case .swiftValueSelfSegment:
return "\(placeholder).$memorySegment()"

case .temporaryArena:
return "arena$"

case .call(let inner, let function, let withArena):
case .call(let inner, let base, let function, let withArena):
let inner = inner.render(&printer, placeholder)
let arenaArg = withArena ? ", arena$" : ""
return "\(function)(\(inner)\(arenaArg))"
let baseStr : String =
if let base {
base.render(&printer, placeholder) + "."
} else {
""
}
return "\(baseStr)\(function)(\(inner)\(arenaArg))"

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 argsStr = (args + (withArena ? ["arena$"] : [])).joined(separator: " ,")
return "\(inner).\(methodName)(\(argsStr))"

case .property(let inner, let propertyName):
let inner = inner.render(&printer, placeholder)
return "\(inner).\(propertyName)"

case .constructSwiftValue(let inner, let javaType):
let inner = inner.render(&printer, placeholder)
return "new \(javaType.className!)(\(inner), swiftArena$)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,24 @@ extension FFMSwift2JavaGenerator {
case .composite:
throw JavaTranslationError.unhandledType(swiftType)

case .array(let elementType):
case .array(let wrapped) where wrapped == knownTypes.uint8:
return TranslatedParameter(
javaParameters: [
JavaParameter(name: parameterName, type: .array(.byte)),
],
conversion:
.commaSeparated([
.call(
.commaSeparated([.constant("ValueLayout.JAVA_BYTE"), .placeholder]),
base: .temporaryArena,
function: "allocateFrom",
withArena: false // this would pass the arena as last argument, but instead we make a call on the arena
),
.property(.placeholder, propertyName: "length"),
])
)

case .array:
throw JavaTranslationError.unhandledType(swiftType)
}
}
Expand Down Expand Up @@ -715,42 +732,57 @@ extension FFMSwift2JavaGenerator {

/// Describes how to convert values between Java types and FFM types.
enum JavaConversionStep {
// The input
/// The input
case placeholder

// The input exploded into components.
/// The temporary `arena$` that is necessary to complete the conversion steps.
case temporaryArena

/// The input exploded into components.
case explodedName(component: String)

// A fixed value
/// A fixed value
case constant(String)

// 'value.$memorySegment()'
/// 'value.$memorySegment()'
indirect case swiftValueSelfSegment(JavaConversionStep)

// call specified function using the placeholder as arguments.
// If `withArena` is true, `arena$` argument is added.
indirect case call(JavaConversionStep, function: String, withArena: Bool)
/// Call specified function using the placeholder as arguments.
///
/// The 'base' is if the call should be performed as 'base.function',
/// otherwise the function is assumed to be a free function.
///
/// If `withArena` is true, `arena$` argument is added.
indirect case call(JavaConversionStep, base: JavaConversionStep?, function: String, withArena: Bool)

static func call(_ step: JavaConversionStep, function: String, withArena: Bool) -> 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 {
Expand Down
Loading