com.oracle.graal.python.runtime.NativeLibrary Maven / Gradle / Ivy
/*
* Copyright (c) 2020, 2023, 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.TS_ENCODING;
import java.util.Objects;
import java.util.logging.Level;
import com.oracle.graal.python.PythonLanguage;
import com.oracle.graal.python.nodes.PNodeWithContext;
import com.oracle.graal.python.runtime.NativeLibraryFactory.InvokeNativeFunctionNodeGen;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NeverDefault;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.ArityException;
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.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.InlinedExactClassProfile;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.strings.TruffleString;
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.
*
* For now, until there is no need to access the library and function objects directly, this object
* is opaque to the outside code and the only entrypoint is {@link InvokeNativeFunction}, which
* lazily loads the library, the requested function and invokes it. This node takes care of
* efficient caching of the loaded NFI objects.
*/
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();
}
enum NFIBackend {
NATIVE(""),
LLVM("with llvm ");
private final String withClause;
NFIBackend(String withClause) {
this.withClause = withClause;
}
}
/**
* 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;
private final NFIBackend nfiBackend;
/**
* 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 Object[] cachedFunctions;
private volatile Object cachedLibrary;
private volatile InteropLibrary cachedLibraryInterop;
private volatile Object dummy;
public NativeLibrary(String name, int functionsCount, NFIBackend nfiBackend, String noNativeAccessHelp, boolean optional) {
this.functionsCount = functionsCount;
this.name = name;
this.nfiBackend = nfiBackend;
this.noNativeAccessHelp = noNativeAccessHelp;
this.optional = optional;
}
private Object getCachedLibrary(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(context);
if (lib != null) {
// order matters due to multi-threading cases.
cachedLibraryInterop = InteropLibrary.getUncached(lib);
cachedLibrary = lib;
}
}
}
}
return cachedLibrary;
}
private Object getCachedFunction(PythonContext context, NativeFunction function) {
Object lib = getCachedLibrary(context);
if (cachedFunctions == null) {
// This should be a one-off thing for each context
CompilerDirectives.transferToInterpreter();
synchronized (this) {
if (cachedFunctions == null) {
cachedFunctions = new Object[functionsCount];
}
}
}
int functionIndex = function.ordinal();
if (cachedFunctions[functionIndex] == null) {
// This should be a one-off thing for each context
CompilerDirectives.transferToInterpreter();
synchronized (this) {
dummy = getFunction(context, lib, function);
// 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] = dummy;
}
}
return cachedFunctions[functionIndex];
}
private Object getFunction(PythonContext context, NativeFunction function) {
CompilerAsserts.neverPartOfCompilation();
Object lib = getCachedLibrary(context);
return getFunction(context, lib, function);
}
private Object parseSignature(PythonContext context, String signature) {
Source sigSource = Source.newBuilder(J_NFI_LANGUAGE, nfiBackend.withClause + signature, "python-nfi-signature").build();
return context.getEnv().parseInternal(sigSource).call();
}
private Object getFunction(PythonContext context, Object lib, NativeFunction function) {
CompilerAsserts.neverPartOfCompilation();
try {
Object signature = parseSignature(context, function.signature());
Object symbol = cachedLibraryInterop.readMember(lib, function.name());
return SignatureLibrary.getUncached().bind(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(PythonContext context) {
CompilerAsserts.neverPartOfCompilation();
if (context.isNativeAccessAllowed()) {
String path = getLibPath(context, name);
String src = String.format("%sload (RTLD_LOCAL) \"%s\"", nfiBackend.withClause, path);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(String.format("Loading native library %s from path %s %s", name, path, nfiBackend.withClause));
}
Source loadSrc = Source.newBuilder(J_NFI_LANGUAGE, src, "load:" + name).internal(true).build();
try {
return context.getEnv().parseInternal(loadSrc).call();
} 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 GraalPython 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(context);
if (lib != null) {
try {
Object signature = parseSignature(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 create(name, functions, NFIBackend.NATIVE, noNativeAccessHelp, canIgnore);
}
public static & NativeFunction> TypedNativeLibrary create(String name, T[] functions, NFIBackend nfiBackendName, String noNativeAccessHelp, boolean canIgnore) {
return new TypedNativeLibrary<>(name, functions.length, nfiBackendName, noNativeAccessHelp, canIgnore);
}
public static final class TypedNativeLibrary & NativeFunction> extends NativeLibrary {
public TypedNativeLibrary(String name, int functionsCount, NFIBackend nfiBackendName, String noNativeAccessHelp, boolean canIgnore) {
super(name, functionsCount, nfiBackendName, noNativeAccessHelp, canIgnore);
}
}
public abstract static class InvokeNativeFunction extends PNodeWithContext {
private static final InvokeNativeFunction UNCACHED = InvokeNativeFunctionNodeGen.create(InteropLibrary.getUncached());
@Child private InteropLibrary resultInterop;
@Child private TruffleString.SwitchEncodingNode switchEncodingNode;
public InvokeNativeFunction(InteropLibrary resultInterop) {
this.resultInterop = resultInterop;
}
@NeverDefault
public static InvokeNativeFunction create() {
return InvokeNativeFunctionNodeGen.create(null);
}
public static InvokeNativeFunction getUncached() {
return UNCACHED;
}
public & NativeFunction> Object call(TypedNativeLibrary lib, T function, Object... args) {
return execute(lib, function, args);
}
public & NativeFunction> long callLong(TypedNativeLibrary lib, T function, Object... args) {
try {
return ensureResultInterop().asLong(call(lib, function, args));
} catch (UnsupportedMessageException e) {
throw CompilerDirectives.shouldNotReachHere(function.name(), e);
}
}
public & NativeFunction> int callInt(TypedNativeLibrary lib, T function, Object... args) {
try {
return ensureResultInterop().asInt(call(lib, function, args));
} catch (UnsupportedMessageException e) {
throw CompilerDirectives.shouldNotReachHere(function.name(), e);
}
}
public & NativeFunction> TruffleString callString(TypedNativeLibrary lib, T function, Object... args) {
try {
return ensureSwitchEncoding().execute(ensureResultInterop().asTruffleString(call(lib, function, args)), TS_ENCODING);
} catch (UnsupportedMessageException e) {
throw CompilerDirectives.shouldNotReachHere(function.name(), e);
}
}
protected abstract Object execute(NativeLibrary lib, NativeFunction function, Object[] args);
@Specialization(guards = {"isSingleContext()", "function == cachedFunction", "lib == cachedLib"}, limit = "3")
static Object doSingleContext(@SuppressWarnings("unused") NativeLibrary lib, @SuppressWarnings("unused") NativeFunction function, Object[] args,
@SuppressWarnings("unused") @Cached(value = "lib", weak = true) NativeLibrary cachedLib,
@Cached("function") NativeFunction cachedFunction,
@Cached(value = "getFunction(lib, function)", weak = true) Object funObj,
@CachedLibrary("funObj") InteropLibrary funInterop) {
return invoke(cachedFunction, args, funObj, funInterop);
}
@Specialization(replaces = "doSingleContext")
static Object doMultiContext(NativeLibrary lib, NativeFunction functionIn, Object[] args,
@Bind("this") Node inliningTarget,
@Cached InlinedExactClassProfile functionClassProfile,
@CachedLibrary(limit = "1") InteropLibrary funInterop) {
NativeFunction function = functionClassProfile.profile(inliningTarget, functionIn);
Object funObj = lib.getCachedFunction(PythonContext.get(funInterop), function);
return invoke(function, args, funObj, funInterop);
}
private static Object invoke(NativeFunction function, Object[] args, Object funObj, InteropLibrary funInterop) {
try {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest(buildLogMessage(function, args));
}
Object result = funInterop.execute(funObj, args);
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest(buildReturnLogMessage(function, result));
}
return result;
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
throw CompilerDirectives.shouldNotReachHere(function.name(), e);
}
}
protected Object getFunction(NativeLibrary lib, NativeFunction fun) {
return lib.getFunction(PythonContext.get(this), fun);
}
@TruffleBoundary
private static String buildLogMessage(NativeFunction function, Object[] args) {
StringBuilder sb = new StringBuilder("Executing native function ");
sb.append(function.name()).append(" with arguments: ");
for (Object arg : args) {
sb.append(safeToString(arg)).append(',');
}
return sb.toString();
}
@TruffleBoundary
private static String buildReturnLogMessage(NativeFunction function, Object result) {
return "Finished executing native function " + function.name() + " with result: " + safeToString(result);
}
private static String safeToString(Object value) {
try {
return Objects.toString(value);
} catch (Exception ex) {
return String.format("%s (toString threw %s),", value.getClass().getSimpleName(), ex.getClass().getSimpleName());
}
}
public InteropLibrary ensureResultInterop() {
if (resultInterop == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
resultInterop = insert(InteropLibrary.getFactory().createDispatched(3));
}
return resultInterop;
}
public TruffleString.SwitchEncodingNode ensureSwitchEncoding() {
if (switchEncodingNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
switchEncodingNode = insert(TruffleString.SwitchEncodingNode.create());
}
return switchEncodingNode;
}
}
}