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

com.oracle.truffle.api.vm.PolyglotEngineImpl 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, 2018, 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.api.vm;

import static com.oracle.truffle.api.vm.VMAccessor.INSTRUMENT;
import static com.oracle.truffle.api.vm.VMAccessor.LANGUAGE;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.logging.Handler;
import java.util.logging.Level;

import org.graalvm.options.OptionDescriptors;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Instrument;
import org.graalvm.polyglot.Language;
import org.graalvm.polyglot.io.FileSystem;

import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.InstrumentInfo;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleException;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.impl.DispatchOutputStream;
import com.oracle.truffle.api.instrumentation.ContextsListener;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
import com.oracle.truffle.api.instrumentation.Instrumenter;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.ThreadsListener;
import com.oracle.truffle.api.nodes.LanguageInfo;
import com.oracle.truffle.api.nodes.Node;

@SuppressWarnings("deprecation")
class PolyglotEngineImpl extends org.graalvm.polyglot.impl.AbstractPolyglotImpl.AbstractEngineImpl implements com.oracle.truffle.api.vm.PolyglotImpl.VMObject {

    /**
     * Context index for the host language.
     */
    static final int HOST_LANGUAGE_INDEX = 0;
    static final String HOST_LANGUAGE_ID = "host";
    private static final Set RESERVED_IDS = new HashSet<>(
                    Arrays.asList(HOST_LANGUAGE_ID, "graal", "truffle", "engine", "language", "instrument", "graalvm", "context", "polyglot", "compiler", "vm",
                                    PolyglotEngineOptions.OPTION_GROUP_LOG));

    private static final Map ENGINES = Collections.synchronizedMap(new WeakHashMap<>());
    private static volatile boolean shutdownHookInitialized = false;
    private static final boolean DEBUG_MISSING_CLOSE = Boolean.getBoolean("polyglotimpl.DebugMissingClose");

    Engine creatorApi; // effectively final
    Engine currentApi;
    final Object instrumentationHandler;
    final PolyglotImpl impl;
    DispatchOutputStream out;       // effectively final
    DispatchOutputStream err;       // effectively final
    InputStream in;                 // effectively final
    long timeout;                   // effectively final
    TimeUnit timeoutUnit;           // effectively final
    boolean sandbox;                // effectively final

    final Map idToLanguage;
    final Map idToPublicLanguage;
    final Map idToInternalLanguageInfo;

    final Map idToInstrument;
    final Map idToPublicInstrument;
    final Map idToInternalInstrumentInfo;

    final OptionDescriptors engineOptions;
    final OptionDescriptors compilerOptions;
    final OptionDescriptors allEngineOptions;

    final OptionValuesImpl engineOptionValues;
    final OptionValuesImpl compilerOptionValues;
    ClassLoader contextClassLoader;     // effectively final
    boolean boundEngine;    // effectively final
    Handler logHandler;     // effectively final
    final Exception createdLocation = DEBUG_MISSING_CLOSE ? new Exception() : null;
    private final Set contexts = new LinkedHashSet<>();
    private PolyglotContextImpl preInitializedContext;

    PolyglotLanguage hostLanguage;
    final Assumption singleContext = Truffle.getRuntime().createAssumption();

    volatile OptionDescriptors allOptions;
    volatile boolean closed;

    private volatile CancelHandler cancelHandler;
    // Data used by the runtime to enable "global" state per Engine
    volatile Object runtimeData;
    final Map javaInteropCodeCache = new ConcurrentHashMap<>();
    Map logLevels;    // effectively final

    PolyglotEngineImpl(PolyglotImpl impl, DispatchOutputStream out, DispatchOutputStream err, InputStream in, Map options, long timeout, TimeUnit timeoutUnit,
                    boolean sandbox, boolean useSystemProperties, ClassLoader contextClassLoader, boolean boundEngine, Handler logHandler) {
        this(impl, out, err, in, options, timeout, timeoutUnit, sandbox, useSystemProperties, contextClassLoader, boundEngine, false, logHandler);
    }

    private PolyglotEngineImpl(PolyglotImpl impl, DispatchOutputStream out, DispatchOutputStream err, InputStream in, Map options, long timeout, TimeUnit timeoutUnit,
                    boolean sandbox, boolean useSystemProperties, ClassLoader contextClassLoader, boolean boundEngine, boolean preInitialization, Handler logHandler) {
        super(impl);
        this.instrumentationHandler = INSTRUMENT.createInstrumentationHandler(this, out, err, in);
        this.impl = impl;
        this.out = out;
        this.err = err;
        this.in = in;
        this.timeout = timeout;
        this.timeoutUnit = timeoutUnit;
        this.contextClassLoader = contextClassLoader;
        this.sandbox = sandbox;
        this.boundEngine = boundEngine;
        this.logHandler = logHandler;

        Map languageInfos = new LinkedHashMap<>();
        this.idToLanguage = Collections.unmodifiableMap(initializeLanguages(languageInfos));
        this.idToInternalLanguageInfo = Collections.unmodifiableMap(languageInfos);

        Map instrumentInfos = new LinkedHashMap<>();
        this.idToInstrument = Collections.unmodifiableMap(initializeInstruments(instrumentInfos));
        this.idToInternalInstrumentInfo = Collections.unmodifiableMap(instrumentInfos);

        for (String id : idToLanguage.keySet()) {
            if (idToInstrument.containsKey(id)) {
                throw failDuplicateId(id,
                                idToLanguage.get(id).cache.getClassName(),
                                idToInstrument.get(id).cache.getClassName());
            }
        }

        this.engineOptions = new PolyglotEngineOptionsOptionDescriptors();
        this.compilerOptions = VMAccessor.SPI.getCompilerOptions();
        this.allEngineOptions = OptionDescriptors.createUnion(engineOptions, compilerOptions);
        this.engineOptionValues = new OptionValuesImpl(this, this.engineOptions);
        this.compilerOptionValues = new OptionValuesImpl(this, this.compilerOptions);

        Map publicLanguages = new LinkedHashMap<>();
        for (String key : this.idToLanguage.keySet()) {
            PolyglotLanguage languageImpl = idToLanguage.get(key);
            if (!languageImpl.cache.isInternal()) {
                publicLanguages.put(key, languageImpl.api);
            }
        }
        idToPublicLanguage = Collections.unmodifiableMap(publicLanguages);

        Map publicInstruments = new LinkedHashMap<>();
        for (String key : this.idToInstrument.keySet()) {
            PolyglotInstrument instrumentImpl = idToInstrument.get(key);
            if (!instrumentImpl.cache.isInternal()) {
                publicInstruments.put(key, instrumentImpl.api);
            }
        }
        idToPublicInstrument = Collections.unmodifiableMap(publicInstruments);

        Map originalEngineOptions = new HashMap<>();
        Map originalCompilerOptions = new HashMap<>();
        logLevels = new HashMap<>();
        Map> languagesOptions = new HashMap<>();
        Map> instrumentsOptions = new HashMap<>();

        parseOptions(options, useSystemProperties, originalEngineOptions, originalCompilerOptions, languagesOptions, instrumentsOptions, logLevels, preInitialization);

        this.engineOptionValues.putAll(originalEngineOptions);
        this.compilerOptionValues.putAll(originalCompilerOptions);

        for (PolyglotLanguage language : languagesOptions.keySet()) {
            language.getOptionValues().putAll(languagesOptions.get(language));
        }

        if (!boundEngine) {
            initializeMultiContext(null);
        }

        ENGINES.put(this, null);
        if (!preInitialization) {
            createInstruments(instrumentsOptions);
            registerShutDownHook();
        }
    }

    boolean patch(DispatchOutputStream newOut, DispatchOutputStream newErr, InputStream newIn, Map newOptions, long newTimeout, TimeUnit newTimeoutUnit,
                    boolean newSandbox, boolean newUseSystemProperties, ClassLoader newContextClassLoader, boolean newBoundEngine, Handler newLogHandler) {
        CompilerAsserts.neverPartOfCompilation();
        if (this.boundEngine != newBoundEngine) {
            return false;
        }
        this.out = newOut;
        this.err = newErr;
        this.in = newIn;
        this.timeout = newTimeout;
        this.timeoutUnit = newTimeoutUnit;
        this.contextClassLoader = newContextClassLoader;
        this.sandbox = newSandbox;
        this.boundEngine = newBoundEngine;
        this.logHandler = newLogHandler;
        INSTRUMENT.patchInstrumentationHandler(instrumentationHandler, newOut, newErr, newIn);

        Map originalEngineOptions = new HashMap<>();
        Map originalCompilerOptions = new HashMap<>();
        Map> languagesOptions = new HashMap<>();
        Map> instrumentsOptions = new HashMap<>();

        assert this.logLevels.isEmpty();
        parseOptions(newOptions, newUseSystemProperties, originalEngineOptions, originalCompilerOptions, languagesOptions, instrumentsOptions, logLevels, false);

        this.engineOptionValues.putAll(originalEngineOptions);
        this.compilerOptionValues.putAll(originalCompilerOptions);

        for (PolyglotLanguage language : languagesOptions.keySet()) {
            language.getOptionValues().putAll(languagesOptions.get(language));
        }

        createInstruments(instrumentsOptions);
        registerShutDownHook();
        return true;
    }

    private void createInstruments(final Map> instrumentsOptions) {
        for (PolyglotInstrument instrument : instrumentsOptions.keySet()) {
            instrument.getOptionValues().putAll(instrumentsOptions.get(instrument));
        }

        try {
            for (PolyglotInstrument instrument : instrumentsOptions.keySet()) {
                // we got options for this instrument -> create it.
                instrument.ensureCreated();
            }
        } catch (Throwable e) {
            throw PolyglotImpl.wrapGuestException(this, e);
        }
    }

    private static void registerShutDownHook() {
        if (!shutdownHookInitialized) {
            synchronized (ENGINES) {
                if (!shutdownHookInitialized) {
                    shutdownHookInitialized = true;
                    Runtime.getRuntime().addShutdownHook(new Thread(new PolyglotShutDownHook()));
                }
            }
        }
    }

    synchronized void initializeMultiContext(PolyglotContextImpl existingContext) {
        if (singleContext.isValid()) {
            singleContext.invalidate("More than one context introduced.");
            if (existingContext != null) {
                for (PolyglotLanguageContext context : existingContext.contexts) {
                    if (context.isInitialized()) {
                        context.getLanguageInstance().initializeMultiContext();
                    }
                }
            }
        }
    }

    private void parseOptions(Map options, boolean useSystemProperties,
                    Map originalEngineOptions, Map originalCompilerOptions,
                    Map> languagesOptions, Map> instrumentsOptions,
                    Map logOptions, boolean preInitialization) {
        // When changing this logic, make sure it is in synch with #isEngineGroup()
        if (useSystemProperties) {
            Properties properties = System.getProperties();
            synchronized (properties) {
                for (Object systemKey : properties.keySet()) {
                    String key = (String) systemKey;
                    if (key.startsWith(OptionValuesImpl.SYSTEM_PROPERTY_PREFIX)) {
                        String engineKey = key.substring(OptionValuesImpl.SYSTEM_PROPERTY_PREFIX.length(), key.length());
                        String optionGroup = parseOptionGroup(engineKey);
                        if (!options.containsKey(engineKey) && (!preInitialization || idToPublicLanguage.containsKey(optionGroup) ||
                                        engineKey.equals(PolyglotImpl.OPTION_GROUP_ENGINE + '.' + PolyglotEngineOptions.PREINITIALIZE_CONTEXT_NAME) ||
                                        PolyglotEngineOptions.OPTION_GROUP_LOG.equals(optionGroup))) {
                            options.put(engineKey, System.getProperty(key));
                        }
                    }
                }
            }
        }
        for (String key : options.keySet()) {
            String group = parseOptionGroup(key);
            String value = options.get(key);
            PolyglotLanguage language = idToLanguage.get(group);
            if (language != null && !language.cache.isInternal()) {
                Map languageOptions = languagesOptions.get(language);
                if (languageOptions == null) {
                    languageOptions = new HashMap<>();
                    languagesOptions.put(language, languageOptions);
                }
                languageOptions.put(key, value);
                continue;
            }
            PolyglotInstrument instrument = idToInstrument.get(group);
            if (instrument != null && !instrument.cache.isInternal()) {
                Map instrumentOptions = instrumentsOptions.get(instrument);
                if (instrumentOptions == null) {
                    instrumentOptions = new HashMap<>();
                    instrumentsOptions.put(instrument, instrumentOptions);
                }
                instrumentOptions.put(key, value);
                continue;
            }

            if (group.equals(PolyglotImpl.OPTION_GROUP_ENGINE)) {
                originalEngineOptions.put(key, value);
                continue;
            }

            if (group.equals(PolyglotImpl.OPTION_GROUP_COMPILER)) {
                originalCompilerOptions.put(key, value);
                continue;
            }
            if (group.equals(PolyglotEngineOptions.OPTION_GROUP_LOG)) {
                logOptions.put(parseLoggerName(key), Level.parse(value));
                continue;
            }
            throw OptionValuesImpl.failNotFound(getAllOptions(), key);
        }
    }

    /**
     * Find if there is an "engine option" (covers engine, compiler and instruments options) present
     * among the given options.
     */
    // The implementation must be in synch with #parseOptions()
    boolean isEngineGroup(String group) {
        return idToPublicInstrument.containsKey(group) ||
                        group.equals(PolyglotImpl.OPTION_GROUP_ENGINE) ||
                        group.equals(PolyglotImpl.OPTION_GROUP_COMPILER);
    }

    static String parseOptionGroup(String key) {
        int groupIndex = key.indexOf('.');
        String group;
        if (groupIndex != -1) {
            group = key.substring(0, groupIndex);
        } else {
            group = key;
        }
        return group;
    }

    static String parseLoggerName(String optionKey) {
        final String prefix = "log.";
        final String suffix = ".level";
        if (!optionKey.startsWith(prefix) || !optionKey.endsWith(suffix)) {
            throw new IllegalArgumentException(optionKey);
        }
        final int start = prefix.length();
        final int end = optionKey.length() - suffix.length();
        return start < end ? optionKey.substring(start, end) : "";
    }

    @Override
    public PolyglotEngineImpl getEngine() {
        return this;
    }

    PolyglotLanguage findLanguage(String languageId, String mimeType, boolean failIfNotFound) {
        assert languageId != null || mimeType != null : Objects.toString(languageId) + ", " + Objects.toString(mimeType);
        if (languageId != null) {
            PolyglotLanguage language = idToLanguage.get(languageId);
            if (language != null) {
                return language;
            }
        }
        if (mimeType != null) {
            // we need to interpret mime types for compatibility.
            PolyglotLanguage language = idToLanguage.get(mimeType);
            if (language != null) {
                return language;
            }
            for (PolyglotLanguage searchLanguage : idToLanguage.values()) {
                if (searchLanguage.cache.getMimeTypes().contains(mimeType)) {
                    return searchLanguage;
                }
            }
        }
        if (failIfNotFound) {
            if (languageId != null) {
                Set ids = new LinkedHashSet<>();
                for (PolyglotLanguage language : idToLanguage.values()) {
                    ids.add(language.cache.getId());
                }
                throw new IllegalStateException("No language for id " + languageId + " found. Supported languages are: " + ids);
            } else {
                Set mimeTypes = new LinkedHashSet<>();
                for (PolyglotLanguage language : idToLanguage.values()) {
                    mimeTypes.addAll(language.cache.getMimeTypes());
                }
                throw new IllegalStateException("No language for MIME type " + mimeType + " found. Supported languages are: " + mimeTypes);
            }
        } else {
            return null;
        }
    }

    private Map initializeInstruments(Map infos) {
        Map instruments = new LinkedHashMap<>();
        List cachedInstruments = InstrumentCache.load(VMAccessor.allLoaders());
        for (InstrumentCache instrumentCache : cachedInstruments) {
            PolyglotInstrument instrumentImpl = new PolyglotInstrument(this, instrumentCache);
            instrumentImpl.info = LANGUAGE.createInstrument(instrumentImpl, instrumentCache.getId(), instrumentCache.getName(), instrumentCache.getVersion());
            Instrument instrument = impl.getAPIAccess().newInstrument(instrumentImpl);
            instrumentImpl.api = instrument;

            String id = instrumentImpl.cache.getId();
            verifyId(id, instrumentCache.getClassName());
            if (instruments.containsKey(id)) {
                throw failDuplicateId(id, instrumentImpl.cache.getClassName(), instruments.get(id).cache.getClassName());
            }
            instruments.put(id, instrumentImpl);
            infos.put(id, instrumentImpl.info);
        }
        return instruments;
    }

    private Map initializeLanguages(Map infos) {
        Map polyglotLanguages = new LinkedHashMap<>();
        Map cachedLanguages = new HashMap<>();
        List sortedLanguages = new ArrayList<>();
        for (LanguageCache lang : LanguageCache.languages().values()) {
            String id = lang.getId();
            if (!cachedLanguages.containsKey(id)) {
                sortedLanguages.add(lang);
                cachedLanguages.put(id, lang);
            }
        }
        Collections.sort(sortedLanguages);

        LinkedHashSet serializedLanguages = new LinkedHashSet<>();
        Set languageReferences = new HashSet<>();
        Map initErrors = new HashMap<>();

        for (LanguageCache language : sortedLanguages) {
            languageReferences.addAll(language.getDependentLanguages());
        }

        // visit / initialize internal languages first to model the implicit
        // dependency of every public language to every internal language
        for (LanguageCache language : sortedLanguages) {
            if (language.isInternal() && !languageReferences.contains(language.getId())) {
                visitLanguage(initErrors, cachedLanguages, serializedLanguages, language);
            }
        }

        for (LanguageCache language : sortedLanguages) {
            if (!language.isInternal() && !languageReferences.contains(language.getId())) {
                visitLanguage(initErrors, cachedLanguages, serializedLanguages, language);
            }
        }

        this.hostLanguage = createLanguage(createHostLanguageCache(), HOST_LANGUAGE_INDEX, null);

        int index = 1;
        for (LanguageCache cache : serializedLanguages) {
            PolyglotLanguage languageImpl = createLanguage(cache, index, initErrors.get(cache.getId()));

            String id = languageImpl.cache.getId();
            verifyId(id, cache.getClassName());
            if (polyglotLanguages.containsKey(id)) {
                throw failDuplicateId(id, languageImpl.cache.getClassName(), polyglotLanguages.get(id).cache.getClassName());
            }
            polyglotLanguages.put(id, languageImpl);
            infos.put(id, languageImpl.info);
            index++;
        }

        return polyglotLanguages;
    }

    private void visitLanguage(Map initErrors, Map cachedLanguages, LinkedHashSet serializedLanguages,
                    LanguageCache language) {
        visitLanguageImpl(new HashSet<>(), initErrors, cachedLanguages, serializedLanguages, language);
    }

    private void visitLanguageImpl(Set visitedIds, Map initErrors, Map cachedLanguages, LinkedHashSet serializedLanguages,
                    LanguageCache language) {
        Set dependencies = language.getDependentLanguages();
        for (String dependency : dependencies) {
            LanguageCache dependentLanguage = cachedLanguages.get(dependency);
            if (dependentLanguage == null) {
                // dependent languages are optional
                continue;
            }
            if (visitedIds.contains(dependency)) {
                initErrors.put(language.getId(), new PolyglotIllegalStateException("Illegal cyclic language dependency found:" + language.getId() + " -> " + dependency));
                continue;
            }
            visitedIds.add(dependency);
            visitLanguageImpl(visitedIds, initErrors, cachedLanguages, serializedLanguages, dependentLanguage);
            visitedIds.remove(dependency);
        }
        serializedLanguages.add(language);
    }

    private PolyglotLanguage createLanguage(LanguageCache cache, int index, RuntimeException initError) {
        PolyglotLanguage languageImpl = new PolyglotLanguage(this, cache, index, index == HOST_LANGUAGE_INDEX, initError);
        Language language = impl.getAPIAccess().newLanguage(languageImpl);
        languageImpl.api = language;
        return languageImpl;
    }

    private static LanguageCache createHostLanguageCache() {
        return new LanguageCache(HOST_LANGUAGE_ID, Collections.emptySet(),
                        "Host", "Host", System.getProperty("java.version"), false, false, new HostLanguage());
    }

    private static void verifyId(String id, String className) {
        if (RESERVED_IDS.contains(id)) {
            throw new IllegalStateException(String.format("The language or instrument with class '%s' uses a reserved id '%s'. " +
                            "Resolve this by using a not reserved id for the language or instrument. " +
                            "The following ids are reserved %s for internal use.",
                            className, id, RESERVED_IDS));
        } else if (id.contains(".")) {
            throw new IllegalStateException(String.format("The language '%s' must not contain a period in its id '%s'. " +
                            "Remove all periods from the id to resolve this issue. ",
                            className, id));
        }
    }

    private static RuntimeException failDuplicateId(String duplicateId, String className1, String className2) {
        return new IllegalStateException(
                        String.format("Duplicate id '%s' specified by language or instrument with class '%s' and '%s'. " +
                                        "Resolve this by specifying a unique id for each language or instrument.",
                                        duplicateId, className1, className2));
    }

    final void checkState() {
        if (closed) {
            throw new IllegalStateException("Engine is already closed.");
        }
    }

    OptionDescriptors getEngineOptions() {
        throw new UnsupportedOperationException();
    }

    void addContext(PolyglotContextImpl context) {
        assert Thread.holdsLock(this);
        contexts.add(context);
    }

    synchronized void removeContext(PolyglotContextImpl context) {
        contexts.remove(context);
    }

    void reportAllLanguageContexts(ContextsListener listener) {
        PolyglotContextImpl[] allContexts;
        synchronized (this) {
            if (contexts.isEmpty()) {
                return;
            }
            allContexts = contexts.toArray(new PolyglotContextImpl[contexts.size()]);
        }
        for (PolyglotContextImpl context : allContexts) {
            listener.onContextCreated(context.truffleContext);
            for (PolyglotLanguageContext lc : context.contexts) {
                LanguageInfo language = lc.language.info;
                if (lc.eventsEnabled && lc.env != null) {
                    listener.onLanguageContextCreated(context.truffleContext, language);
                    if (lc.isInitialized()) {
                        listener.onLanguageContextInitialized(context.truffleContext, language);
                        if (lc.finalized) {
                            listener.onLanguageContextFinalized(context.truffleContext, language);
                        }
                    }
                }
            }
        }
    }

    void reportAllContextThreads(ThreadsListener listener) {
        PolyglotContextImpl[] allContexts;
        synchronized (this) {
            if (contexts.isEmpty()) {
                return;
            }
            allContexts = contexts.toArray(new PolyglotContextImpl[contexts.size()]);
        }
        for (PolyglotContextImpl context : allContexts) {
            Thread[] threads;
            synchronized (context) {
                threads = context.getSeenThreads().keySet().toArray(new Thread[0]);
            }
            for (Thread thread : threads) {
                listener.onThreadInitialized(context.truffleContext, thread);
            }
        }
    }

    @Override
    public Language requirePublicLanguage(String id) {
        checkState();
        Language language = idToPublicLanguage.get(id);
        if (language == null) {
            String misspelledGuess = matchSpellingError(idToPublicLanguage.keySet(), id);
            String didYouMean = "";
            if (misspelledGuess != null) {
                didYouMean = String.format("Did you mean '%s'? ", misspelledGuess);
            }
            throw new PolyglotIllegalStateException(String.format("A language with id '%s' is not installed. %sInstalled languages are: %s.", id, didYouMean, getLanguages().keySet()));
        }
        return language;
    }

    private static String matchSpellingError(Set allIds, String enteredId) {
        String lowerCaseEnteredId = enteredId.toLowerCase();
        for (String id : allIds) {
            if (id.toLowerCase().equals(lowerCaseEnteredId)) {
                return id;
            }
        }
        return null;
    }

    @Override
    public Instrument requirePublicInstrument(String id) {
        checkState();
        Instrument instrument = idToPublicInstrument.get(id);
        if (instrument == null) {
            String misspelledGuess = matchSpellingError(idToPublicInstrument.keySet(), id);
            String didYouMean = "";
            if (misspelledGuess != null) {
                didYouMean = String.format("Did you mean '%s'? ", misspelledGuess);
            }
            throw new PolyglotIllegalStateException(String.format("An instrument with id '%s' is not installed. %sInstalled instruments are: %s.", id, didYouMean, getInstruments().keySet()));
        }
        return instrument;
    }

    @Override
    public void close(Engine sourceEngine, boolean cancelIfExecuting) {
        if (sourceEngine != creatorApi) {
            throw new IllegalStateException("Engine instances that were indirectly received using Context.get() cannot be closed.");
        }
        ensureClosed(cancelIfExecuting, false);
    }

    synchronized void ensureClosed(boolean cancelIfExecuting, boolean ignoreCloseFailure) {
        if (!closed) {
            PolyglotContextImpl[] localContexts = contexts.toArray(new PolyglotContextImpl[0]);
            /*
             * Check ahead of time for open contexts to fail early and avoid closing only some
             * contexts.
             */
            if (!cancelIfExecuting && !ignoreCloseFailure) {
                for (PolyglotContextImpl context : localContexts) {
                    synchronized (context) {
                        if (context.hasActiveOtherThread(false)) {
                            throw new IllegalStateException(String.format("One of the context instances is currently executing. " +
                                            "Set cancelIfExecuting to true to stop the execution on this thread."));
                        }
                    }
                }
            }
            for (PolyglotContextImpl context : localContexts) {
                try {
                    boolean closeCompleted = context.closeImpl(cancelIfExecuting, cancelIfExecuting);
                    if (!closeCompleted && !cancelIfExecuting && !ignoreCloseFailure) {
                        throw new IllegalStateException(String.format("One of the context instances is currently executing. " +
                                        "Set cancelIfExecuting to true to stop the execution on this thread."));
                    }
                } catch (Throwable e) {
                    if (!ignoreCloseFailure) {
                        throw e;
                    }
                }
            }
            if (cancelIfExecuting) {
                getCancelHandler().waitForClosing(localContexts);
            }

            if (!boundEngine) {
                for (PolyglotContextImpl context : localContexts) {
                    PolyglotContextImpl.disposeStaticContext(context);
                }
            }

            contexts.clear();
            for (Instrument instrument : idToPublicInstrument.values()) {
                PolyglotInstrument instrumentImpl = (PolyglotInstrument) getAPIAccess().getImpl(instrument);
                try {
                    instrumentImpl.notifyClosing();
                } catch (Throwable e) {
                    if (!ignoreCloseFailure) {
                        throw e;
                    }
                }
            }
            for (Instrument instrument : idToPublicInstrument.values()) {
                PolyglotInstrument instrumentImpl = (PolyglotInstrument) getAPIAccess().getImpl(instrument);
                try {
                    instrumentImpl.ensureClosed();
                } catch (Throwable e) {
                    if (!ignoreCloseFailure) {
                        throw e;
                    }
                }
            }

            ENGINES.remove(this);
            closed = true;
        }
    }

    @Override
    public Map getInstruments() {
        checkState();
        return idToPublicInstrument;
    }

    @Override
    public Map getLanguages() {
        checkState();
        return idToPublicLanguage;
    }

    @Override
    public OptionDescriptors getOptions() {
        checkState();
        return allEngineOptions;
    }

    @Override
    public String getVersion() {
        String version = System.getProperty("org.graalvm.version");
        if (version == null) {
            version = System.getProperty("graalvm.version");
        }
        if (version == null) {
            return "Development Build";
        } else {
            return version;
        }
    }

    OptionDescriptors getAllOptions() {
        checkState();
        if (allOptions == null) {
            synchronized (this) {
                if (allOptions == null) {
                    List allDescriptors = new ArrayList<>();
                    allDescriptors.add(engineOptions);
                    allDescriptors.add(compilerOptions);
                    for (PolyglotLanguage language : idToLanguage.values()) {
                        allDescriptors.add(language.getOptions());
                    }
                    for (PolyglotInstrument instrument : idToInstrument.values()) {
                        allDescriptors.add(instrument.getOptions());
                    }
                    allOptions = OptionDescriptors.createUnion(allDescriptors.toArray(new OptionDescriptors[0]));
                }
            }
        }
        return allOptions;
    }

    Collection getAllThreads(PolyglotContextImpl context) {
        synchronized (context) {
            return new ArrayList<>(context.getSeenThreads().keySet());
        }
    }

    static PolyglotEngineImpl preInitialize(PolyglotImpl impl, DispatchOutputStream out, DispatchOutputStream err, InputStream in, ClassLoader contextClassLoader, Handler logHandler) {
        final PolyglotEngineImpl engine = new PolyglotEngineImpl(impl, out, err, in, new HashMap<>(), 0, null, false, true, contextClassLoader, true, true, logHandler);
        synchronized (engine) {
            try {
                engine.preInitializedContext = PolyglotContextImpl.preInitialize(engine);
                engine.addContext(engine.preInitializedContext);
            } finally {
                // Reset language homes from native-image compilatio time, will be recomputed in
                // image execution time
                LanguageCache.resetNativeImageCacheLanguageHomes();
                // Clear logger settings
                engine.logLevels.clear();
                engine.logHandler = null;
            }
        }
        return engine;
    }

    /**
     * Clears the pre-initialized engines. The TruffleFeature needs to clean emitted engines during
     * Feature.cleanup.
     */
    static void resetPreInitializedEngine() {
        ENGINES.clear();
    }

    private static final class PolyglotShutDownHook implements Runnable {

        public void run() {
            PolyglotEngineImpl[] engines = ENGINES.keySet().toArray(new PolyglotEngineImpl[0]);
            for (PolyglotEngineImpl engine : engines) {
                if (DEBUG_MISSING_CLOSE) {
                    PrintStream out = System.out;
                    out.println("Missing close on vm shutdown: ");
                    out.print(" InitializedLanguages:");
                    for (PolyglotContextImpl context : engine.contexts) {
                        for (PolyglotLanguageContext langContext : context.contexts) {
                            if (langContext.env != null) {
                                out.print(langContext.language.getId());
                                out.print(", ");
                            }
                        }
                    }
                    out.println();
                    engine.createdLocation.printStackTrace();
                }
                if (engine != null) {
                    engine.ensureClosed(false, true);
                }
            }
            ENGINES.keySet().removeAll(Arrays.asList(engines));
        }
    }

    CancelHandler getCancelHandler() {
        if (cancelHandler == null) {
            synchronized (this) {
                if (cancelHandler == null) {
                    cancelHandler = new CancelHandler();
                }
            }
        }
        return cancelHandler;

    }

    final class CancelHandler {
        private final Instrumenter instrumenter;
        private volatile EventBinding cancellationBinding;
        private int cancellationUsers;

        CancelHandler() {
            this.instrumenter = (Instrumenter) INSTRUMENT.getEngineInstrumenter(instrumentationHandler);
        }

        void waitForClosing(PolyglotContextImpl... localContexts) {
            boolean cancelling = false;
            for (PolyglotContextImpl context : localContexts) {
                if (context.cancelling) {
                    cancelling = true;
                    break;
                }
            }

            if (cancelling) {
                enableCancel();

                try {
                    for (PolyglotContextImpl context : localContexts) {
                        context.sendInterrupt();
                    }
                    for (PolyglotContextImpl context : localContexts) {
                        context.waitForClose();
                    }
                } finally {
                    disableCancel();
                }
            }
        }

        private synchronized void enableCancel() {
            if (cancellationBinding == null) {
                cancellationBinding = instrumenter.attachExecutionEventListener(SourceSectionFilter.ANY, new ExecutionEventListener() {
                    public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
                        cancelExecution(context);
                    }

                    public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
                        cancelExecution(context);
                    }

                    public void onEnter(EventContext context, VirtualFrame frame) {
                        cancelExecution(context);
                    }

                    @TruffleBoundary
                    private void cancelExecution(EventContext eventContext) {
                        PolyglotContextImpl context = PolyglotContextImpl.requireContext();
                        if (context.cancelling) {
                            throw new CancelExecution(eventContext);
                        }
                    }
                });
            }
            cancellationUsers++;
        }

        private synchronized void disableCancel() {
            int usersLeft = --cancellationUsers;
            if (usersLeft <= 0) {
                EventBinding b = cancellationBinding;
                if (b != null) {
                    b.dispose();
                }
                cancellationBinding = null;
            }
        }
    }

    @SuppressWarnings("serial")
    private static final class CancelExecution extends ThreadDeath implements TruffleException {

        private final Node node;

        CancelExecution(EventContext context) {
            this.node = context.getInstrumentedNode();
        }

        public Node getLocation() {
            return node;
        }

        @Override
        public String getMessage() {
            return "Execution got cancelled.";
        }

        public boolean isCancelled() {
            return true;
        }

    }

    @Override
    public String getImplementationName() {
        return Truffle.getRuntime().getName();
    }

    @Override
    @SuppressWarnings({"all"})
    public synchronized Context createContext(OutputStream configOut, OutputStream configErr, InputStream configIn, boolean allowHostAccess,
                    boolean allowNativeAccess, boolean allowCreateThread, boolean allowHostIO, boolean allowHostClassLoading,
                    Predicate classFilter, Map options, Map arguments, String[] onlyLanguages, FileSystem fileSystem, Handler logHandler) {
        checkState();
        if (boundEngine && preInitializedContext == null && !contexts.isEmpty()) {
            throw new IllegalArgumentException("Automatically created engines cannot be used to create more than one context. " +
                            "Use Engine.newBuilder().build() to construct a new engine and pass it using Context.newBuilder().engine(engine).build().");
        }

        Set allowedLanguages;
        if (onlyLanguages.length == 0) {
            allowedLanguages = getLanguages().keySet();
        } else {
            allowedLanguages = new HashSet<>(Arrays.asList(onlyLanguages));
        }
        final FileSystem fs;
        if (allowHostIO) {
            fs = fileSystem != null ? fileSystem : FileSystems.getDefaultFileSystem();
        } else {
            fs = FileSystems.newNoIOFileSystem();
        }
        final OutputStream useOut;
        if (configOut == null || configOut == INSTRUMENT.getOut(this.out)) {
            useOut = this.out;
        } else {
            useOut = INSTRUMENT.createDelegatingOutput(configOut, this.out);
        }
        final OutputStream useErr;
        if (configErr == null || configErr == INSTRUMENT.getOut(this.err)) {
            useErr = this.err;
        } else {
            useErr = INSTRUMENT.createDelegatingOutput(configErr, this.err);
        }

        Handler useHandler = logHandler != null ? logHandler : this.logHandler;
        useHandler = useHandler != null ? useHandler : PolyglotLogHandler.createStreamHandler(useOut, false, true);

        final InputStream useIn = configIn == null ? this.in : configIn;

        PolyglotContextConfig config = new PolyglotContextConfig(this, useOut, useErr, useIn,
                        allowHostAccess, allowNativeAccess, allowCreateThread, allowHostClassLoading,
                        classFilter, arguments, allowedLanguages, options, fs, useHandler);

        PolyglotContextImpl context = loadPreinitializedContext(config);
        if (context == null) {
            context = new PolyglotContextImpl(this, config);
            addContext(context);
        } else {
            // don't add contexts for preinitialized contexts as they have been added already
            assert Thread.holdsLock(this);
            assert contexts.contains(context);
        }

        Context api = impl.getAPIAccess().newContext(context);
        context.creatorApi = api;
        context.currentApi = impl.getAPIAccess().newContext(context);
        return api;
    }

    private PolyglotContextImpl loadPreinitializedContext(PolyglotContextConfig config) {
        PolyglotContextImpl context = preInitializedContext;
        preInitializedContext = null;
        if (context != null) {
            FileSystems.PreInitializeContextFileSystem preInitFs = (FileSystems.PreInitializeContextFileSystem) context.config.fileSystem;
            preInitFs.patchDelegate(config.fileSystem);
            FileSystem oldFileSystem = config.fileSystem;
            config.fileSystem = preInitFs;

            boolean patchResult = false;
            try {
                patchResult = context.patch(config);
            } finally {
                if (!patchResult) {
                    context.closeImpl(false, false);
                    context = null;
                    PolyglotContextImpl.disposeStaticContext(context);
                    config.fileSystem = oldFileSystem;
                }
            }
        }
        return context;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy