Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
@@ -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
Expand Up @@ -36,25 +36,4 @@ 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);
}
}
}
11 changes: 9 additions & 2 deletions Sources/JExtractSwiftLib/Common/TypeAnnotations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 []
Expand Down
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,46 @@ struct CdeclLowering {
case .composite:
throw LoweringError.unhandledType(type)

case .array(let wrapped) where wrapped == knownTypes.uint8:
// Lower an array as 'address' raw pointer and 'count' integer
let cdeclParameters = [
SwiftParameter(
convention: .byValue,
parameterName: "\(parameterName)_pointer",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have naming inconsistency; these should be $ and not _ but seems we got this inconsistency already from the Data work -- lets fix it separately, since this made the PR kinda big

type: knownTypes.unsafeRawPointer
),
SwiftParameter(
convention: .byValue,
parameterName: "\(parameterName)_count",
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), annotations: parameterAnnotations),
],
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
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions Sources/JExtractSwiftLib/SwiftTypes/SwiftKnownTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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])) }
Expand Down
Loading
Loading