package com.oracle.truffle.llvm.nativemode.runtime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.WeakHashMap;
import com.oracle.truffle.llvm.runtime.LLVMContext;
import com.oracle.truffle.llvm.runtime.LLVMFunction;
import com.oracle.truffle.llvm.runtime.LLVMFunctionCode;
import com.oracle.truffle.llvm.runtime.LibraryLocator;
import com.oracle.truffle.llvm.runtime.NativeContextExtension;
import org.graalvm.collections.EconomicMap;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.interop.InteropException;
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.source.Source;
import com.oracle.truffle.llvm.runtime.ContextExtension;
import com.oracle.truffle.llvm.runtime.except.LLVMLinkerException;
import com.oracle.truffle.llvm.runtime.interop.nfi.LLVMNativeWrapper;
import com.oracle.truffle.llvm.runtime.nodes.func.LLVMCallNode;
import com.oracle.truffle.llvm.runtime.options.SulongEngineOption;
import com.oracle.truffle.llvm.runtime.pointer.LLVMNativePointer;
import com.oracle.truffle.llvm.runtime.types.FunctionType;
import com.oracle.truffle.llvm.runtime.types.PointerType;
import com.oracle.truffle.llvm.runtime.types.PrimitiveType;
import com.oracle.truffle.llvm.runtime.types.PrimitiveType.PrimitiveKind;
import com.oracle.truffle.llvm.runtime.types.Type;
import com.oracle.truffle.llvm.runtime.types.VoidType;
import com.oracle.truffle.nfi.api.SignatureLibrary;
import org.graalvm.collections.Equivalence;
public final class NFIContextExtension extends NativeContextExtension {
private static final String SIGNATURE_SOURCE_NAME = "llvm-nfi-signature";
private static final int WELL_KNOWN_CACHE_INITIAL_SIZE = 8;
static final class WellKnownFunction {
final int index;
final String name;
final Source signatureSource;
WellKnownFunction(int index, String name, Source signatureSource) {
this.index = index;
this.name = name;
this.signatureSource = signatureSource;
}
}
private static final class SignatureSourceCache {
private final WeakHashMap<FunctionType, Source> sigCache;
private final WeakHashMap<FunctionType, Source> sigCacheSkipStackArg;
private final EconomicMap<String, WellKnownFunction> wellKnown;
private int nextIndex;
SignatureSourceCache() {
sigCache = new WeakHashMap<>();
sigCacheSkipStackArg = new WeakHashMap<>();
wellKnown = EconomicMap.create();
nextIndex = 0;
}
Source getSignatureSource(FunctionType type) throws UnsupportedNativeTypeException {
return getSignatureSource(type, sigCache, 0);
}
Source getSignatureSourceSkipStackArg(FunctionType type) throws UnsupportedNativeTypeException {
return getSignatureSource(type, sigCacheSkipStackArg, LLVMCallNode.USER_ARGUMENT_OFFSET);
}
private static Source getSignatureSource(FunctionType type, WeakHashMap<FunctionType, Source> map, int skipArgs) throws UnsupportedNativeTypeException {
synchronized (map) {
Source ret = map.get(type);
if (ret == null) {
String sig = getNativeSignature(type, skipArgs);
ret = Source.newBuilder("nfi", sig, SIGNATURE_SOURCE_NAME).build();
map.put(type, ret);
}
return ret;
}
}
synchronized WellKnownFunction getWellKnownFunction(String name, String signature) {
WellKnownFunction ret = wellKnown.get(name);
if (ret == null) {
Source signatureSource = Source.newBuilder("nfi", signature, SIGNATURE_SOURCE_NAME).build();
ret = new WellKnownFunction(nextIndex++, name, signatureSource);
wellKnown.put(name, ret);
}
return ret;
}
}
public static final class Factory implements ContextExtension.Factory<NativeContextExtension> {
private final SignatureSourceCache signatureSourceCache = new SignatureSourceCache();
@Override
public NativeContextExtension create(Env env) {
return new NFIContextExtension(env, signatureSourceCache);
}
}
private static final InteropLibrary INTEROP = InteropLibrary.getFactory().getUncached();
private Object defaultLibraryHandle;
private boolean internalLibrariesAdded = false;
private final List<Object> libraryHandles = new ArrayList<>();
private final EconomicMap<String, CallTarget> visited = EconomicMap.create();
private final Env env;
private final SignatureSourceCache signatureSourceCache;
private final EconomicMap<Source, Object> signatureCache = EconomicMap.create(Equivalence.IDENTITY);
private Object[] wellKnownFunctionCache;
private NFIContextExtension(Env env, SignatureSourceCache signatureSourceCache) {
assert env.getOptions().get(SulongEngineOption.ENABLE_NFI);
this.env = env;
this.signatureSourceCache = signatureSourceCache;
this.wellKnownFunctionCache = new Object[WELL_KNOWN_CACHE_INITIAL_SIZE];
}
@Override
public void initialize(LLVMContext context) {
assert !isInitialized();
if (!internalLibrariesAdded) {
TruffleFile file = locateInternalLibrary(context, "libsulong-native." + getNativeLibrarySuffix(), "<default nfi library>");
Object lib = loadLibrary(file.getPath(), context);
if (lib instanceof CallTarget) {
libraryHandles.add(((CallTarget) lib).call());
}
Object defaultLib = loadDefaultLibrary();
if (defaultLib instanceof CallTarget) {
this.defaultLibraryHandle = ((CallTarget) defaultLib).call();
}
internalLibrariesAdded = true;
}
}
public boolean isInitialized() {
return defaultLibraryHandle != null;
}
@Override
public NativePointerIntoLibrary getNativeHandle(String name) {
CompilerAsserts.neverPartOfCompilation();
try {
NativeLookupResult result = getNativeDataObjectOrNull(name);
if (result != null) {
long pointer = INTEROP.asPointer(result.getObject());
return new NativePointerIntoLibrary(pointer);
}
return null;
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Override
@TruffleBoundary
public Object createNativeWrapper(LLVMFunction function, LLVMFunctionCode code) {
Object wrapper = null;
try {
Source signatureSource = signatureSourceCache.getSignatureSource(function.getType());
Object signature = getCachedSignature(signatureSource);
wrapper = SignatureLibrary.getUncached().createClosure(signature, new LLVMNativeWrapper(function, code));
} catch (UnsupportedNativeTypeException ex) {
}
return wrapper;
}
@Override
public synchronized void addLibraryHandles(Object library) {
CompilerAsserts.neverPartOfCompilation();
if (!libraryHandles.contains(library)) {
libraryHandles.add(library);
}
}
@Override
public synchronized CallTarget parseNativeLibrary(String path, LLVMContext context) throws UnsatisfiedLinkError {
CompilerAsserts.neverPartOfCompilation();
if (!visited.containsKey(path)) {
Object callTarget = loadLibrary(path, context);
if (callTarget != null) {
visited.put(path, (CallTarget) callTarget);
return (CallTarget) callTarget;
} else {
throw new IllegalStateException("Native library call target is null.");
}
} else {
return visited.get(path);
}
}
public static String getNativeLibrarySuffix() {
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
return "dylib";
} else {
return "so";
}
}
public static String getNativeLibrarySuffixVersioned(int version) {
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
return version + ".dylib";
} else {
return "so." + version;
}
}
private Object loadLibrary(String path, LLVMContext context) {
CompilerAsserts.neverPartOfCompilation();
return loadLibrary(path, false, null, context);
}
private Object loadLibrary(String path, boolean optional, String flags, LLVMContext context) {
LibraryLocator.traceLoadNative(context, path);
String loadExpression;
if (flags == null) {
loadExpression = String.format("load \"%s\"", path);
} else {
loadExpression = String.format("load(%s) \"%s\"", flags, path);
}
final Source source = Source.newBuilder("nfi", loadExpression, "(load " + path + ")").internal(true).build();
try {
return env.parseInternal(source);
} catch (UnsatisfiedLinkError ex) {
if (optional) {
return null;
} else {
throw ex;
}
}
}
private Object loadDefaultLibrary() {
CompilerAsserts.neverPartOfCompilation();
final Source source = Source.newBuilder("nfi", "default", "default").internal(true).build();
try {
return env.parseInternal(source);
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
}
private static Object getNativeFunctionOrNull(Object library, String name) {
CompilerAsserts.neverPartOfCompilation();
if (!INTEROP.isMemberReadable(library, name)) {
return null;
}
try {
return INTEROP.readMember(library, name);
} catch (UnknownIdentifierException ex) {
return null;
} catch (InteropException ex) {
throw new IllegalStateException(ex);
}
}
private static String getNativeType(Type type) throws UnsupportedNativeTypeException {
if (type instanceof FunctionType) {
return getNativeSignature((FunctionType) type, 0);
} else if (type instanceof PointerType && ((PointerType) type).getPointeeType() instanceof FunctionType) {
FunctionType functionType = (FunctionType) ((PointerType) type).getPointeeType();
return getNativeSignature(functionType, 0);
} else if (type instanceof PointerType) {
return "POINTER";
} else if (type instanceof PrimitiveType) {
PrimitiveType primitiveType = (PrimitiveType) type;
PrimitiveKind kind = primitiveType.getPrimitiveKind();
switch (kind) {
case I1:
case I8:
return "SINT8";
case I16:
return "SINT16";
case I32:
return "SINT32";
case I64:
return "SINT64";
case FLOAT:
return "FLOAT";
case DOUBLE:
return "DOUBLE";
default:
throw new UnsupportedNativeTypeException(primitiveType);
}
} else if (type instanceof VoidType) {
return "VOID";
}
throw new UnsupportedNativeTypeException(type);
}
private static String[] getNativeArgumentTypes(FunctionType functionType, int skipArguments) throws UnsupportedNativeTypeException {
String[] types = new String[functionType.getNumberOfArguments() - skipArguments];
for (int i = skipArguments; i < functionType.getNumberOfArguments(); i++) {
types[i - skipArguments] = getNativeType(functionType.getArgumentType(i));
}
return types;
}
@Override
public synchronized NativeLookupResult getNativeFunctionOrNull(String name) {
CompilerAsserts.neverPartOfCompilation();
Object[] cursor = libraryHandles.toArray();
for (int i = 0; i < cursor.length; i++) {
Object symbol = getNativeFunctionOrNull(cursor[i], name);
if (symbol != null) {
return new NativeLookupResult(symbol);
}
}
Object symbol = getNativeFunctionOrNull(defaultLibraryHandle, name);
if (symbol != null) {
assert isInitialized();
return new NativeLookupResult(symbol);
}
return null;
}
private synchronized NativeLookupResult getNativeDataObjectOrNull(String name) {
CompilerAsserts.neverPartOfCompilation();
Object[] cursor = libraryHandles.toArray();
for (int i = 0; i < cursor.length; i++) {
Object symbol = getNativeFunctionOrNull(cursor[i], name);
if (symbol != null) {
return new NativeLookupResult(symbol);
}
}
Object symbol = getNativeDataObjectOrNull(defaultLibraryHandle, name);
if (symbol != null) {
assert isInitialized();
return new NativeLookupResult(symbol);
}
return null;
}
private static Object getNativeDataObjectOrNull(Object libraryHandle, String name) {
try {
Object symbol = INTEROP.readMember(libraryHandle, name);
if (symbol != null && 0 != INTEROP.asPointer(symbol)) {
return symbol;
} else {
return null;
}
} catch (UnknownIdentifierException ex) {
return null;
} catch (InteropException ex) {
throw new IllegalStateException(ex);
}
}
private static Object bindNativeFunction(Object symbol, String signature) {
CompilerAsserts.neverPartOfCompilation();
try {
return INTEROP.invokeMember(symbol, "bind", signature);
} catch (InteropException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public Object getNativeFunction(String name, String signature) {
CompilerAsserts.neverPartOfCompilation();
NativeLookupResult result = getNativeFunctionOrNull(name);
if (result != null) {
return bindNativeFunction(result.getObject(), signature);
}
throw new LLVMLinkerException(String.format("External function %s cannot be found.", name));
}
@Override
public WellKnownNativeFunctionNode getWellKnownNativeFunction(String name, String signature) {
CompilerAsserts.neverPartOfCompilation();
WellKnownFunction fn = signatureSourceCache.getWellKnownFunction(name, signature);
return WellKnownNFIFunctionNodeGen.create(fn);
}
private Object createWellKnownFunction(WellKnownFunction fn) {
CompilerAsserts.neverPartOfCompilation();
NativeLookupResult result = getNativeFunctionOrNull(fn.name);
if (result != null) {
CallTarget parsedSignature = env.parseInternal(fn.signatureSource);
Object signature = parsedSignature.call();
return SignatureLibrary.getUncached().bind(signature, result.getObject());
}
throw new LLVMLinkerException(String.format("External function %s cannot be found.", fn.name));
}
@TruffleBoundary
private Object getWellKnownFuctionSlowPath(WellKnownFunction fn) {
synchronized (this) {
if (wellKnownFunctionCache.length <= fn.index) {
int newLength = wellKnownFunctionCache.length * 2;
assert fn.index < signatureSourceCache.nextIndex;
while (newLength < signatureSourceCache.nextIndex) {
newLength *= 2;
}
wellKnownFunctionCache = Arrays.copyOf(wellKnownFunctionCache, newLength);
}
Object ret = wellKnownFunctionCache[fn.index];
if (ret == null) {
ret = createWellKnownFunction(fn);
wellKnownFunctionCache[fn.index] = ret;
}
return ret;
}
}
Object getCachedWellKnownFunction(WellKnownFunction fn) {
if (fn.index < wellKnownFunctionCache.length) {
Object ret = wellKnownFunctionCache[fn.index];
if (ret != null) {
return ret;
}
}
return getWellKnownFuctionSlowPath(fn);
}
@Override
public Source getNativeSignatureSourceSkipStackArg(FunctionType type) throws UnsupportedNativeTypeException {
CompilerAsserts.neverPartOfCompilation();
return signatureSourceCache.getSignatureSourceSkipStackArg(type);
}
@TruffleBoundary
private Object createSignature(Source signatureSource) {
synchronized (signatureCache) {
Object ret = signatureCache.get(signatureSource);
if (ret == null) {
CallTarget createSignature = env.parseInternal(signatureSource);
ret = createSignature.call();
signatureCache.put(signatureSource, ret);
}
return ret;
}
}
private Object getCachedSignature(Source signatureSource) {
Object ret = signatureCache.get(signatureSource);
if (ret == null) {
ret = createSignature(signatureSource);
}
return ret;
}
@Override
public Object bindSignature(LLVMFunctionCode function, Source signatureSource) {
CompilerAsserts.neverPartOfCompilation();
Object nativeFunction = function.getNativeFunctionSlowPath();
Object signature = getCachedSignature(signatureSource);
return SignatureLibrary.getUncached().bind(signature, nativeFunction);
}
@Override
public Object bindSignature(long fnPtr, Source signatureSource) {
CompilerAsserts.neverPartOfCompilation();
Object signature = getCachedSignature(signatureSource);
return SignatureLibrary.getUncached().bind(signature, LLVMNativePointer.create(fnPtr));
}
private static String getNativeSignature(FunctionType type, int skipArguments) throws UnsupportedNativeTypeException {
CompilerAsserts.neverPartOfCompilation();
String nativeRet = getNativeType(type.getReturnType());
String[] argTypes = getNativeArgumentTypes(type, skipArguments);
StringBuilder sb = new StringBuilder();
sb.append("(");
for (String a : argTypes) {
sb.append(a);
sb.append(",");
}
if (argTypes.length > 0) {
sb.setCharAt(sb.length() - 1, ')');
} else {
sb.append(')');
}
sb.append(":");
sb.append(nativeRet);
return sb.toString();
}
}