Skip to content

[GR-66310] Add support for arrays to Panama NFI backend #11897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 7, 2025
Merged
Show file tree
Hide file tree
Changes from all 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 truffle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan
* GR-65048: Introduced `InternalResource.OS.UNSUPPORTED` and `InternalResource.CPUArchitecture.UNSUPPORTED` to represent unsupported platforms. Execution on unsupported platforms must be explicitly enabled using the system property `-Dpolyglot.engine.allowUnsupportedPlatform=true`. If this property is not set, calls to `OS.getCurrent()` or `CPUArchitecture.getCurrent()` will throw an `IllegalStateException` when running on an unsupported platform. `InternalResource` implementations should handle the unsupported platform and describe possible steps in the error message on how to proceed.
* GR-66839: Deprecate `Location#isFinal()` as it always returns false.
* GR-67702: Specialization DSL: For nodes annotated with `@GenerateInline`, inlining warnings emitted for `@Cached` expressions are now suppressed if the inlined node is explicitly annotated with `@GenerateInline(false)`. This avoids unnecessary warnings if inlining for a node was explicitly disabled.
* GR-66310: Added support for passing arrays of primitive types to native code through the Truffle NFI Panama backend.

## Version 25.0
* GR-31495 Added ability to specify language and instrument specific options using `Source.Builder.option(String, String)`. Languages may describe available source options by implementing `TruffleLanguage.getSourceOptionDescriptors()` and `TruffleInstrument.getSourceOptionDescriptors()` respectively.
Expand Down
2 changes: 1 addition & 1 deletion truffle/docs/NFI.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Depending on the configuration of components you are running, available backends
### Panama backend

The Panama backend uses the Foreign Function and Memory APIs introduced by [project Panama](https://openjdk.org/projects/panama/).
This backend only supports a subset of all the types. Specifically, it does not support `STRING`, `OBJECT`, `ENV`, `FP80` or array types.
This backend only supports a subset of all the types. Specifically, it does not support `STRING`, `OBJECT`, `ENV`, or `FP80`.
Although less feature-complete, the backend is typically more performant.
It is available starting from JDK 22.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -40,9 +40,15 @@
*/
package com.oracle.truffle.nfi.backend.panama;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.reflect.Array;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
Expand All @@ -51,17 +57,14 @@
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;

abstract class ArgumentNode extends Node {
final PanamaType type;

ArgumentNode(PanamaType type) {
this.type = type;
}

abstract Object execute(Object value) throws UnsupportedTypeException;
abstract Object execute(Arena arena, Object value) throws UnsupportedTypeException;

abstract static class ToVOIDNode extends ArgumentNode {

Expand All @@ -70,7 +73,7 @@ abstract static class ToVOIDNode extends ArgumentNode {
}

@Specialization
Object doConvert(@SuppressWarnings("unused") Object value) {
Object doConvert(@SuppressWarnings("unused") Arena arena, @SuppressWarnings("unused") Object value) {
return null;
}
}
Expand All @@ -82,7 +85,7 @@ abstract static class ToINT8Node extends ArgumentNode {
}

@Specialization(limit = "3")
byte doConvert(Object value,
byte doConvert(@SuppressWarnings("unused") Arena arena, Object value,
@CachedLibrary("value") InteropLibrary interop) throws UnsupportedTypeException {
try {
return interop.asByte(value);
Expand All @@ -99,7 +102,7 @@ abstract static class ToINT16Node extends ArgumentNode {
}

@Specialization(limit = "3")
short doConvert(Object value,
short doConvert(@SuppressWarnings("unused") Arena arena, Object value,
@CachedLibrary("value") InteropLibrary interop) throws UnsupportedTypeException {
try {
return interop.asShort(value);
Expand All @@ -116,7 +119,7 @@ abstract static class ToINT32Node extends ArgumentNode {
}

@Specialization(limit = "3")
int doConvert(Object value,
int doConvert(@SuppressWarnings("unused") Arena arena, Object value,
@CachedLibrary("value") InteropLibrary interop) throws UnsupportedTypeException {
try {
return interop.asInt(value);
Expand All @@ -133,7 +136,7 @@ abstract static class ToINT64Node extends ArgumentNode {
}

@Specialization(limit = "3")
long doConvert(Object value,
long doConvert(@SuppressWarnings("unused") Arena arena, Object value,
@CachedLibrary("value") InteropLibrary interop) throws UnsupportedTypeException {
try {
return interop.asLong(value);
Expand All @@ -145,24 +148,26 @@ long doConvert(Object value,

abstract static class ToPointerNode extends ArgumentNode {

abstract long executeLong(Arena arena, Object value) throws UnsupportedTypeException;

ToPointerNode(PanamaType type) {
super(type);
}

@Specialization(limit = "3", guards = "interop.isPointer(arg)", rewriteOn = UnsupportedMessageException.class)
long putPointer(Object arg,
long putPointer(@SuppressWarnings("unused") Arena arena, Object arg,
@CachedLibrary("arg") InteropLibrary interop) throws UnsupportedMessageException {
return interop.asPointer(arg);
}

@Specialization(limit = "3", guards = {"!interop.isPointer(arg)", "interop.isNull(arg)"})
long putNull(@SuppressWarnings("unused") Object arg,
long putNull(@SuppressWarnings("unused") Arena arena, @SuppressWarnings("unused") Object arg,
@SuppressWarnings("unused") @CachedLibrary("arg") InteropLibrary interop) {
return NativePointer.NULL.asPointer();
}

@Specialization(limit = "3", replaces = {"putPointer", "putNull"})
static long putGeneric(Object arg,
static long putGeneric(@SuppressWarnings("unused") Arena arena, Object arg,
@Bind Node node,
@CachedLibrary("arg") InteropLibrary interop,
@Cached InlinedBranchProfile exception) throws UnsupportedTypeException {
Expand Down Expand Up @@ -199,7 +204,7 @@ abstract static class ToFLOATNode extends ArgumentNode {
}

@Specialization(limit = "3")
float doConvert(Object value,
float doConvert(@SuppressWarnings("unused") Arena arena, Object value,
@CachedLibrary("value") InteropLibrary interop) throws UnsupportedTypeException {
try {
return interop.asFloat(value);
Expand All @@ -216,7 +221,7 @@ abstract static class ToDOUBLENode extends ArgumentNode {
}

@Specialization(limit = "3")
double doConvert(Object value,
double doConvert(@SuppressWarnings("unused") Arena arena, Object value,
@CachedLibrary("value") InteropLibrary interop) throws UnsupportedTypeException {
try {
return interop.asDouble(value);
Expand All @@ -233,11 +238,10 @@ abstract static class ToSTRINGNode extends ArgumentNode {
}

@Specialization(limit = "3")
Object doConvert(Object value,
Object doConvert(@SuppressWarnings("unused") Arena arena, Object value,
@CachedLibrary("value") InteropLibrary interop) throws UnsupportedTypeException {
PanamaNFIContext ctx = PanamaNFIContext.get(this);
try {
return allocateFrom(ctx.getContextArena(), interop.asString(value));
return allocateFrom(arena, interop.asString(value));
} catch (UnsupportedMessageException ex) {
throw UnsupportedTypeException.create(new Object[]{value});
}
Expand All @@ -248,4 +252,39 @@ private static MemorySegment allocateFrom(Arena arena, String str) {
return arena.allocateFrom(str);
}
}

abstract static class ToArrayNode extends ArgumentNode {

@Child GetArrayElementLayoutNode getArrayElementLayoutNode;

ToArrayNode(PanamaType type) {
super(type);
getArrayElementLayoutNode = GetArrayElementLayoutNode.create(type.type);
}

@Specialization(guards = "elementLayout != null")
MemorySegment doArray(Arena arena, Object value,
@Bind("getArrayElementLayoutNode.execute(value)") ValueLayout elementLayout) {
return doCopy(arena, value, elementLayout);
}

@Fallback
MemorySegment doInteropObject(@SuppressWarnings("unused") Arena arena, Object value,
@Cached(parameters = "type") ToPointerNode toPointer) throws UnsupportedTypeException {
return wrapPointer(toPointer.executeLong(arena, value));
}

@TruffleBoundary(allowInlining = true)
private static MemorySegment doCopy(Arena arena, Object array, ValueLayout elementLayout) {
int arrayLength = Array.getLength(array);
MemorySegment dst = arena.allocate(elementLayout, arrayLength);
MemorySegment.copy(array, 0, dst, elementLayout, 0, arrayLength);
return dst;
}

@TruffleBoundary(allowInlining = true)
private static MemorySegment wrapPointer(long ptr) {
return MemorySegment.ofAddress(ptr);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -40,6 +40,7 @@
*/
package com.oracle.truffle.nfi.backend.panama;

import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.ref.Reference;

Expand All @@ -57,7 +58,6 @@
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;

import com.oracle.truffle.nfi.backend.panama.PanamaSignature.CachedSignatureInfo;

@GenerateUncached
Expand Down Expand Up @@ -108,15 +108,28 @@ abstract static class SignatureExecuteNode extends RootNode {

final CachedSignatureInfo signatureInfo;
@Children ArgumentNode[] argNodes;
@Children PostCallArgumentNode[] postCallArgNodes;
final boolean needsArena;

SignatureExecuteNode(PanamaNFILanguage language, CachedSignatureInfo signatureInfo) {
super(language);
this.signatureInfo = signatureInfo;

PanamaType[] argTypes = signatureInfo.getArgTypes();
this.argNodes = new ArgumentNode[argTypes.length];
boolean postCall = false;
boolean arenaNeeded = false;
for (int i = 0; i < argTypes.length; i++) {
argNodes[i] = argTypes[i].createArgumentNode();
postCall |= argTypes[i].needsPostCallProcessing();
arenaNeeded |= argTypes[i].needsArena();
}
this.needsArena = arenaNeeded;
if (postCall) {
this.postCallArgNodes = new PostCallArgumentNode[argNodes.length];
for (int i = 0; i < argNodes.length; i++) {
postCallArgNodes[i] = argTypes[i].createPostCallArgumentNode();
}
}
}

Expand Down Expand Up @@ -146,18 +159,36 @@ public Object doGeneric(VirtualFrame frame) {
throw silenceException(RuntimeException.class, ArityException.create(argNodes.length, argNodes.length, args.length));
}

Object[] convertedArgs = postCallArgNodes == null ? args : new Object[args.length];
Arena arena = needsArena ? Arena.ofConfined() : null;
try {
PanamaType[] types = signatureInfo.getArgTypes();
assert argNodes.length == types.length;
try {
PanamaType[] types = signatureInfo.getArgTypes();
assert argNodes.length == types.length;

for (int i = 0; i < argNodes.length; i++) {
convertedArgs[i] = argNodes[i].execute(arena, args[i]);
}
} catch (UnsupportedTypeException ex) {
throw silenceException(RuntimeException.class, ex);
}
try {
return signatureInfo.execute(signature, convertedArgs, address, this);
} finally {
if (postCallArgNodes != null) {
for (int i = 0; i < postCallArgNodes.length; i++) {
if (postCallArgNodes[i] != null) {
postCallArgNodes[i].execute(args[i], convertedArgs[i]);
}
}
}
}

for (int i = 0; i < argNodes.length; i++) {
args[i] = argNodes[i].execute(args[i]);
} finally {
if (needsArena) {
arena.close();
}
} catch (UnsupportedTypeException ex) {
throw silenceException(RuntimeException.class, ex);
}

return signatureInfo.execute(signature, args, address, this);
}

@SuppressWarnings({"unchecked", "unused"})
Expand Down
Loading