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; } 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/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