From fa4ee4d34a7a8fabdc4f26665b63febe1f6f8d82 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Mon, 1 Jun 2026 17:19:42 +0200 Subject: [PATCH 1/3] Allow void C API upcall targets --- .../oracle/graal/python/processor/CApiBuiltinsProcessor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java b/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java index 3176849381..c19609436f 100644 --- a/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java +++ b/graalpython/com.oracle.graal.python.processor/src/com/oracle/graal/python/processor/CApiBuiltinsProcessor.java @@ -971,7 +971,8 @@ private static String toJNIName(TypeMirror typeMirror) { } private boolean verifySignatureOfExplicitUpcallTarget(ExecutableElement explicitUpcallTarget) { - if (!explicitUpcallTarget.getReturnType().getKind().isPrimitive()) { + TypeKind returnKind = explicitUpcallTarget.getReturnType().getKind(); + if (returnKind != TypeKind.VOID && !returnKind.isPrimitive()) { processingEnv.getMessager().printError("Return type must be primitive but was " + explicitUpcallTarget.getReturnType(), explicitUpcallTarget); return false; } From ad88eee85a2786414d9425a26d74619ef8817a8e Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Mon, 1 Jun 2026 17:19:58 +0200 Subject: [PATCH 2/3] Migrate Arrow callbacks to NativeAccess --- .../modules/GraalPythonModuleBuiltins.java | 4 +- .../objects/cext/common/NativePointer.java | 5 +- .../arrow/ArrowReleaseCallback.java} | 29 +++-- .../arrow/InvokeArrowReleaseCallbackNode.java | 102 ------------------ .../capsule/ArrowArrayCapsuleDestructor.java | 90 ++++++++-------- .../capsule/ArrowSchemaCapsuleDestructor.java | 74 ++++++------- .../python/runtime/arrow/ArrowSupport.java | 41 +++---- 7 files changed, 126 insertions(+), 219 deletions(-) rename graalpython/com.oracle.graal.python/src/com/oracle/graal/python/{runtime/arrow/ArrowUtil.java => nodes/arrow/ArrowReleaseCallback.java} (69%) delete mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/InvokeArrowReleaseCallbackNode.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java index d15e2ede6f..1f2fb48e54 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java @@ -1537,11 +1537,11 @@ static PTuple doCreate(long arrowArrayAddr, long arrowSchemaAddr, @Bind Node inliningTarget, @Cached PythonCextCapsuleBuiltins.PyCapsuleNewNode pyCapsuleNewNode) { PythonContext ctx = getContext(inliningTarget); - long arrayDestructor = ctx.arrowSupport.getArrowArrayDestructor(inliningTarget); + long arrayDestructor = ctx.arrowSupport.getArrowArrayDestructor(); long arrayCapsuleNamePointer = ctx.stringToNativeUtf8Bytes(ArrowArray.CAPSULE_NAME, true); PyCapsule arrowArrayCapsule = pyCapsuleNewNode.execute(inliningTarget, arrowArrayAddr, arrayCapsuleNamePointer, arrayDestructor); - long schemaDestructor = ctx.arrowSupport.getArrowSchemaDestructor(inliningTarget); + long schemaDestructor = ctx.arrowSupport.getArrowSchemaDestructor(); long schemaCapsuleNamePointer = ctx.stringToNativeUtf8Bytes(ArrowSchema.CAPSULE_NAME, true); PyCapsule arrowSchemaCapsule = pyCapsuleNewNode.execute(inliningTarget, arrowSchemaAddr, schemaCapsuleNamePointer, schemaDestructor); return PFactory.createTuple(ctx.getLanguage(inliningTarget), new Object[]{arrowSchemaCapsule, arrowArrayCapsule}); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/NativePointer.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/NativePointer.java index 893169a2ab..38e005fd63 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/NativePointer.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/common/NativePointer.java @@ -47,10 +47,7 @@ import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; -/** - * Currently only used for wrapping pointers to call TruffleString, see GR-71311 and in - * InvokeArrowReleaseCallbackNode which still uses original NFI. - */ +/** Currently only used for wrapping pointers to call TruffleString, see GR-71311. */ @ExportLibrary(InteropLibrary.class) public final class NativePointer implements TruffleObject { private final long ptr; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/arrow/ArrowUtil.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/ArrowReleaseCallback.java similarity index 69% rename from graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/arrow/ArrowUtil.java rename to graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/ArrowReleaseCallback.java index cab540e404..85082a0d49 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/arrow/ArrowUtil.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/ArrowReleaseCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -38,19 +38,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.oracle.graal.python.runtime.arrow; +package com.oracle.graal.python.nodes.arrow; -import static com.oracle.graal.python.nodes.StringLiterals.J_NFI_LANGUAGE; -import static com.oracle.graal.python.util.PythonUtils.callCallTarget; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; -import com.oracle.graal.python.runtime.PythonContext; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.source.Source; +import com.oracle.graal.python.runtime.nativeaccess.NativeAccessSupport; +import com.oracle.truffle.api.CompilerDirectives; -public class ArrowUtil { +public final class ArrowReleaseCallback { + private static final MethodHandle HANDLE = NativeAccessSupport.createDowncallHandle( + MethodType.methodType(void.class, long.class, long.class), false); - public static Object createNfiSignature(Node location, String methodSignature, PythonContext ctx) { - Source sigSource = Source.newBuilder(J_NFI_LANGUAGE, methodSignature, "python-nfi-signature").build(); - return callCallTarget(ctx.getEnv().parseInternal(sigSource), location); + private ArrowReleaseCallback() { + } + + public static void execute(long releaseCallback, long baseStructure) { + try { + HANDLE.invokeExact(releaseCallback, baseStructure); + } catch (Throwable e) { + throw CompilerDirectives.shouldNotReachHere("Unable to call release callback. Error:", e); + } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/InvokeArrowReleaseCallbackNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/InvokeArrowReleaseCallbackNode.java deleted file mode 100644 index fd1a0f1c8f..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/InvokeArrowReleaseCallbackNode.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2025, 2026, 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 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.oracle.graal.python.nodes.arrow; - -import com.oracle.graal.python.builtins.objects.cext.common.NativePointer; -import com.oracle.graal.python.nodes.PNodeWithContext; -import com.oracle.graal.python.runtime.PythonContext; -import com.oracle.graal.python.runtime.arrow.ArrowUtil; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.dsl.Bind; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.GenerateCached; -import com.oracle.truffle.api.dsl.GenerateInline; -import com.oracle.truffle.api.dsl.GenerateUncached; -import com.oracle.truffle.api.dsl.NeverDefault; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.library.CachedLibrary; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.nfi.api.SignatureLibrary; - -@GenerateCached -@GenerateInline(inlineByDefault = true) -@GenerateUncached -public abstract class InvokeArrowReleaseCallbackNode extends PNodeWithContext { - - public abstract void execute(Node inliningTarget, long releaseCallback, long baseStructure); - - public final void executeCached(long releaseCallback, long baseStructure) { - execute(this, releaseCallback, baseStructure); - } - - @Specialization - static void doIt(Node inliningTarget, long releaseCallback, long baseStructure, - @Bind("getContext(inliningTarget)") PythonContext ctx, - @Cached(value = "createReleaseCallbackSignature($node, ctx)", allowUncached = true) Object callbackSignature, - @CachedLibrary(limit = "1") SignatureLibrary signatureLibrary) { - try { - signatureLibrary.call(callbackSignature, NativePointer.wrap(releaseCallback), baseStructure); - } catch (Exception e) { - throw CompilerDirectives.shouldNotReachHere("Unable to call release callback. Error:", e); - } - } - - @NeverDefault - static Object createReleaseCallbackSignature(Node location, PythonContext context) { - return ArrowUtil.createNfiSignature(location, "(UINT64):VOID", context); - } - - @GenerateCached(false) - @GenerateInline - @GenerateUncached - public abstract static class Lazy extends Node { - public final InvokeArrowReleaseCallbackNode get(Node inliningTarget) { - return execute(inliningTarget); - } - - abstract InvokeArrowReleaseCallbackNode execute(Node inliningTarget); - - @Specialization - static InvokeArrowReleaseCallbackNode doIt(@Cached(inline = false) InvokeArrowReleaseCallbackNode node) { - return node; - } - } -} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/capsule/ArrowArrayCapsuleDestructor.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/capsule/ArrowArrayCapsuleDestructor.java index 5bdc83867d..d5af1163fe 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/capsule/ArrowArrayCapsuleDestructor.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/capsule/ArrowArrayCapsuleDestructor.java @@ -40,60 +40,62 @@ */ package com.oracle.graal.python.nodes.arrow.capsule; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +import com.oracle.graal.python.annotations.CApiUpcallTarget; import com.oracle.graal.python.builtins.modules.cext.PythonCextCapsuleBuiltins.PyCapsuleGetPointerNode; -import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonNode; -import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; import com.oracle.graal.python.nodes.arrow.ArrowArray; -import com.oracle.graal.python.nodes.arrow.InvokeArrowReleaseCallbackNode; +import com.oracle.graal.python.nodes.arrow.ArrowReleaseCallback; import com.oracle.graal.python.runtime.PythonContext; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.dsl.Bind; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.TruffleObject; -import com.oracle.truffle.api.library.CachedLibrary; -import com.oracle.truffle.api.library.ExportLibrary; -import com.oracle.truffle.api.library.ExportMessage; -import com.oracle.truffle.api.nodes.Node; +import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; +import com.oracle.truffle.api.CompilerAsserts; + +public final class ArrowArrayCapsuleDestructor { + private static final MethodHandle HANDLE_EXECUTE; -@ExportLibrary(InteropLibrary.class) -public class ArrowArrayCapsuleDestructor implements TruffleObject { + static { + try { + HANDLE_EXECUTE = MethodHandles.lookup().findStatic(ArrowArrayCapsuleDestructor.class, "execute", + MethodType.methodType(void.class, long.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } - @ExportMessage - boolean isExecutable() { - return true; + private ArrowArrayCapsuleDestructor() { } - @ExportMessage - Object execute(Object[] args, - @Bind Node inliningTarget, - @CachedLibrary(limit = "1") InteropLibrary lib, - @Cached NativeToPythonNode nativeToPythonNode, - @Cached PyCapsuleGetPointerNode capsuleGetPointerNode, - @Cached InvokeArrowReleaseCallbackNode.Lazy invokeReleaseCallbackNode) { - if (args.length != 1 || !lib.isPointer(args[0])) { - throw CompilerDirectives.shouldNotReachHere(); - } + public static MethodHandle getMethodHandle() { + return HANDLE_EXECUTE; + } - Object capsule = nativeToPythonNode.execute(args[0]); - PythonContext ctx = PythonContext.get(inliningTarget); + @CApiUpcallTarget + private static void execute(long capsulePointer) { + CompilerAsserts.neverPartOfCompilation(); + PythonContext ctx = PythonContext.get(null); ctx.ensureNativeAccess(); - long capsuleNamePointer = ctx.stringToNativeUtf8Bytes(ArrowArray.CAPSULE_NAME, true); - var arrowArray = ArrowArray.wrap(capsuleGetPointerNode.execute(inliningTarget, capsule, capsuleNamePointer)); - /* - * The exported PyCapsules should have a destructor that calls the release callback of the - * Arrow struct, if it is not already null. This prevents a memory leak in case the capsule - * was never passed to another consumer. - * - * For more information see: - * https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html#lifetime- - * semantics - */ - if (!arrowArray.isReleased()) { - invokeReleaseCallbackNode.get(inliningTarget).executeCached(arrowArray.releaseCallback(), arrowArray.memoryAddress()); + Object capsule = NativeToPythonNode.executeRawUncached(capsulePointer); + long capsuleNamePointer = ctx.stringToNativeUtf8Bytes(ArrowArray.CAPSULE_NAME, false); + try { + var arrowArray = ArrowArray.wrap(PyCapsuleGetPointerNode.executeUncached(capsule, capsuleNamePointer)); + /* + * The exported PyCapsules should have a destructor that calls the release callback of + * the Arrow struct, if it is not already null. This prevents a memory leak in case the + * capsule was never passed to another consumer. + * + * For more information see: + * https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html#lifetime- + * semantics + */ + if (!arrowArray.isReleased()) { + ArrowReleaseCallback.execute(arrowArray.releaseCallback(), arrowArray.memoryAddress()); + } + NativeMemory.free(arrowArray.memoryAddress()); + } finally { + NativeMemory.free(capsuleNamePointer); } - NativeMemory.free(arrowArray.memoryAddress()); - return PNone.NO_VALUE; } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/capsule/ArrowSchemaCapsuleDestructor.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/capsule/ArrowSchemaCapsuleDestructor.java index da04d3b7bb..a0e5adc585 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/capsule/ArrowSchemaCapsuleDestructor.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/arrow/capsule/ArrowSchemaCapsuleDestructor.java @@ -40,53 +40,55 @@ */ package com.oracle.graal.python.nodes.arrow.capsule; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +import com.oracle.graal.python.annotations.CApiUpcallTarget; import com.oracle.graal.python.builtins.modules.cext.PythonCextCapsuleBuiltins.PyCapsuleGetPointerNode; -import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonNode; -import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; +import com.oracle.graal.python.nodes.arrow.ArrowReleaseCallback; import com.oracle.graal.python.nodes.arrow.ArrowSchema; -import com.oracle.graal.python.nodes.arrow.InvokeArrowReleaseCallbackNode; import com.oracle.graal.python.runtime.PythonContext; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.dsl.Bind; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.TruffleObject; -import com.oracle.truffle.api.library.CachedLibrary; -import com.oracle.truffle.api.library.ExportLibrary; -import com.oracle.truffle.api.library.ExportMessage; -import com.oracle.truffle.api.nodes.Node; +import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; +import com.oracle.truffle.api.CompilerAsserts; -@ExportLibrary(InteropLibrary.class) -public class ArrowSchemaCapsuleDestructor implements TruffleObject { +public final class ArrowSchemaCapsuleDestructor { + private static final MethodHandle HANDLE_EXECUTE; - @ExportMessage - boolean isExecutable() { - return true; + static { + try { + HANDLE_EXECUTE = MethodHandles.lookup().findStatic(ArrowSchemaCapsuleDestructor.class, "execute", + MethodType.methodType(void.class, long.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException(e); + } } - @ExportMessage - Object execute(Object[] args, - @Bind Node inliningTarget, - @CachedLibrary(limit = "1") InteropLibrary lib, - @Cached NativeToPythonNode nativeToPythonNode, - @Cached PyCapsuleGetPointerNode pyCapsuleGetPointerNode, - @Cached InvokeArrowReleaseCallbackNode.Lazy invokeReleaseCallbackNode) { - if (args.length != 1 || !lib.isPointer(args[0])) { - throw CompilerDirectives.shouldNotReachHere(); - } + private ArrowSchemaCapsuleDestructor() { + } + + public static MethodHandle getMethodHandle() { + return HANDLE_EXECUTE; + } - Object capsule = nativeToPythonNode.execute(args[0]); - PythonContext ctx = PythonContext.get(inliningTarget); + @CApiUpcallTarget + private static void execute(long capsulePointer) { + CompilerAsserts.neverPartOfCompilation(); + PythonContext ctx = PythonContext.get(null); ctx.ensureNativeAccess(); - long capsuleNamePointer = ctx.stringToNativeUtf8Bytes(ArrowSchema.CAPSULE_NAME, true); - var arrowSchema = ArrowSchema.wrap(pyCapsuleGetPointerNode.execute(inliningTarget, capsule, capsuleNamePointer)); + Object capsule = NativeToPythonNode.executeRawUncached(capsulePointer); + long capsuleNamePointer = ctx.stringToNativeUtf8Bytes(ArrowSchema.CAPSULE_NAME, false); + try { + var arrowSchema = ArrowSchema.wrap(PyCapsuleGetPointerNode.executeUncached(capsule, capsuleNamePointer)); - if (!arrowSchema.isReleased()) { - invokeReleaseCallbackNode.get(inliningTarget).executeCached(arrowSchema.releaseCallback(), arrowSchema.memoryAddress()); - } + if (!arrowSchema.isReleased()) { + ArrowReleaseCallback.execute(arrowSchema.releaseCallback(), arrowSchema.memoryAddress()); + } - NativeMemory.free(arrowSchema.memoryAddress()); - return PNone.NO_VALUE; + NativeMemory.free(arrowSchema.memoryAddress()); + } finally { + NativeMemory.free(capsuleNamePointer); + } } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/arrow/ArrowSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/arrow/ArrowSupport.java index 6f71638ef8..377a965dde 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/arrow/ArrowSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/arrow/ArrowSupport.java @@ -40,19 +40,22 @@ */ package com.oracle.graal.python.runtime.arrow; +import static com.oracle.graal.python.annotations.NativeSimpleType.POINTER; +import static com.oracle.graal.python.annotations.NativeSimpleType.VOID; + +import java.lang.invoke.MethodHandle; + import com.oracle.graal.python.nodes.arrow.capsule.ArrowArrayCapsuleDestructor; import com.oracle.graal.python.nodes.arrow.capsule.ArrowSchemaCapsuleDestructor; import com.oracle.graal.python.runtime.PythonContext; -import com.oracle.graal.python.util.PythonUtils; +import com.oracle.graal.python.runtime.nativeaccess.NativeSignature; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.nfi.api.SignatureLibrary; public class ArrowSupport { + private static final NativeSignature ARROW_CAPSULE_DESTRUCTOR_SIGNATURE = NativeSignature.create(VOID, POINTER); protected final PythonContext ctx; @@ -61,44 +64,42 @@ public ArrowSupport(PythonContext ctx) { } // ArrowArray destructor - private Object arrowArrayDestructorNFIClosure; @CompilationFinal private long arrowArrayDestructor; // ArrowSchema destructor - private Object arrowSchemaDestructorNFIClosure; @CompilationFinal private long arrowSchemaDestructor; - public long getArrowSchemaDestructor(Node location) { + public long getArrowSchemaDestructor() { if (arrowSchemaDestructor == 0) { CompilerDirectives.transferToInterpreterAndInvalidate(); - initArrowSchemaDestructor(location); + initArrowSchemaDestructor(); } return arrowSchemaDestructor; } - public long getArrowArrayDestructor(Node location) { + public long getArrowArrayDestructor() { if (arrowArrayDestructor == 0L) { CompilerDirectives.transferToInterpreterAndInvalidate(); - initArrowArrayDestructor(location); + initArrowArrayDestructor(); } return arrowArrayDestructor; } @TruffleBoundary - private void initArrowArrayDestructor(Node location) { + private void initArrowArrayDestructor() { CompilerAsserts.neverPartOfCompilation(); - var signature = ArrowUtil.createNfiSignature(location, "(POINTER):VOID", ctx); - var executable = new ArrowArrayCapsuleDestructor(); - this.arrowArrayDestructorNFIClosure = SignatureLibrary.getUncached().createClosure(signature, executable); - this.arrowArrayDestructor = PythonUtils.coerceToLong(arrowArrayDestructorNFIClosure, InteropLibrary.getUncached()); + this.arrowArrayDestructor = createDestructorClosure("arrow_array_capsule_destructor", + ArrowArrayCapsuleDestructor.getMethodHandle()); } @TruffleBoundary - private void initArrowSchemaDestructor(Node location) { + private void initArrowSchemaDestructor() { CompilerAsserts.neverPartOfCompilation(); - var signature = ArrowUtil.createNfiSignature(location, "(POINTER):VOID", ctx); - var executable = new ArrowSchemaCapsuleDestructor(); - this.arrowSchemaDestructorNFIClosure = SignatureLibrary.getUncached().createClosure(signature, executable); - this.arrowSchemaDestructor = PythonUtils.coerceToLong(arrowSchemaDestructorNFIClosure, InteropLibrary.getUncached()); + this.arrowSchemaDestructor = createDestructorClosure("arrow_schema_capsule_destructor", + ArrowSchemaCapsuleDestructor.getMethodHandle()); + } + + private long createDestructorClosure(String name, MethodHandle methodHandle) { + return ARROW_CAPSULE_DESTRUCTOR_SIGNATURE.createClosure(ctx.ensureNativeContext(), name, methodHandle); } } From 84660d0264c8a7444c71ff34f8024e089e304825 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Wed, 10 Jun 2026 09:03:17 +0200 Subject: [PATCH 3/3] Remove unused NativeLibrary and FunctionWithSignature --- .../graal/python/runtime/NativeLibrary.java | 262 ------------------ .../python/util/FunctionWithSignature.java | 44 --- 2 files changed, 306 deletions(-) delete mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativeLibrary.java delete mode 100644 graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/FunctionWithSignature.java diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativeLibrary.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativeLibrary.java deleted file mode 100644 index 491d0584f8..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativeLibrary.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (c) 2020, 2026, 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 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.oracle.graal.python.runtime; - -import static com.oracle.graal.python.nodes.StringLiterals.J_NFI_LANGUAGE; -import static com.oracle.graal.python.util.PythonUtils.callCallTarget; - -import java.lang.invoke.VarHandle; -import java.util.logging.Level; - -import com.oracle.graal.python.PythonLanguage; -import com.oracle.graal.python.util.FunctionWithSignature; -import com.oracle.truffle.api.CompilerAsserts; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.TruffleFile; -import com.oracle.truffle.api.TruffleLogger; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.UnknownIdentifierException; -import com.oracle.truffle.api.interop.UnsupportedMessageException; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.source.Source; -import com.oracle.truffle.nfi.api.SignatureLibrary; - -/** - * Wraps a native library loaded via NFI and provides caching for functions looked up in the - * library. The set of functions to be loaded from the library is expressed as Java enum - * implementing {@link NativeFunction}. This is runtime object: it should not be cached in the AST - * and is expected to be stored in the context. - *

- * Because of Truffle DSL restrictions this class cannot be generic, but users should work with - * generic subclass {@link TypedNativeLibrary}, which can be created with one of the {@code create} - * factory methods. - */ -public class NativeLibrary { - private static final TruffleLogger LOGGER = PythonLanguage.getLogger(NativeLibrary.class); - - /** - * This interface is intended to be implemented by enums. - */ - interface NativeFunction { - String signature(); - - String name(); - - int ordinal(); - } - - /** - * This is a helper exception that will be thrown in case a library is {@link #optional} and not - * available. - */ - public static final class NativeLibraryCannotBeLoaded extends RuntimeException { - private static final NativeLibraryCannotBeLoaded INSTANCE = new NativeLibraryCannotBeLoaded(); - private static final long serialVersionUID = 6066722947025284374L; - - private NativeLibraryCannotBeLoaded() { - super(null, null); - } - - @SuppressWarnings("sync-override") - @Override - public Throwable fillInStackTrace() { - return this; - } - } - - private final int functionsCount; - private final String name; - - /** - * If given functionality has a fully managed variant that can be configured, this help message - * should explain how to switch to it. It will be printed if loading of the native library - * fails. - */ - private final String noNativeAccessHelp; - private final boolean optional; - - private volatile FunctionWithSignature[] cachedFunctions; - private volatile Object cachedLibrary; - private volatile InteropLibrary cachedLibraryInterop; - - public NativeLibrary(String name, int functionsCount, String noNativeAccessHelp, boolean optional) { - this.functionsCount = functionsCount; - this.name = name; - this.noNativeAccessHelp = noNativeAccessHelp; - this.optional = optional; - } - - private Object getCachedLibrary(Node location, PythonContext context) { - if (cachedLibrary == null) { - // This should be a one-off thing for each context - CompilerDirectives.transferToInterpreter(); - synchronized (this) { - if (cachedLibrary == null) { - Object lib = loadLibrary(location, context); - if (lib != null) { - // order matters due to multi-threading cases. - cachedLibraryInterop = InteropLibrary.getUncached(lib); - cachedLibrary = lib; - } - } - } - } - return cachedLibrary; - } - - private FunctionWithSignature getCachedFunction(Node location, PythonContext context, NativeFunction function) { - Object lib = getCachedLibrary(location, context); - if (cachedFunctions == null) { - // This should be a one-off thing for each context - CompilerDirectives.transferToInterpreter(); - synchronized (this) { - if (cachedFunctions == null) { - cachedFunctions = new FunctionWithSignature[functionsCount]; - } - } - } - int functionIndex = function.ordinal(); - if (cachedFunctions[functionIndex] == null) { - // This should be a one-off thing for each context - CompilerDirectives.transferToInterpreter(); - synchronized (this) { - FunctionWithSignature signature = getFunction(location, context, lib, function); - VarHandle.storeStoreFence(); - // it is OK to overwrite cachedFunctions[functionIndex] that may have been - // written from another thread: no need to double-check that it's still null. - // dummy is volatile, the object must be fully initialized at this point - cachedFunctions[functionIndex] = signature; - } - } - return cachedFunctions[functionIndex]; - } - - private FunctionWithSignature getFunction(Node location, PythonContext context, NativeFunction function) { - CompilerAsserts.neverPartOfCompilation(); - Object lib = getCachedLibrary(location, context); - return getFunction(location, context, lib, function); - } - - private Object parseSignature(Node location, PythonContext context, String signature) { - Source sigSource = Source.newBuilder(J_NFI_LANGUAGE, signature, "python-nfi-signature").build(); - return callCallTarget(context.getEnv().parseInternal(sigSource), location); - } - - private FunctionWithSignature getFunction(Node location, PythonContext context, Object lib, NativeFunction function) { - CompilerAsserts.neverPartOfCompilation(); - try { - Object signature = parseSignature(location, context, function.signature()); - Object symbol = cachedLibraryInterop.readMember(lib, function.name()); - return new FunctionWithSignature(signature, symbol); - } catch (UnsupportedMessageException | UnknownIdentifierException e) { - throw new IllegalStateException(String.format("Cannot load symbol '%s' from the internal shared library '%s'", function.name(), name), e); - } - } - - private Object loadLibrary(Node location, PythonContext context) { - CompilerAsserts.neverPartOfCompilation(); - if (context.isNativeAccessAllowed()) { - String path = getLibPath(context, name); - String src = String.format("load (RTLD_LOCAL) \"%s\"", path); - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine(String.format("Loading native library %s from path %s", name, path)); - } - Source loadSrc = Source.newBuilder(J_NFI_LANGUAGE, src, "load:" + name).internal(true).build(); - try { - return context.getEnv().parseInternal(loadSrc).call(location); - } catch (RuntimeException ex) { - Level level = optional ? Level.FINE : Level.SEVERE; - if (LOGGER.isLoggable(level)) { - LOGGER.log(level, ex, () -> String.format("Error while opening shared library at '%s'.\nFull NFI source: %s.", path, src)); - } - if (!optional) { - throw new RuntimeException(String.format( - "Cannot load supporting native library '%s'. " + - "Either the shared library file does not exist, or your system may be missing some dependencies. " + - "Turn on logging with --log.%s.level=INFO for more details. %s", - name, - NativeLibrary.class.getName(), - noNativeAccessHelp)); - } - } - } else { - throw new RuntimeException(String.format( - "Cannot load supporting native library '%s' because the native access is not allowed. " + - "The native access should be allowed when running GraalPython via the graalpython command. " + - "If you are embedding GraalPy using the Context API, make sure to allow native access using 'allowNativeAccess(true)'. %s", - name, - noNativeAccessHelp)); - } - throw NativeLibraryCannotBeLoaded.INSTANCE; - } - - private static String getLibPath(PythonContext context, String name) { - CompilerAsserts.neverPartOfCompilation(); - TruffleFile homePath = context.getEnv().getInternalTruffleFile(context.getCAPIHome().toJavaStringUncached()); - TruffleFile file = homePath.resolve(name); - return file.getPath(); - } - - protected Object callUncached(PythonContext context, NativeFunction f, Object... args) { - CompilerAsserts.neverPartOfCompilation(); - final Object lib = getCachedLibrary(null, context); - if (lib != null) { - try { - Object signature = parseSignature(null, context, f.signature()); - Object symbol = cachedLibraryInterop.readMember(lib, f.name()); - return SignatureLibrary.getUncached().call(signature, symbol, args); - } catch (Exception e) { - throw CompilerDirectives.shouldNotReachHere(f.name(), e); - } - } - return null; - } - - public static & NativeFunction> TypedNativeLibrary create(String name, T[] functions, String noNativeAccessHelp, boolean canIgnore) { - return new TypedNativeLibrary<>(name, functions.length, noNativeAccessHelp, canIgnore); - } - - public static final class TypedNativeLibrary & NativeFunction> extends NativeLibrary { - public TypedNativeLibrary(String name, int functionsCount, String noNativeAccessHelp, boolean canIgnore) { - super(name, functionsCount, noNativeAccessHelp, canIgnore); - } - } -} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/FunctionWithSignature.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/FunctionWithSignature.java deleted file mode 100644 index 994acf2d87..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/FunctionWithSignature.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 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 - * - * Subject to the condition set forth below, permission is hereby granted to any - * person obtaining a copy of this software, associated documentation and/or - * data (collectively the "Software"), free of charge and under any and all - * copyright rights in the Software, and any and all patent rights owned or - * freely licensable by each licensor hereunder covering either (i) the - * unmodified Software as contributed to or provided by such licensor, or (ii) - * the Larger Works (as defined below), to deal in both - * - * (a) the Software, and - * - * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if - * one is included with the Software each a "Larger Work" to which the Software - * is contributed by such licensors), - * - * without restriction, including without limitation the rights to copy, create - * derivative works of, display, perform, and distribute the Software and make, - * use, sell, offer for sale, import, export, have made, and have sold the - * Software and the Larger Work(s), and to sublicense the foregoing rights on - * either these or other terms. - * - * This license is subject to the following condition: - * - * The above copyright notice and either this complete permission notice or at a - * minimum a reference to the UPL must be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package com.oracle.graal.python.util; - -public final record FunctionWithSignature(Object signature, Object function) { -}