/* * Copyright (c) 2014, 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.api.vm; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.InstrumentInfo; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.impl.DispatchOutputStream; import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.nodes.LanguageInfo; import com.oracle.truffle.api.vm.LanguageCache.LoadedLanguage; import com.oracle.truffle.api.vm.PolyglotEngine.Access; import com.oracle.truffle.api.vm.PolyglotEngine.Language; /** * A runtime environment for one or more {@link PolyglotEngine} instances. By default a constructed * {@link PolyglotEngine} provides data and code isolation. However, if the same runtime is used to * construct multiple engines then code can be shared between them. Languages can also decide to * share immutable parts of their data between engines of a runtime. As a consequence memory * consumption for an engine is expected to be lower if they are constructed with the same runtime. * Methods of {@link PolyglotRuntime} can be safely used from multiple-threads. * * Usage: * <p> * {@codesnippet com.oracle.truffle.api.vm.PolyglotEngineSnippets#createEngines} * <p> * The above example prepares three engines that share the {@link PolyglotRuntime runtime}. * * @since 0.25 * @see PolyglotEngine * @see TruffleLanguage More information for language implementors. */ public final class PolyglotRuntime { private final List<LanguageShared> languages; final Object instrumentationHandler; @SuppressWarnings("deprecation") final Map<String, PolyglotEngine.Instrument> instruments; final Object[] debugger = {null}; final PolyglotEngineProfile engineProfile; private final AtomicInteger instanceCount = new AtomicInteger(0); final DispatchOutputStream out; final DispatchOutputStream err; final InputStream in; volatile boolean disposed; final boolean automaticDispose; final Map<String, InstrumentInfo> instrumentInfos; final Map<String, LanguageInfo> languageInfos; private PolyglotRuntime() { this(null, null, null, false); } PolyglotRuntime(DispatchOutputStream out, DispatchOutputStream err, InputStream in, boolean automaticDispose) { this.instrumentationHandler = PolyglotEngine.SPIAccessor.instrumentAccess().createInstrumentationHandler(this, out, err, in); /* * TODO the engine profile needs to be shared between all engines that potentially share * code. Currently this is stored statically to be compatible with the legacy deprecated API * TruffleLanguage#createFindContextNode() and the deprecated RootNode constructor. As soon * as this deprecated API is removed and EngineImpl#findVM() can be removed as well, we can * allocate this context store profile for each shared vm. */ this.engineProfile = PolyglotEngine.GLOBAL_PROFILE; List<LanguageShared> languageList = new ArrayList<>(); /* We want to create a language instance but per LanguageCache and not per mime type. */ List<LanguageCache> convertedLanguages = new ArrayList<>(new HashSet<>(LanguageCache.languages().values())); Collections.sort(convertedLanguages); Map<String, LanguageInfo> langInfos = new LinkedHashMap<>(); Map<String, InstrumentInfo> instInfos = new LinkedHashMap<>(); int languageIndex = 0; for (LanguageCache languageCache : convertedLanguages) { LanguageShared lang = new LanguageShared(this, languageCache, languageIndex++); languageList.add(lang); for (String mimeType : lang.language.getMimeTypes()) { langInfos.put(mimeType, lang.language); } } this.automaticDispose = automaticDispose; this.languages = languageList; this.instruments = createInstruments(InstrumentCache.load()); for (Instrument instrument : instruments.values()) { instInfos.put(instrument.getId(), PolyglotEngine.Access.LANGS.createInstrument(instrument, instrument.getId(), instrument.getName(), instrument.getVersion())); } this.languageInfos = Collections.unmodifiableMap(langInfos); this.instrumentInfos = Collections.unmodifiableMap(instInfos); this.out = out; this.err = err; this.in = in; } PolyglotEngine currentVM() { return engineProfile.get(); } List<LanguageShared> getLanguages() { return languages; } void notifyEngineDisposed() { instanceCount.decrementAndGet(); if (automaticDispose) { dispose(); } } void notifyEngineCreated() { instanceCount.incrementAndGet(); } @SuppressWarnings("deprecation") private Map<String, PolyglotEngine.Instrument> createInstruments(List<InstrumentCache> instrumentCaches) { Map<String, PolyglotEngine.Instrument> instr = new LinkedHashMap<>(); for (InstrumentCache cache : instrumentCaches) { PolyglotEngine.Instrument instrument = PolyglotEngine.UNUSABLE_ENGINE.new Instrument(this, cache); instr.put(cache.getId(), instrument); } return Collections.unmodifiableMap(instr); } /** * Gets the map: {@linkplain Instrument#getId() Instrument ID} --> {@link Instrument} loaded in * this {@linkplain PolyglotRuntime runtime}, whether the instrument is * {@linkplain Instrument#isEnabled() enabled} or not. * * @return map of currently loaded instruments * @since 0.25 */ public Map<String, ? extends Instrument> getInstruments() { return instruments; } /** * Disposes the runtime and with it all created instruments. Throws * {@link IllegalStateException} if not all engines created using this runtime are not yet * {@link PolyglotEngine#dispose() disposed}. * {@link PolyglotEngine.Builder#runtime(PolyglotRuntime) Default/private} runtimes of an engine * are disposed automatically with the engine. * * @since 0.25 */ public synchronized void dispose() { if (instanceCount.get() > 0) { throw new IllegalStateException("Cannot dispose runtime if not all engine instances are disposed."); } if (!disposed) { disposed = true; // only dispose instruments if all engine group is disposed for (PolyglotRuntime.Instrument instrument : getInstruments().values()) { try { instrument.setEnabledImpl(false, false); } catch (Exception | Error ex) { PolyglotEngine.LOG.log(Level.SEVERE, "Error disposing " + instrument, ex); } } } } /** * Starts creation of a new runtime instance. Call any methods of the {@link Builder} and finish * the creation by calling {@link Builder#build()}. * * @return new instance of a builder * @since 0.25 */ public static Builder newBuilder() { return new PolyglotRuntime().new Builder(); } static final class LanguageShared { final LanguageCache cache; final PolyglotRuntime runtime; final PolyglotEngineProfile engineProfile; final int languageId; final LanguageInfo language; volatile boolean initialized; LanguageShared(PolyglotRuntime engineShared, LanguageCache cache, int languageId) { this.runtime = engineShared; this.engineProfile = engineShared.engineProfile; assert engineProfile != null; this.cache = cache; this.languageId = languageId; this.language = Access.NODES.createLanguage(this, cache.getName(), cache.getVersion(), cache.getMimeTypes()); } Language currentLanguage() { return runtime.currentVM().findLanguage(this); } Object getCurrentContext() { // is on fast-path final PolyglotEngine engine = engineProfile.get(); Object context = PolyglotEngine.UNSET_CONTEXT; if (engine != null) { context = engine.languageArray[languageId].context; } if (context == PolyglotEngine.UNSET_CONTEXT) { CompilerDirectives.transferToInterpreter(); throw new IllegalStateException( "The language context is not yet initialized or already disposed. "); } return context; } PolyglotRuntime getRuntime() { return runtime; } LanguageInfo getLanguageEnsureInitialized() { if (!initialized) { synchronized (this) { if (!initialized) { initialized = true; LoadedLanguage loadedLanguage = cache.loadLanguage(); Access.LANGS.initializeLanguage(language, loadedLanguage.getLanguage(), loadedLanguage.isSingleton()); } } } return language; } } /** * Builder for creating new instance of a {@link PolyglotRuntime}. * * @since 0.25 */ public final class Builder { private OutputStream out; private OutputStream err; private InputStream in; private Builder() { } /** * Configures default output for languages running in the {@link PolyglotEngine engine} * being built, defaults to {@link System#out}. * * @param os the stream to use as output * @return this builder * @since 0.25 */ public PolyglotRuntime.Builder setOut(OutputStream os) { out = os; return this; } /** * Configures error output for languages running in the {@link PolyglotRuntime runtime} * being built, defaults to {@link System#err}. * * @param os the stream to use as output * @return this builder * @since 0.25 */ public PolyglotRuntime.Builder setErr(OutputStream os) { err = os; return this; } /** * Configures default input for languages running in the {@link PolyglotRuntime runtime} * being built, defaults to {@link System#in}. * * @param is the stream to use as input * @return this builder * @since 0.25 */ public PolyglotRuntime.Builder setIn(InputStream is) { in = is; return this; } /** * Creates new instance of a runtime. Uses data stored in this builder to configure it. Once * the instance is obtained, pass it to * {@link PolyglotEngine.Builder#runtime(com.oracle.truffle.api.vm.PolyglotRuntime)} method. * * @return new instances of the {@link PolyglotRuntime} * @since 0.25 */ public PolyglotRuntime build() { DispatchOutputStream realOut = PolyglotEngine.SPIAccessor.instrumentAccess().createDispatchOutput(out == null ? System.out : out); DispatchOutputStream realErr = PolyglotEngine.SPIAccessor.instrumentAccess().createDispatchOutput(err == null ? System.err : err); InputStream realIn = in == null ? System.in : in; return new PolyglotRuntime(realOut, realErr, realIn, false); } PolyglotRuntime build(boolean autoDispose) { DispatchOutputStream realOut = PolyglotEngine.SPIAccessor.instrumentAccess().createDispatchOutput(out == null ? System.out : out); DispatchOutputStream realErr = PolyglotEngine.SPIAccessor.instrumentAccess().createDispatchOutput(err == null ? System.err : err); InputStream realIn = in == null ? System.in : in; return new PolyglotRuntime(realOut, realErr, realIn, autoDispose); } } /** * A handle for an <em>instrument</em> installed in the {@linkplain PolyglotRuntime runtime}, * usable from other threads, that can observe and inject behavior into language execution. The * handle provides access to the instrument's metadata and allows the instrument to be * dynamically {@linkplain Instrument#setEnabled(boolean) enabled/disabled} in the runtime. * <p> * All methods here, as well as instrumentation services in general, can be used safely from * threads other than the engine's single execution thread. * <p> * Refer to {@link TruffleInstrument} for information about implementing and installing * instruments. * * @see PolyglotRuntime#getInstruments() * @since 0.25 */ public class Instrument { private final InstrumentCache cache; private final Object instrumentLock = new Object(); private volatile boolean enabled; Instrument(InstrumentCache cache) { this.cache = cache; } /** * Gets the id clients can use to acquire this instrument. * * @return this instrument's unique id * @since 0.9 */ public String getId() { return cache.getId(); } /** * Gets a human readable name of this instrument. * * @return this instrument's user-friendly name * @since 0.9 */ public String getName() { return cache.getName(); } /** * Gets the version of this instrument. * * @return this instrument's version * @since 0.9 */ public String getVersion() { return cache.getVersion(); } InstrumentCache getCache() { return cache; } PolyglotRuntime getRuntime() { return PolyglotRuntime.this; } /** * Returns whether this instrument is currently enabled in the engine. * * @return this instrument's status in the engine * @since 0.9 */ public boolean isEnabled() { return enabled; } /** * Returns an additional service provided by this instrument, specified by type. * <p> * Here is an example for locating a hypothetical <code>DebuggerController</code>: * * {@codesnippet DebuggerExampleTest} * * @param <T> the type of the service * @param type class of the service that is being requested * @return instance of requested type, <code>null</code> if no such service is available * @since 0.9 */ public <T> T lookup(Class<T> type) { if (PolyglotRuntime.this.disposed) { return null; } if (!isEnabled() && cache.supportsService(type)) { setEnabled(true); } return PolyglotEngine.Access.INSTRUMENT.getInstrumentationHandlerService(PolyglotRuntime.this.instrumentationHandler, this, type); } /** * Enables/disables this instrument in the engine. * * @param enabled <code>true</code> to enable <code>false</code> to disable * @since 0.9 */ public void setEnabled(final boolean enabled) { setEnabledImpl(enabled, true); } void setEnabledImpl(final boolean enabled, boolean cleanup) { synchronized (instrumentLock) { if (this.enabled != enabled) { if (enabled) { if (PolyglotRuntime.this.disposed) { return; } PolyglotEngine.Access.INSTRUMENT.addInstrument(PolyglotRuntime.this.instrumentationHandler, this, getCache().getInstrumentationClass(), cache.services()); } else { PolyglotEngine.Access.INSTRUMENT.disposeInstrument(PolyglotRuntime.this.instrumentationHandler, this, cleanup); } this.enabled = enabled; } } } /** * @since 0.9 */ @Override public String toString() { return "Instrument [id=" + getId() + ", name=" + getName() + ", version=" + getVersion() + ", enabled=" + enabled + "]"; } } }