All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.nfi.backend.libffi.LibFFISignature Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2017, 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.truffle.nfi.backend.libffi;

import java.util.Arrays;

import com.oracle.truffle.api.CallTarget;
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.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateAOT;
import com.oracle.truffle.api.dsl.ImportStatic;
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.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
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.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.nfi.backend.libffi.FunctionExecuteNode.SignatureExecuteNode;
import com.oracle.truffle.nfi.backend.libffi.LibFFIClosure.MonomorphicClosureInfo;
import com.oracle.truffle.nfi.backend.libffi.LibFFIClosure.PolymorphicClosureInfo;
import com.oracle.truffle.nfi.backend.libffi.LibFFIType.ArrayType;
import com.oracle.truffle.nfi.backend.libffi.LibFFIType.CachedTypeInfo;
import com.oracle.truffle.nfi.backend.libffi.LibFFIType.Direction;
import com.oracle.truffle.nfi.backend.libffi.NativeAllocation.FreeDestructor;
import com.oracle.truffle.nfi.backend.spi.NFIBackendSignatureBuilderLibrary;
import com.oracle.truffle.nfi.backend.spi.NFIBackendSignatureLibrary;
import com.oracle.truffle.nfi.backend.spi.types.NativeSimpleType;
import com.oracle.truffle.nfi.backend.spi.util.ProfiledArrayBuilder;
import com.oracle.truffle.nfi.backend.spi.util.ProfiledArrayBuilder.ArrayBuilderFactory;
import com.oracle.truffle.nfi.backend.spi.util.ProfiledArrayBuilder.ArrayFactory;

/**
 * Runtime object representing native signatures. Instances of this class can not be cached in
 * shared AST nodes, since they contain references to native datastructures of libffi.
 *
 * All information that is context and process independent is collected in a separate object,
 * {@link CachedSignatureInfo}. Two {@link LibFFISignature} objects that have the same
 * {@link CachedSignatureInfo} are guaranteed to behave the same semantically.
 */
@ExportLibrary(value = NFIBackendSignatureLibrary.class, useForAOT = true, useForAOTPriority = 1)
final class LibFFISignature {

    @TruffleBoundary
    @NeverDefault
    public static LibFFISignature create(LibFFIContext context, CachedSignatureInfo info, LibFFIType retType, int argCount, int fixedArgCount, LibFFIType[] argTypes) {
        LibFFIType realRetType = retType;
        if (retType == null) {
            realRetType = context.lookupSimpleType(NativeSimpleType.VOID);
        }

        long cif;
        if (fixedArgCount == SignatureBuilder.NOT_VARARGS) {
            cif = context.prepareSignature(realRetType, argCount, argTypes);
        } else {
            cif = context.prepareSignatureVarargs(realRetType, argCount, fixedArgCount, argTypes);
        }
        if (cif == 0) {
            throw CompilerDirectives.shouldNotReachHere(String.format("invalid signature (ret: %s, argCount: %d, fixedArgCount: %d, args: %s)",
                            retType, argCount, fixedArgCount, Arrays.toString(argTypes)));
        }
        return create(cif, info);
    }

    private static LibFFISignature create(long cif, CachedSignatureInfo info) {
        assert cif > 0;
        LibFFISignature ret = new LibFFISignature(cif, info);
        NativeAllocation.getGlobalQueue().registerNativeAllocation(ret, new FreeDestructor(cif));
        return ret;
    }

    @ExportMessage
    static class Call {

        @Specialization
        static Object callLibFFI(LibFFISignature self, LibFFISymbol functionPointer, Object[] args,
                        @Bind("$node") Node node,
                        @Cached.Exclusive @Cached FunctionExecuteNode functionExecute) throws ArityException, UnsupportedTypeException {
            long pointer = functionPointer.asPointer();
            return functionExecute.execute(node, pointer, self, args);
        }

        @Specialization(limit = "3")
        @GenerateAOT.Exclude
        static Object callGeneric(LibFFISignature self, Object functionPointer, Object[] args,
                        @CachedLibrary("functionPointer") InteropLibrary interop,
                        @Bind("$node") Node node,
                        @Cached InlinedBranchProfile isExecutable,
                        @Cached InlinedBranchProfile toNative,
                        @Cached InlinedBranchProfile error,
                        @Cached.Exclusive @Cached FunctionExecuteNode functionExecute) throws ArityException, UnsupportedTypeException {
            if (interop.isExecutable(functionPointer)) {
                // This branch can be invoked when SignatureLibrary is used to invoke a function
                // pointer without prior engaging the interop to execute executable function
                // pointers. It may happen, for example, in SVM for function substitutes.
                try {
                    isExecutable.enter(node);
                    return interop.execute(functionPointer, args);
                } catch (UnsupportedMessageException e) {
                    error.enter(node);
                    throw UnsupportedTypeException.create(new Object[]{functionPointer}, "functionPointer was executable but threw UnsupportedMessageException on execute()");
                }
            }
            if (!interop.isPointer(functionPointer)) {
                toNative.enter(node);
                interop.toNative(functionPointer);
            }
            long pointer;
            try {
                pointer = interop.asPointer(functionPointer);
            } catch (UnsupportedMessageException e) {
                error.enter(node);
                throw UnsupportedTypeException.create(new Object[]{functionPointer}, "functionPointer is not executable and not a pointer");
            }
            return functionExecute.execute(node, pointer, self, args);
        }
    }

    @ExportMessage
    @ImportStatic(LibFFILanguage.class)
    static class CreateClosure {

        @Specialization(guards = {"signature.signatureInfo == cachedSignatureInfo", "executable == cachedExecutable"}, assumptions = "getSingleContextAssumption()", limit = "3")
        static LibFFIClosure doCachedExecutable(LibFFISignature signature, Object executable,
                        @Cached("signature.signatureInfo") CachedSignatureInfo cachedSignatureInfo,
                        @Cached("executable") Object cachedExecutable,
                        @CachedLibrary("signature") NFIBackendSignatureLibrary self,
                        @Cached("create(cachedSignatureInfo, cachedExecutable)") MonomorphicClosureInfo cachedClosureInfo) {
            assert signature.signatureInfo == cachedSignatureInfo && executable == cachedExecutable;
            // no need to cache duplicated allocation in the single-context case
            // the NFI frontend is taking care of that already
            ClosureNativePointer nativePointer = cachedClosureInfo.allocateClosure(LibFFIContext.get(self), signature);
            return LibFFIClosure.newClosureWrapper(nativePointer);
        }

        @Specialization(replaces = "doCachedExecutable", guards = "signature.signatureInfo == cachedSignatureInfo", limit = "3")
        static LibFFIClosure doCachedSignature(LibFFISignature signature, Object executable,
                        @Cached("signature.signatureInfo") CachedSignatureInfo cachedSignatureInfo,
                        @CachedLibrary("signature") NFIBackendSignatureLibrary self,
                        @Cached("create(cachedSignatureInfo)") PolymorphicClosureInfo cachedClosureInfo) {
            assert signature.signatureInfo == cachedSignatureInfo;
            ClosureNativePointer nativePointer = cachedClosureInfo.allocateClosure(LibFFIContext.get(self), signature, executable);
            return LibFFIClosure.newClosureWrapper(nativePointer);
        }

        @TruffleBoundary
        @Specialization(replaces = "doCachedSignature")
        static LibFFIClosure createClosure(LibFFISignature signature, Object executable,
                        @CachedLibrary("signature") NFIBackendSignatureLibrary self) {
            PolymorphicClosureInfo cachedClosureInfo = signature.signatureInfo.getCachedClosureInfo();
            ClosureNativePointer nativePointer = cachedClosureInfo.allocateClosure(LibFFIContext.get(self), signature, executable);
            return LibFFIClosure.newClosureWrapper(nativePointer);
        }
    }

    @TruffleBoundary
    @NeverDefault
    public static CachedSignatureInfo prepareSignatureInfo(CachedTypeInfo retTypeInfo, ArgsState state) {
        if (retTypeInfo instanceof ArrayType) {
            throw new IllegalArgumentException("array type as return value is not supported");
        }

        boolean allowJavaToNativeCall = state.allowJavaToNativeCall;
        boolean allowNativeToJavaCall = state.allowNativeToJavaCall;

        switch (retTypeInfo.allowedDataFlowDirection) {
            /*
             * If the call goes from Java to native, the return value flows from native-to-Java, and
             * vice-versa.
             */
            case JAVA_TO_NATIVE_ONLY:
                allowJavaToNativeCall = false;
                break;
            case NATIVE_TO_JAVA_ONLY:
                allowNativeToJavaCall = false;
                break;
        }

        Direction allowedCallDirection;
        if (allowNativeToJavaCall) {
            if (allowJavaToNativeCall) {
                allowedCallDirection = Direction.BOTH;
            } else {
                allowedCallDirection = Direction.NATIVE_TO_JAVA_ONLY;
            }
        } else {
            if (allowJavaToNativeCall) {
                allowedCallDirection = Direction.JAVA_TO_NATIVE_ONLY;
            } else {
                throw new IllegalArgumentException("invalid signature");
            }
        }

        CachedTypeInfo[] argTypesInfo = new CachedTypeInfo[state.argCount];
        ArgsState curState = state;
        for (int i = state.argCount - 1; i >= 0; i--) {
            argTypesInfo[i] = curState.lastArg;
            curState = curState.prev;
        }

        return new CachedSignatureInfo(LibFFILanguage.get(null), retTypeInfo, argTypesInfo, state.primitiveSize, state.objectCount, allowedCallDirection);
    }

    private final long cif; // native pointer
    final CachedSignatureInfo signatureInfo;

    private LibFFISignature(long cif, CachedSignatureInfo signatureInfo) {
        this.cif = cif;
        this.signatureInfo = signatureInfo;
    }

    /**
     * This class contains all information about native signatures that can be shared across
     * contexts. Instances of this class can safely be cached in shared AST. This contains a shared
     * {@link CallTarget} that can be used to call functions of that signature.
     */
    static final class CachedSignatureInfo {

        final CachedTypeInfo retType;
        @CompilationFinal(dimensions = 1) final CachedTypeInfo[] argTypes;

        final int primitiveSize;
        final int objectCount;

        final Direction allowedCallDirection;

        final CallTarget callTarget;

        PolymorphicClosureInfo cachedClosureInfo;

        CachedSignatureInfo(LibFFILanguage language, CachedTypeInfo retType, CachedTypeInfo[] argTypes, int primitiveSize, int objectCount, Direction allowedCallDirection) {
            this.retType = retType;
            this.argTypes = argTypes;
            this.primitiveSize = primitiveSize;
            this.objectCount = objectCount;
            this.allowedCallDirection = allowedCallDirection;

            this.callTarget = new SignatureExecuteNode(language, this).getCallTarget();
        }

        NativeArgumentBuffer.Array prepareBuffer() {
            return new NativeArgumentBuffer.Array(primitiveSize, objectCount);
        }

        CachedTypeInfo[] getArgTypes() {
            return argTypes;
        }

        CachedTypeInfo getRetType() {
            return retType;
        }

        Direction getAllowedCallDirection() {
            return allowedCallDirection;
        }

        Object execute(Node node, LibFFISignature signature, LibFFIContext ctx, long functionPointer, NativeArgumentBuffer.Array argBuffer) {
            assert signature.signatureInfo == this;
            CachedTypeInfo localRetType = retType;
            CompilerAsserts.partialEvaluationConstant(localRetType);
            if (localRetType == null) {
                throw CompilerDirectives.shouldNotReachHere();
            } else if (localRetType instanceof LibFFIType.ObjectType) {
                Object ret = ctx.executeObject(signature.cif, functionPointer, argBuffer.prim, argBuffer.getPatchCount(), argBuffer.patches, argBuffer.objects);
                if (ret == null) {
                    return NativePointer.NULL;
                } else {
                    return ret;
                }
            } else if (localRetType instanceof LibFFIType.SimpleType) {
                LibFFIType.SimpleType simpleType = (LibFFIType.SimpleType) localRetType;
                long ret = ctx.executePrimitive(signature.cif, functionPointer, argBuffer.prim, argBuffer.getPatchCount(), argBuffer.patches, argBuffer.objects);
                return simpleType.fromPrimitive(ret);
            } else {
                NativeArgumentBuffer.Array retBuffer = new NativeArgumentBuffer.Array(localRetType.size, localRetType.objectCount);
                ctx.executeNative(signature.cif, functionPointer, argBuffer.prim, argBuffer.getPatchCount(), argBuffer.patches, argBuffer.objects, retBuffer.prim);
                return localRetType.deserializeRet(node, retBuffer);
            }
        }

        @TruffleBoundary
        private synchronized void initCachedClosureInfo() {
            if (cachedClosureInfo == null) {
                cachedClosureInfo = PolymorphicClosureInfo.create(this);
            }
        }

        @NeverDefault
        PolymorphicClosureInfo getCachedClosureInfo() {
            if (cachedClosureInfo == null) {
                initCachedClosureInfo();
            }
            assert cachedClosureInfo != null;
            return cachedClosureInfo;
        }
    }

    static final class ArgsState {

        static final ArgsState NO_ARGS = new ArgsState(0, 0, 0, true, true, null, null);

        final int argCount;

        final int primitiveSize;
        final int objectCount;

        final boolean allowJavaToNativeCall;
        final boolean allowNativeToJavaCall;

        final CachedTypeInfo lastArg;
        final ArgsState prev;

        ArgsState(int argCount, int primitiveSize, int objectCount, boolean allowJavaToNativeCall, boolean allowNativeToJavaCall, CachedTypeInfo lastArg, ArgsState prev) {
            this.argCount = argCount;
            this.primitiveSize = primitiveSize;
            this.objectCount = objectCount;
            this.allowJavaToNativeCall = allowJavaToNativeCall;
            this.allowNativeToJavaCall = allowNativeToJavaCall;
            this.lastArg = lastArg;
            this.prev = prev;
        }

        @NeverDefault
        ArgsState addArg(CachedTypeInfo typeInfo) {
            if (typeInfo instanceof LibFFIType.VoidType) {
                throw new IllegalArgumentException("void is not a valid argument type");
            }

            boolean newAllowNativeToJavaCall = this.allowNativeToJavaCall;
            boolean newAllowJavaToNativeCall = this.allowJavaToNativeCall;
            switch (typeInfo.allowedDataFlowDirection) {
                case JAVA_TO_NATIVE_ONLY:
                    newAllowNativeToJavaCall = false;
                    break;
                case NATIVE_TO_JAVA_ONLY:
                    newAllowJavaToNativeCall = false;
                    break;
            }

            int align = typeInfo.alignment;
            int newPrimitiveSize = this.primitiveSize;
            if (primitiveSize % align != 0) {
                newPrimitiveSize += align - (primitiveSize % align);
            }
            newPrimitiveSize += typeInfo.size;

            int newObjectCount = this.objectCount + typeInfo.objectCount;

            return new ArgsState(argCount + 1, newPrimitiveSize, newObjectCount, newAllowJavaToNativeCall, newAllowNativeToJavaCall, typeInfo, this);
        }
    }

    @ExportLibrary(NFIBackendSignatureBuilderLibrary.class)
    static final class SignatureBuilder {

        static final int NOT_VARARGS = -1;

        @NeverDefault ArgsState state;
        CachedTypeInfo retTypeInfo;

        LibFFIType retType;
        ProfiledArrayBuilder argTypes;

        int fixedArgCount;

        private static final ArrayFactory FACTORY = new ArrayFactory<>() {

            @Override
            public LibFFIType[] create(int size) {
                return new LibFFIType[size];
            }
        };

        SignatureBuilder(ArrayBuilderFactory factory) {
            this.state = ArgsState.NO_ARGS;
            this.fixedArgCount = NOT_VARARGS;
            this.argTypes = factory.allocate(FACTORY);
        }

        void addArg(LibFFIType arg, ArgsState newState) {
            assert state.argCount + 1 == newState.argCount;
            argTypes.add(arg);
            state = newState;
        }

        LibFFIType maybePromote(Node node, LibFFIType argType) {
            if (fixedArgCount == NOT_VARARGS) {
                return argType;
            } else {
                return argType.varargsPromoteType(node);
            }
        }

        @ExportMessage
        static class SetReturnType {

            @Specialization
            static void doSet(SignatureBuilder builder, LibFFIType retType) {
                builder.retType = retType;
                builder.retTypeInfo = retType.typeInfo;
            }
        }

        @ExportMessage
        static class AddArgument {

            static boolean isSame(CachedTypeInfo v0, CachedTypeInfo v1) {
                return v0 == v1;
            }

            @Specialization(guards = {"builder.state == oldState", "isSame(promotedType.typeInfo, cachedTypeInfo)"}, limit = "1")
            static void doCached(SignatureBuilder builder, @SuppressWarnings("unused") LibFFIType argType,
                            @SuppressWarnings("unused") @CachedLibrary("builder") NFIBackendSignatureBuilderLibrary self,
                            @Bind("builder.maybePromote(self, argType)") LibFFIType promotedType,
                            @Cached("builder.state") ArgsState oldState,
                            @Cached("promotedType.typeInfo") CachedTypeInfo cachedTypeInfo,
                            @Cached("oldState.addArg(cachedTypeInfo)") ArgsState newState) {
                assert builder.state == oldState && promotedType.typeInfo == cachedTypeInfo;
                builder.addArg(promotedType, newState);
            }

            @Specialization(replaces = "doCached")
            static void doGeneric(SignatureBuilder builder, LibFFIType argType,
                            @CachedLibrary("builder") NFIBackendSignatureBuilderLibrary self) {
                LibFFIType promotedType = builder.maybePromote(self, argType);
                ArgsState newState = builder.state.addArg(promotedType.typeInfo);
                builder.addArg(promotedType, newState);
            }
        }

        @ExportMessage
        void makeVarargs() {
            fixedArgCount = state.argCount;
        }

        @ExportMessage
        @ImportStatic(LibFFISignature.class)
        static class Build {

            @Specialization(guards = {"builder.state == cachedState", "builder.retTypeInfo == cachedRetType"}, limit = "1")
            static Object doCached(SignatureBuilder builder,
                            @Cached("builder.state") ArgsState cachedState,
                            @SuppressWarnings("unused") @Cached("builder.retType.typeInfo") CachedTypeInfo cachedRetType,
                            @CachedLibrary("builder") NFIBackendSignatureBuilderLibrary self,
                            @Cached("prepareSignatureInfo(cachedRetType, cachedState)") CachedSignatureInfo cachedSigInfo) {
                return create(LibFFIContext.get(self), cachedSigInfo, builder.retType, cachedState.argCount, builder.fixedArgCount, builder.argTypes.getFinalArray());
            }

            @Specialization(replaces = "doCached")
            static Object doGeneric(SignatureBuilder builder,
                            @CachedLibrary("builder") NFIBackendSignatureBuilderLibrary self) {
                CachedSignatureInfo sigInfo = prepareSignatureInfo(builder.retType.typeInfo, builder.state);
                return create(LibFFIContext.get(self), sigInfo, builder.retType, builder.state.argCount, builder.fixedArgCount, builder.argTypes.getFinalArray());
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy