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

com.oracle.truffle.polyglot.PolyglotLanguageContext Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 1.0.0-rc7
Show newest version
/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.truffle.polyglot;

import static com.oracle.truffle.polyglot.VMAccessor.LANGUAGE;

import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.logging.Level;

import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.impl.AbstractPolyglotImpl.APIAccess;
import org.graalvm.polyglot.proxy.Proxy;

import com.oracle.truffle.api.CallTarget;
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.Scope;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.Source;

final class PolyglotLanguageContext implements PolyglotImpl.VMObject {

    private static final TruffleLogger LOG = TruffleLogger.getLogger("engine", PolyglotLanguageContext.class);

    /*
     * Lazily created when a language context is created.
     */
    final class Lazy {

        final PolyglotSourceCache sourceCache;
        final Map, PolyglotValue> valueCache;
        final PolyglotValue defaultValueCache;
        final Set activePolyglotThreads;
        final Object polyglotGuestBindings;
        final Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
        final PolyglotLanguageInstance languageInstance;

        Lazy(PolyglotLanguageInstance languageInstance) {
            this.valueCache = new ConcurrentHashMap<>();
            this.languageInstance = languageInstance;
            this.sourceCache = languageInstance.getSourceCache();
            this.activePolyglotThreads = new HashSet<>();
            this.defaultValueCache = new PolyglotValue.Default(PolyglotLanguageContext.this);
            this.polyglotGuestBindings = new PolyglotBindings(PolyglotLanguageContext.this, context.polyglotBindings);
            this.uncaughtExceptionHandler = new PolyglotUncaughtExceptionHandler();
        }
    }

    final PolyglotContextImpl context;
    final PolyglotLanguage language;
    final boolean eventsEnabled;

    private volatile boolean creating;
    private volatile boolean initialized;
    volatile boolean finalized;
    @CompilationFinal private volatile Value hostBindings;
    @CompilationFinal private volatile Lazy lazy;

    @CompilationFinal volatile Env env; // effectively final

    PolyglotLanguageContext(PolyglotContextImpl context, PolyglotLanguage language) {
        this.context = context;
        this.language = language;
        this.eventsEnabled = !language.isHost();
    }

    Thread.UncaughtExceptionHandler getPolyglotExceptionHandler() {
        assert env != null;
        return lazy.uncaughtExceptionHandler;
    }

    Map, PolyglotValue> getValueCache() {
        assert env != null;
        return lazy.valueCache;
    }

    PolyglotValue getDefaultValueCache() {
        assert env != null;
        return lazy.defaultValueCache;
    }

    PolyglotLanguageInstance getLanguageInstance() {
        assert env != null;
        return lazy.languageInstance;
    }

    private void checkThreadAccess(Env localEnv) {
        assert Thread.holdsLock(context);
        boolean singleThreaded = context.isSingleThreaded();
        Thread firstFailingThread = null;
        for (PolyglotThreadInfo threadInfo : context.getSeenThreads().values()) {
            if (!LANGUAGE.isThreadAccessAllowed(localEnv, threadInfo.thread, singleThreaded)) {
                firstFailingThread = threadInfo.thread;
                break;
            }
        }
        if (firstFailingThread != null) {
            throw PolyglotContextImpl.throwDeniedThreadAccess(firstFailingThread, singleThreaded, Arrays.asList(language));
        }
    }

    Object getContextImpl() {
        if (env != null) {
            return LANGUAGE.getContext(env);
        } else {
            return null;
        }
    }

    Value getHostBindings() {
        assert initialized;
        if (this.hostBindings == null) {
            synchronized (this) {
                if (this.hostBindings == null) {
                    Object prev = context.enterIfNeeded();
                    try {
                        Iterable scopes = LANGUAGE.findTopScopes(env);
                        this.hostBindings = this.asValue(new PolyglotLanguageBindings(scopes));
                    } catch (Throwable t) {
                        throw PolyglotImpl.wrapGuestException(this, t);
                    } finally {
                        context.leaveIfNeeded(prev);
                    }
                }
            }
        }
        return this.hostBindings;
    }

    Object getPolyglotGuestBindings() {
        assert isInitialized();
        return this.lazy.polyglotGuestBindings;
    }

    boolean isInitialized() {
        return initialized;
    }

    CallTarget parseCached(PolyglotLanguage accessingLanguage, Source source, String[] argumentNames) throws AssertionError {
        ensureInitialized(accessingLanguage);
        PolyglotSourceCache cache = lazy.sourceCache;
        assert cache != null;
        return cache.parseCached(this, source, argumentNames);
    }

    Env requireEnv() {
        Env localEnv = this.env;
        if (localEnv == null) {
            CompilerDirectives.transferToInterpreter();
            throw new AssertionError(
                            "No language context is active on this thread.");
        }
        return localEnv;
    }

    boolean finalizeContext() {
        if (!finalized) {
            finalized = true;
            LANGUAGE.finalizeContext(env);
            if (eventsEnabled) {
                VMAccessor.INSTRUMENT.notifyLanguageContextFinalized(context.engine, context.truffleContext, language.info);
            }
            return true;
        }
        return false;
    }

    boolean dispose() {
        assert Thread.holdsLock(context);
        Env localEnv = this.env;
        if (localEnv != null) {
            if (!lazy.activePolyglotThreads.isEmpty()) {
                throw new AssertionError("The language did not complete all polyglot threads but should have: " + lazy.activePolyglotThreads);
            }
            for (PolyglotThreadInfo threadInfo : context.getSeenThreads().values()) {
                assert threadInfo.thread != null;
                if (threadInfo.isPolyglotThread(context)) {
                    // polyglot threads need to be cleaned up by the language
                    continue;
                }
                LANGUAGE.disposeThread(localEnv, threadInfo.thread);
            }
            LANGUAGE.dispose(localEnv);
            language.freeInstance(lazy.languageInstance);
            return true;
        }
        return false;
    }

    void notifyDisposed() {
        if (eventsEnabled) {
            VMAccessor.INSTRUMENT.notifyLanguageContextDisposed(context.engine, context.truffleContext, language.info);
        }
    }

    Object enterThread(PolyglotThread thread) {
        assert isInitialized();
        assert Thread.currentThread() == thread;
        synchronized (context) {
            lazy.activePolyglotThreads.add(thread);
            return context.enter();
        }
    }

    void leaveThread(Object prev, PolyglotThread thread) {
        assert isInitialized();
        assert Thread.currentThread() == thread;
        synchronized (context) {
            Map seenThreads = context.getSeenThreads();
            PolyglotThreadInfo info = seenThreads.get(thread);
            if (info == null) {
                // already disposed
                return;
            }
            for (PolyglotLanguageContext languageContext : context.contexts) {
                if (languageContext.isInitialized()) {
                    LANGUAGE.disposeThread(languageContext.env, thread);
                }
            }
            lazy.activePolyglotThreads.remove(thread);
            context.leave(prev);
            seenThreads.remove(thread);
        }
        VMAccessor.INSTRUMENT.notifyThreadFinished(context.engine, context.truffleContext, thread);
    }

    private void ensureCreated(PolyglotLanguage accessingLanguage) {
        if (creating) {
            throw new PolyglotIllegalStateException(String.format("Cyclic access to language context for language %s. " +
                            "The context is currently being created.", language.getId()));
        }
        if (lazy == null) {
            checkAccess(accessingLanguage);

            Map creatorConfig = context.creator == language ? context.creatorArguments : Collections.emptyMap();
            PolyglotContextConfig envConfig = context.config;
            PolyglotLanguageInstance lang = language.allocateInstance(envConfig.getOptionValues(language));
            try {
                synchronized (context) {
                    if (lazy == null) {
                        Env localEnv = LANGUAGE.createEnv(PolyglotLanguageContext.this, lang.spi, envConfig.out,
                                        envConfig.err,
                                        envConfig.in,
                                        creatorConfig,
                                        envConfig.getOptionValues(language),
                                        envConfig.getApplicationArguments(language),
                                        envConfig.fileSystem);
                        Lazy localLazy = new Lazy(lang);
                        PolyglotValue.createDefaultValueCaches(PolyglotLanguageContext.this, localLazy.valueCache);
                        checkThreadAccess(localEnv);

                        // no more errors after this line
                        creating = true;
                        PolyglotLanguageContext.this.env = localEnv;
                        PolyglotLanguageContext.this.lazy = localLazy;
                        assert VMAccessor.LANGUAGE.getLanguage(env) != null;

                        try {
                            LANGUAGE.createEnvContext(localEnv);
                            lang.language.profile.notifyContextCreate(localEnv);
                            if (eventsEnabled) {
                                VMAccessor.INSTRUMENT.notifyLanguageContextCreated(context.engine, context.truffleContext, language.info);
                            }
                            lang = null; // commit language use
                        } catch (Throwable e) {
                            PolyglotLanguageContext.this.env = null;
                            PolyglotLanguageContext.this.lazy = null;
                            throw e;
                        } finally {
                            creating = false;
                        }
                    }
                }
            } finally {
                // free not commited language instance
                if (lang != null) {
                    language.freeInstance(lang);
                }
            }
        }
    }

    boolean ensureInitialized(PolyglotLanguage accessingLanguage) {
        ensureCreated(accessingLanguage);
        boolean wasInitialized = false;
        if (!initialized) {
            synchronized (context) {
                if (!initialized) {
                    initialized = true; // Allow language use during initialization
                    try {
                        if (!context.inContextPreInitialization) {
                            LANGUAGE.initializeThread(env, Thread.currentThread());
                        }

                        LANGUAGE.postInitEnv(env);

                        if (!context.isSingleThreaded()) {
                            LANGUAGE.initializeMultiThreading(env);
                        }

                        for (PolyglotThreadInfo threadInfo : context.getSeenThreads().values()) {
                            if (threadInfo.thread == Thread.currentThread()) {
                                continue;
                            }
                            LANGUAGE.initializeThread(env, threadInfo.thread);
                        }

                        wasInitialized = true;
                    } catch (Throwable e) {
                        // language not successfully initialized, reset to avoid inconsistent
                        // language contexts
                        initialized = false;
                        throw e;
                    }
                }
            }
        }
        if (wasInitialized && eventsEnabled) {
            VMAccessor.INSTRUMENT.notifyLanguageContextInitialized(context.engine, context.truffleContext, language.info);
        }
        return wasInitialized;
    }

    void checkAccess(PolyglotLanguage accessingLanguage) {
        context.engine.checkState();
        if (context.closed) {
            throw new PolyglotIllegalStateException("The Context is already closed.");
        }
        boolean accessPermitted = language.isHost() || language.cache.isInternal() || context.config.allowedPublicLanguages.contains(language.info.getId()) ||
                        (accessingLanguage != null && accessingLanguage.dependsOn(language));

        if (!accessPermitted) {
            throw new PolyglotIllegalArgumentException(String.format("Access to language '%s' is not permitted. ", language.getId()));
        }
        RuntimeException initError = language.initError;
        if (initError != null) {
            throw new PolyglotIllegalStateException(String.format("Initialization error: %s", initError.getMessage()), initError);
        }
    }

    @Override
    public PolyglotEngineImpl getEngine() {
        return context.getEngine();
    }

    void preInitialize() {
        ensureInitialized(null);
        LOG.log(Level.FINE, "Pre-initialized context for language: {0}", language.getId());
    }

    boolean patch(PolyglotContextConfig newConfig) {
        if (isInitialized()) {
            try {
                final Env newEnv = LANGUAGE.patchEnvContext(env, newConfig.out, newConfig.err, newConfig.in,
                                Collections.emptyMap(), newConfig.getOptionValues(language), newConfig.getApplicationArguments(language),
                                newConfig.fileSystem);
                if (newEnv != null) {
                    env = newEnv;
                    LOG.log(Level.FINE, "Successfully patched context of language: {0}", this.language.getId());
                    return true;
                }
                LOG.log(Level.FINE, "Failed to patch context of language: {0}", this.language.getId());
                return false;
            } catch (Throwable t) {
                if (t instanceof ThreadDeath) {
                    throw t;
                }
                LOG.log(Level.FINE, "Exception during patching context of language: {0}", this.language.getId());
                throw PolyglotImpl.wrapGuestException(this, t);
            }
        } else {
            return true;
        }
    }

    static final class ToGuestValuesNode {

        @CompilationFinal(dimensions = 1) private volatile ToGuestValueNode[] toGuestValue;
        @CompilationFinal private volatile boolean needsCopy = false;
        @CompilationFinal private volatile boolean generic = false;

        private ToGuestValuesNode() {
        }

        public Object[] apply(PolyglotLanguageContext languageContext, Object[] args) {
            ToGuestValueNode[] nodes = this.toGuestValue;
            if (nodes == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                nodes = new ToGuestValueNode[args.length];
                for (int i = 0; i < nodes.length; i++) {
                    nodes[i] = createToGuestValue();
                }
                toGuestValue = nodes;
            }
            if (args.length == nodes.length) {
                // fast path
                if (nodes.length == 0) {
                    return args;
                } else {
                    Object[] newArgs = fastToGuestValuesUnroll(nodes, languageContext, args);
                    return newArgs;
                }
            } else {
                if (!generic || nodes.length != 1) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    nodes = Arrays.copyOf(nodes, 1);
                    if (nodes[0] == null) {
                        nodes[0] = createToGuestValue();
                    }
                    this.toGuestValue = nodes;
                    this.generic = true;
                }
                if (args.length == 0) {
                    return args;
                }
                return fastToGuestValues(nodes[0], languageContext, args);
            }
        }

        /*
         * Specialization for constant number of arguments. Uses a profile for each argument.
         */
        @ExplodeLoop
        private Object[] fastToGuestValuesUnroll(ToGuestValueNode[] nodes, PolyglotLanguageContext languageContext, Object[] args) {
            Object[] newArgs = needsCopy ? new Object[nodes.length] : args;
            for (int i = 0; i < nodes.length; i++) {
                Object arg = args[i];
                Object newArg = nodes[i].apply(languageContext, arg);
                if (needsCopy) {
                    newArgs[i] = newArg;
                } else if (arg != newArg) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    newArgs = new Object[nodes.length];
                    System.arraycopy(args, 0, newArgs, 0, args.length);
                    newArgs[i] = newArg;
                    needsCopy = true;
                }
            }
            return newArgs;
        }

        /*
         * Specialization that supports multiple argument lengths but uses a single profile for all
         * arguments.
         */
        private Object[] fastToGuestValues(ToGuestValueNode node, PolyglotLanguageContext languageContext, Object[] args) {
            assert toGuestValue[0] != null;
            Object[] newArgs = needsCopy ? new Object[args.length] : args;
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                Object newArg = node.apply(languageContext, arg);
                if (needsCopy) {
                    newArgs[i] = newArg;
                } else if (arg != newArg) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    newArgs = new Object[args.length];
                    System.arraycopy(args, 0, newArgs, 0, args.length);
                    newArgs[i] = newArg;
                    needsCopy = true;
                }
            }
            return newArgs;
        }

        public static ToGuestValuesNode create() {
            return new ToGuestValuesNode();
        }

    }

    static final class Generic {
        private Generic() {
            throw new AssertionError("no instances");
        }
    }

    static final class ToGuestValueNode {

        @CompilationFinal private volatile Class cachedClass;

        private ToGuestValueNode() {

        }

        public Object apply(PolyglotLanguageContext languageContext, Object receiver) {
            Class cachedClassLocal = this.cachedClass;
            if (cachedClassLocal == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                if (receiver == null) {
                    // directly go to slow path for null
                    cachedClass = cachedClassLocal = Generic.class;
                } else {
                    cachedClass = cachedClassLocal = receiver.getClass();
                }
            }
            if (cachedClassLocal != Generic.class) {
                assert cachedClassLocal != null;
                if (receiver != null && cachedClassLocal == receiver.getClass()) {
                    return languageContext.toGuestValue(cachedClassLocal.cast(receiver));
                } else {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    cachedClass = cachedClassLocal = Generic.class; // switch to generic
                }
            }
            return slowPath(languageContext, receiver);
        }

        @TruffleBoundary
        private static Object slowPath(PolyglotLanguageContext languageContext, Object receiver) {
            return languageContext.toGuestValue(receiver);
        }

        public static ToGuestValueNode create() {
            return new ToGuestValueNode();
        }
    }

    static ToGuestValueNode createToGuestValue() {
        return new ToGuestValueNode();
    }

    @TruffleBoundary
    Value asValue(Object guestValue) {
        assert lazy != null;
        assert guestValue != null;
        assert !(guestValue instanceof Value);
        assert !(guestValue instanceof Proxy);
        Object receiver = guestValue;
        PolyglotValue cache = lazy.valueCache.get(receiver.getClass());
        if (cache == null) {
            cache = lookupValueCache(guestValue);
        }
        return getAPIAccess().newValue(receiver, cache);
    }

    synchronized PolyglotValue lookupValueCache(Object guestValue) {
        assert toGuestValue(guestValue) == guestValue : "Not a valid guest value: " + guestValue + ". Only interop values are allowed to be exported.";
        PolyglotValue cache = lazy.valueCache.computeIfAbsent(guestValue.getClass(), new Function, PolyglotValue>() {
            public PolyglotValue apply(Class t) {
                return PolyglotValue.createInteropValueCache(PolyglotLanguageContext.this, (TruffleObject) guestValue, guestValue.getClass());
            }
        });
        return cache;
    }

    final class ToHostValueNode {

        final APIAccess apiAccess = context.engine.impl.getAPIAccess();
        @CompilationFinal volatile Class cachedClass;
        @CompilationFinal volatile PolyglotValue cachedValue;

        private ToHostValueNode() {
        }

        Value execute(Object value) {
            Object receiver = value;
            Class cachedClassLocal = cachedClass;
            PolyglotValue cache;
            if (cachedClassLocal != Generic.class) {
                if (cachedClassLocal == null) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    cachedClass = receiver.getClass();
                    cache = lazy.valueCache.get(receiver.getClass());
                    if (cache == null) {
                        cache = lookupValueCache(receiver);
                    }
                    cachedValue = cache;
                    return apiAccess.newValue(receiver, cache);
                } else if (value.getClass() == cachedClassLocal) {
                    receiver = CompilerDirectives.inInterpreter() ? receiver : CompilerDirectives.castExact(receiver, cachedClassLocal);
                    cache = cachedValue;
                    if (cache == null) {
                        CompilerDirectives.transferToInterpreterAndInvalidate();
                        // invalid state retry next time for now do generic
                    } else {
                        return apiAccess.newValue(receiver, cache);
                    }
                } else {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    cachedClass = Generic.class; // switch to generic
                    cachedValue = null;
                    // fall through to generic
                }
            }
            return asValue(value);
        }
    }

    ToHostValueNode createToHostValue() {
        return new ToHostValueNode();
    }

    Object toGuestValue(Class receiver) {
        return HostObject.forClass(receiver, this);
    }

    Object toGuestValue(Object receiver) {
        if (receiver instanceof Value) {
            Value receiverValue = (Value) receiver;
            PolyglotValue valueImpl = (PolyglotValue) getAPIAccess().getImpl(receiverValue);
            if (valueImpl.languageContext.context != context) {
                CompilerDirectives.transferToInterpreter();
                throw PolyglotImpl.engineError(new IllegalArgumentException(String.format("Values cannot be passed from one context to another. " +
                                "The current value originates from context 0x%s and the argument originates from context 0x%s.",
                                Integer.toHexString(context.hashCode()), Integer.toHexString(valueImpl.languageContext.context.hashCode()))));
            }
            return getAPIAccess().getReceiver(receiverValue);
        } else if (PolyglotImpl.isGuestPrimitive(receiver)) {
            return receiver;
        } else if (receiver instanceof Proxy) {
            return PolyglotProxy.toProxyGuestObject(this, (Proxy) receiver);
        } else if (receiver instanceof TruffleObject) {
            return receiver;
        } else if (receiver instanceof Class) {
            return HostObject.forClass((Class) receiver, this);
        } else if (receiver == null) {
            return HostObject.NULL;
        } else if (receiver.getClass().isArray()) {
            return HostObject.forObject(receiver, this);
        } else if (receiver instanceof PolyglotList) {
            return ((PolyglotList) receiver).guestObject;
        } else if (receiver instanceof PolyglotMap) {
            return ((PolyglotMap) receiver).guestObject;
        } else if (receiver instanceof PolyglotFunction) {
            return ((PolyglotFunction) receiver).guestObject;
        } else if (TruffleOptions.AOT) {
            return HostObject.forObject(receiver, this);
        } else {
            return HostInteropReflect.asTruffleViaReflection(receiver, this);
        }

    }

    @TruffleBoundary
    Value[] toHostValues(Object[] values, int startIndex) {
        Value[] args = new Value[values.length - startIndex];
        for (int i = startIndex; i < values.length; i++) {
            args[i - startIndex] = asValue(values[i]);
        }
        return args;
    }

    @TruffleBoundary
    Value[] toHostValues(Object[] values) {
        Value[] args = new Value[values.length];
        for (int i = 0; i < args.length; i++) {
            args[i] = asValue(values[i]);
        }
        return args;
    }

    @Override
    public String toString() {
        return "PolyglotLanguageContext [language=" + language + ", initialized=" + (env != null) + "]";
    }

    private class PolyglotUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            Env currentEnv = env;
            if (currentEnv != null) {
                try {
                    e.printStackTrace(new PrintStream(currentEnv.err()));
                } catch (Throwable exc) {
                    // Still show the original error if printing on Env.err() fails for some reason
                    e.printStackTrace();
                }
            } else {
                e.printStackTrace();
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy