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

com.oracle.truffle.api.vm.PolyglotEngine 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) 2014, 2016, 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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
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.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.TruffleLanguage.Registration;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.impl.Accessor;
import com.oracle.truffle.api.impl.FindContextNode;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.java.JavaInterop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;

/**
 * Gate way into the world of {@link TruffleLanguage Truffle languages}. {@link #buildNew()
 * Instantiate} your own portal into the isolated, multi-language system with all the registered
 * languages ready for your use. A {@link PolyglotEngine} runs inside of a JVM. There can
 * however be multiple instances (some would say tenants) of {@link PolyglotEngine} running next to
 * each other in a single JVM with a complete mutual isolation. There is 1:N mapping
 * between JVM and {@link PolyglotEngine}.
 * 

* It would not be correct to think of a {@link PolyglotEngine} as a runtime for a single * {@link TruffleLanguage Truffle language} (Ruby, Python, R, C, JavaScript, etc.) either. * {@link PolyglotEngine} can host as many of Truffle languages as {@link Registration registered on * a class path} of your JVM application. {@link PolyglotEngine} orchestrates these * languages, manages exchange of objects and calls among them. While it may happen that there is * just one activated language inside of a {@link PolyglotEngine}, the greatest strength of * {@link PolyglotEngine} is in inter-operability between all Truffle languages. There is 1:N * mapping between {@link PolyglotEngine} and {@link TruffleLanguage Truffle language * implementations}. * *

Usage

* *

* Use {@link #buildNew()} to create a new isolated portal ready for execution of various languages. * All the languages in a single portal see each others exported global symbols and can cooperate. * Use {@link #buildNew()} multiple times to create different, isolated environments that are * completely separated from each other. *

* Once instantiated use {@link #eval(com.oracle.truffle.api.source.Source)} with a reference to a * file or URL or directly pass code snippet into the virtual machine via * {@link #eval(com.oracle.truffle.api.source.Source)}. Support for individual languages is * initialized on demand - e.g. once a file of certain MIME type is about to be processed, its * appropriate engine (if found), is initialized. Once an engine gets initialized, it remains so, * until the virtual machine is garbage collected. *

* For using a {@link TruffleLanguage language} with a custom setup or configuration, the necessary * parameters should be communicated to the {@link PolyglotEngine} - either via * {@link PolyglotEngine.Builder#config} as exposed by the language implementation or by evaluating * appropriate "prelude" scripts via {@link PolyglotEngine.Language#eval}. Another possibility is to * pre-register various {@link PolyglotEngine.Builder#globalSymbol global objects} and make them * available to the {@link PolyglotEngine}. Configuration parameters are obtained for instance by * parsing command line arguments. *

* The engine is single-threaded and tries to enforce that. It records the thread it has been * {@link Builder#build() created} by and checks that all subsequent calls are coming from the same * thread. There is 1:1 mapping between {@link PolyglotEngine} and a thread that can tell it what to * do. * * @since 0.9 */ @SuppressWarnings({"rawtypes", "deprecation"}) public class PolyglotEngine { static final boolean JAVA_INTEROP_ENABLED = !TruffleOptions.AOT; static final Logger LOG = Logger.getLogger(PolyglotEngine.class.getName()); private static final SPIAccessor SPI = new SPIAccessor(); private final Thread initThread; private final Executor executor; private final Map langs; private final InputStream in; private final OutputStream err; private final OutputStream out; private final EventConsumer[] handlers; private final Map globals; private final Object instrumenter; // old instrumentation private final Object instrumentationHandler; // new instrumentation private final Map instruments; private final List config; private final Object[] debugger = {null}; private final ContextStore context; private boolean disposed; static final boolean JDK8OrEarlier = System.getProperty("java.specification.version").compareTo("1.9") < 0; static { try { // We need to ensure that the Instrumentation class is loaded so accessors are created // properly. Class.forName(TruffleInstrument.class.getName(), true, TruffleInstrument.class.getClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e); } } /** * Private & temporary only constructor. */ PolyglotEngine() { assertNoTruffle(); this.initThread = null; this.in = null; this.err = null; this.out = null; this.langs = null; this.handlers = null; this.globals = null; this.executor = null; this.instrumenter = null; this.instrumentationHandler = null; this.instruments = null; this.config = null; this.context = null; } /** * Real constructor used from the builder. */ PolyglotEngine(Executor executor, Map globals, OutputStream out, OutputStream err, InputStream in, EventConsumer[] handlers, List config) { assertNoTruffle(); this.executor = executor; this.out = out; this.err = err; this.in = in; this.handlers = handlers; this.initThread = Thread.currentThread(); this.globals = new HashMap<>(globals); this.instrumenter = null; // SPI.createInstrumenter(this); this.config = config; // this.debugger = SPI.createDebugger(this, this.instrumenter); // new instrumentation this.instrumentationHandler = Access.INSTRUMENT.createInstrumentationHandler(this, out, err, in); Map map = new HashMap<>(); /* We want to create a language instance but per LanguageCache and not per mime type. */ Set uniqueCaches = new HashSet<>(LanguageCache.languages().values()); for (LanguageCache languageCache : uniqueCaches) { Language newLanguage = new Language(languageCache); for (String mimeType : newLanguage.getMimeTypes()) { map.put(mimeType, newLanguage); } } this.langs = map; this.instruments = createAndAutostartDescriptors(InstrumentCache.load(JDK8OrEarlier ? getClass().getClassLoader() : null)); this.context = ExecutionImpl.createStore(this); } private Map createAndAutostartDescriptors(List instrumentCaches) { Map instr = new LinkedHashMap<>(); for (InstrumentCache cache : instrumentCaches) { Instrument instrument = new Instrument(cache); instr.put(cache.getId(), instrument); } return Collections.unmodifiableMap(instr); } /** * Creation of new Truffle virtual machine. Use the {@link Builder} methods to configure your * virtual machine and then create one using {@link Builder#build()}: * *

     * {@link PolyglotEngine} vm = {@link PolyglotEngine}.{@link PolyglotEngine#buildNew() buildNew()}
     *     .{@link Builder#setOut(java.io.OutputStream) setOut}({@link OutputStream yourOutput})
     *     .{@link Builder#setErr(java.io.OutputStream) setErr}({@link OutputStream yourOutput})
     *     .{@link Builder#setIn(java.io.InputStream) setIn}({@link InputStream yourInput})
     *     .{@link Builder#build() build()};
     * 
* * It searches for {@link Registration languages registered} in the system class loader and * makes them available for later evaluation via * {@link #eval(com.oracle.truffle.api.source.Source)} method. * * @return new builder to create isolated polyglot engine with pre-registered languages * @since 0.10 */ public static PolyglotEngine.Builder newBuilder() { // making Builder non-static inner class is a // nasty trick to avoid the Builder class to appear // in Javadoc next to PolyglotEngine class PolyglotEngine vm = new PolyglotEngine(); return vm.new Builder(); } /** * @return new builder * @deprecated use {@link #newBuilder()} * @since 0.9 */ @Deprecated public static PolyglotEngine.Builder buildNew() { return newBuilder(); } /** * Builder for a new {@link PolyglotEngine}. Call various configuration methods in a chain and * at the end create new {@link PolyglotEngine virtual machine}: * *
     * {@link PolyglotEngine} vm = {@link PolyglotEngine}.{@link PolyglotEngine#buildNew() buildNew()}
     *     .{@link Builder#setOut(java.io.OutputStream) setOut}({@link OutputStream yourOutput})
     *     .{@link Builder#setErr(java.io.OutputStream) setErr}({@link OutputStream yourOutput})
     *     .{@link Builder#setIn(java.io.InputStream) setIn}({@link InputStream yourInput})
     *     .{@link Builder#build() build()};
     * 
* * @since 0.9 */ public class Builder { private OutputStream out; private OutputStream err; private InputStream in; private final List> handlers = new ArrayList<>(); private final Map globals = new HashMap<>(); private Executor executor; private List arguments; Builder() { } /** * Changes the default output for languages running in to be created * {@link PolyglotEngine virtual machine}. The default is to use {@link System#out}. * * @param os the stream to use as output * @return instance of this builder * @since 0.9 */ public Builder setOut(OutputStream os) { out = os; return this; } /** * Changes the error output for languages running in to be created * {@link PolyglotEngine virtual machine}. The default is to use {@link System#err}. * * @param os the stream to use as output * @return instance of this builder * @since 0.9 */ public Builder setErr(OutputStream os) { err = os; return this; } /** * Changes the default input for languages running in to be created * {@link PolyglotEngine virtual machine}. The default is to use {@link System#in}. * * @param is the stream to use as input * @return instance of this builder * @since 0.9 */ public Builder setIn(InputStream is) { in = is; return this; } /** * Registers another instance of {@link EventConsumer} into the to be created * {@link PolyglotEngine}. * * @param handler the handler to register * @return instance of this builder * @since 0.9 */ public Builder onEvent(EventConsumer handler) { Objects.requireNonNull(handler); handlers.add(handler); return this; } /** * Provide configuration data to initialize the {@link PolyglotEngine} for a specific * language. These arguments {@link com.oracle.truffle.api.TruffleLanguage.Env#getConfig() * can be used by the language} to initialize and configure their * {@link com.oracle.truffle.api.TruffleLanguage#createContext(com.oracle.truffle.api.TruffleLanguage.Env) * initial execution state} correctly. * * {@link com.oracle.truffle.api.vm.PolyglotEngineSnippets#initializeWithParameters} * * If the same key is specified multiple times for the same language, the previous values * are replaced and just the last one remains. * * @param mimeType identification of the language for which the arguments are - if the * language declares multiple MIME types, any of them can be used * * @param key to identify a language-specific configuration element * @param value to parameterize initial state of a language * @return instance of this builder * @since 0.11 */ public Builder config(String mimeType, String key, Object value) { if (this.arguments == null) { this.arguments = new ArrayList<>(); } this.arguments.add(new Object[]{mimeType, key, value}); return this; } /** * Adds global named symbol into the configuration of to-be-built {@link PolyglotEngine}. * This symbol will be accessible to all languages via * {@link Env#importSymbol(java.lang.String)} and will take precedence over * {@link TruffleLanguage#findExportedSymbol symbols exported by languages itself}. Repeated * use of globalSymbol is possible; later definition of the same name overrides * the previous one. * * @param name name of the symbol to register * @param obj value of the object - expected to be primitive wrapper, {@link String} or * TruffleObject for mutual inter-operability. If the object isn't * of the previous types, the system tries to wrap it using * {@link JavaInterop#asTruffleObject(java.lang.Object)}, if available * @return instance of this builder * @see PolyglotEngine#findGlobalSymbol(java.lang.String) * @throws IllegalArgumentException if the object isn't of primitive type and cannot be * converted to {@link TruffleObject} * @since 0.9 */ public Builder globalSymbol(String name, Object obj) { final Object truffleReady; if (obj instanceof TruffleObject || obj instanceof Number || obj instanceof String || obj instanceof Character || obj instanceof Boolean) { truffleReady = obj; } else { if (JAVA_INTEROP_ENABLED) { truffleReady = JavaInterop.asTruffleObject(obj); } else { throw new IllegalArgumentException(); } } globals.put(name, truffleReady); return this; } /** * Provides own executor for running {@link PolyglotEngine} scripts. By default * {@link PolyglotEngine#eval(com.oracle.truffle.api.source.Source)} and * {@link Value#invoke(java.lang.Object, java.lang.Object[])} are executed synchronously in * the calling thread. Sometimes, however it is more beneficial to run them asynchronously - * the easiest way to do so is to provide own executor when configuring the { * {@link #executor(java.util.concurrent.Executor) the builder}. The executor is expected to * execute all {@link Runnable runnables} passed into its * {@link Executor#execute(java.lang.Runnable)} method in the order they arrive and in a * single (yet arbitrary) thread. * * @param executor the executor to use for internal execution inside the {@link #build() to * be created} {@link PolyglotEngine} * @return instance of this builder * @since 0.9 */ @SuppressWarnings("hiding") public Builder executor(Executor executor) { this.executor = executor; return this; } /** * Creates the {@link PolyglotEngine Truffle virtual machine}. The configuration is taken * from values passed into configuration methods in this class. * * @return new, isolated virtual machine with pre-registered languages * @since 0.9 */ public PolyglotEngine build() { assertNoTruffle(); if (out == null) { out = System.out; } if (err == null) { err = System.err; } if (in == null) { in = System.in; } return new PolyglotEngine(executor, globals, out, err, in, handlers.toArray(new EventConsumer[0]), arguments); } } /** * Descriptions of languages supported in this Truffle virtual machine. * * @return an immutable map with keys being MIME types and values the {@link Language * descriptions} of associated languages * @since 0.9 */ public Map getLanguages() { return Collections.unmodifiableMap(langs); } /** * Returns all instruments loaded in this this {@linkplain PolyglotEngine engine}, * whether or not they are currently enabled. Some instruments are enabled automatically at * startup. * * @return the set of currently loaded instruments * @since 0.9 */ public Map getInstruments() { return instruments; } /** * Evaluates provided source. Chooses language registered for a particular * {@link Source#getMimeType() MIME type} (throws {@link IOException} if there is none). The * language is then allowed to parse and execute the source. * * @param source code snippet to execute * @return a {@link Value} object that holds result of an execution, never null * @throws IOException thrown to signal errors while processing the code * @since 0.9 */ public Value eval(Source source) throws IOException { assertNoTruffle(); String mimeType = source.getMimeType(); assert checkThread(); Language l = langs.get(mimeType); if (l == null) { throw new IOException("No language for MIME type " + mimeType + " found. Supported types: " + langs.keySet()); } return eval(l, source); } /** * Dispose instance of this engine. A user can explicitly * {@link TruffleLanguage#disposeContext(java.lang.Object) dispose all resources} allocated by * the languages active in this engine, when it is known the system is not going to be used in * the future. *

* Calling any other method of this class after the dispose has been done yields an * {@link IllegalStateException}. * * @since 0.9 */ public void dispose() { assert checkThread(); assertNoTruffle(); disposed = true; ComputeInExecutor compute = new ComputeInExecutor(executor) { @Override protected Void compute() throws IOException { for (Language language : getLanguages().values()) { TruffleLanguage impl = language.getImpl(false); if (impl != null) { try { Access.LANGS.dispose(impl, language.getEnv(true)); } catch (Exception | Error ex) { LOG.log(Level.SEVERE, "Error disposing " + impl, ex); } } } for (Instrument instrument : instruments.values()) { try { /* * TODO (chumer): ideally no cleanup is required for disposing * PolyglotEngine if no ASTs are shared between instances. the anything * might be shared assumption invalidates this optimization we should have a * way to find out if a CallTarget/RootNode is shared across PolyglotEngine * instances. */ instrument.setEnabledImpl(false, false); } catch (Exception | Error ex) { LOG.log(Level.SEVERE, "Error disposing " + instrument, ex); } } return null; } }; try { compute.perform(); } catch (IOException ex) { throw new IllegalStateException(ex); } } private Value eval(final Language l, final Source s) throws IOException { final TruffleLanguage[] lang = {null}; if (executor == null) { Object value = evalImpl(lang, s, l); return new DirectValue(lang, value); } ComputeInExecutor compute = new ComputeInExecutor(executor) { @Override protected Object compute() throws IOException { return evalImpl(lang, s, l); } }; compute.perform(); return new ExecutorValue(lang, compute); } Language createLanguage(Map.Entry en) { return new Language(en.getValue()); } ContextStore context() { return context; } Object[] debugger() { return debugger; } @SuppressWarnings("try") private Object evalImpl(TruffleLanguage[] fillLang, Source s, Language l) throws IOException { ContextStore prev = ExecutionImpl.executionStarted(context); try { Access.DEBUG.executionStarted(PolyglotEngine.this, -1, debugger, s); TruffleLanguage langImpl = l.getImpl(true); fillLang[0] = langImpl; return Access.LANGS.eval(langImpl, s, l.cache); } finally { ExecutionImpl.executionEnded(prev); Access.DEBUG.executionEnded(PolyglotEngine.this, debugger); } } @SuppressWarnings({"try"}) final Object invokeForeign(final Node foreignNode, VirtualFrame frame, final TruffleObject receiver) throws IOException { assertNoTruffle(); Object res; CompilerAsserts.neverPartOfCompilation(); if (executor == null) { ContextStore prev = ExecutionImpl.executionStarted(context); try { Access.DEBUG.executionStarted(PolyglotEngine.this, -1, debugger, null); final Object[] args = ForeignAccess.getArguments(frame).toArray(); res = ForeignAccess.execute(foreignNode, frame, receiver, args); } finally { ExecutionImpl.executionEnded(prev); Access.DEBUG.executionEnded(PolyglotEngine.this, debugger); } } else { res = invokeForeignOnExecutor(foreignNode, frame, receiver); } if (res instanceof TruffleObject) { return new EngineTruffleObject(this, (TruffleObject) res); } else { return res; } } static void assertNoTruffle() { CompilerAsserts.neverPartOfCompilation("Methods of PolyglotEngine must not be compiled by Truffle. Use Truffle interoperability or a @TruffleBoundary instead."); } @TruffleBoundary private Object invokeForeignOnExecutor(final Node foreignNode, VirtualFrame frame, final TruffleObject receiver) throws IOException { final MaterializedFrame materialized = frame.materialize(); ComputeInExecutor compute = new ComputeInExecutor(executor) { @SuppressWarnings("try") @Override protected Object compute() throws IOException { ContextStore prev = ExecutionImpl.executionStarted(context); try { Access.DEBUG.executionStarted(PolyglotEngine.this, -1, debugger, null); final Object[] args = ForeignAccess.getArguments(materialized).toArray(); RootNode node = SymbolInvokerImpl.createTemporaryRoot(TruffleLanguage.class, foreignNode, receiver); final CallTarget target = Truffle.getRuntime().createCallTarget(node); return target.call(args); } finally { ExecutionImpl.executionEnded(prev); Access.DEBUG.executionEnded(PolyglotEngine.this, debugger); } } }; return compute.get(); } /** * Looks global symbol provided by one of initialized languages up. First of all execute your * program via one of your {@link #eval(com.oracle.truffle.api.source.Source)} and then look * expected symbol up using this method. *

* The names of the symbols are language dependent, but for example the Java language bindings * follow the specification for method references: *

    *
  • "java.lang.Exception::new" is a reference to constructor of {@link Exception} *
  • "java.lang.Integer::valueOf" is a reference to static method in {@link Integer} class *
* Once an symbol is obtained, it remembers values for fast access and is ready for being * invoked. * * @param globalName the name of the symbol to find * @return found symbol or null if it has not been found * @since 0.9 */ public Value findGlobalSymbol(final String globalName) { assert checkThread(); assertNoTruffle(); final TruffleLanguage[] lang = {null}; ComputeInExecutor compute = new ComputeInExecutor(executor) { @Override protected Object compute() throws IOException { Object obj = globals.get(globalName); if (obj == null) { for (Language dl : langs.values()) { TruffleLanguage.Env env = dl.getEnv(false); if (env == null) { continue; } obj = Access.LANGS.findExportedSymbol(env, globalName, true); if (obj != null) { lang[0] = dl.getImpl(true); break; } } } if (obj == null) { for (Language dl : langs.values()) { TruffleLanguage.Env env = dl.getEnv(false); if (env == null) { continue; } obj = Access.LANGS.findExportedSymbol(env, globalName, true); if (obj != null) { lang[0] = dl.getImpl(true); break; } } } return obj; } }; try { compute.perform(); if (compute.get() == null) { return null; } } catch (IOException ex) { // OK, go on } return new ExecutorValue(lang, compute); } private boolean checkThread() { if (initThread != Thread.currentThread()) { throw new IllegalStateException("PolyglotEngine created on " + initThread.getName() + " but used on " + Thread.currentThread().getName()); } if (disposed) { throw new IllegalStateException("Engine has already been disposed"); } return true; } @SuppressWarnings("unchecked") void dispatch(Object ev, int type) { if (type == Accessor.EngineSupport.EXECUTION_EVENT) { dispatchExecutionEvent(ev); } if (type == Accessor.EngineSupport.SUSPENDED_EVENT) { dispatchSuspendedEvent(ev); } Class clazz = ev.getClass(); dispatch(clazz, ev); } /** * just to make javac happy. * * @param event */ void dispatchSuspendedEvent(Object event) { } /** * just to make javac happy. * * @param event */ void dispatchExecutionEvent(Object event) { } @SuppressWarnings("unchecked") void dispatch(Class type, Event event) { for (EventConsumer handler : handlers) { if (handler.type == type) { handler.on(event); } } } /** * A future value wrapper. A user level wrapper around values returned by evaluation of various * {@link PolyglotEngine} functions like * {@link PolyglotEngine#findGlobalSymbol(java.lang.String)} and * {@link PolyglotEngine#eval(com.oracle.truffle.api.source.Source)} or a value returned by * {@link #invoke(java.lang.Object, java.lang.Object...) a subsequent execution}. In case the * {@link PolyglotEngine} has been initialized for * {@link Builder#executor(java.util.concurrent.Executor) asynchronous execution}, the * {@link Value} represents a future - i.e., it is returned immediately, leaving the execution * running on behind. * * @since 0.9 */ public abstract class Value { private final TruffleLanguage[] language; private CallTarget target; Value(TruffleLanguage[] language) { this.language = language; } abstract boolean isDirect(); abstract Object value() throws IOException; /** * Obtains the object represented by this symbol. The raw object can either be a * wrapper about primitive type (e.g. {@link Number}, {@link String}, {@link Character}, * {@link Boolean}) or a TruffleObject representing more complex object from a * language. The method can return null. * * @return the object or null * @throws IOException in case it is not possible to obtain the value of the object * @since 0.9 */ public Object get() throws IOException { assertNoTruffle(); Object result = waitForSymbol(); if (executor != null && result instanceof TruffleObject) { return new EngineTruffleObject(PolyglotEngine.this, (TruffleObject) result); } else { return result; } } /** * Obtains Java view of the object represented by this symbol. The method basically * delegates to * {@link JavaInterop#asJavaObject(java.lang.Class, com.oracle.truffle.api.interop.TruffleObject)} * . The method handles primitive types (like {@link Number}, etc.) by casting and returning * them. When a {@link String}.class is requested, the method let's the * language that produced the value to do the * {@link TruffleLanguage#toString(java.lang.Object, java.lang.Object) necessary formating}. * * @param the type of the view one wants to obtain * @param representation the class of the view interface (it has to be an interface) * @return instance of the view wrapping the object of this symbol * @throws IOException in case it is not possible to obtain the value of the object * @throws ClassCastException if the value cannot be converted to desired view * @since 0.9 */ public T as(final Class representation) throws IOException { assertNoTruffle(); final Object obj = get(); if (obj instanceof EngineTruffleObject) { EngineTruffleObject eto = (EngineTruffleObject) obj; if (representation.isInstance(eto.getDelegate())) { return representation.cast(eto.getDelegate()); } } if (representation == String.class) { final Class clazz = language[0].getClass(); Object unwrapped = obj; while (unwrapped instanceof EngineTruffleObject) { unwrapped = ((EngineTruffleObject) obj).getDelegate(); } return representation.cast(Access.LANGS.toString(language[0], findEnv(clazz), unwrapped)); } if (representation.isInstance(obj)) { return representation.cast(obj); } if (JAVA_INTEROP_ENABLED) { return JavaInterop.asJavaObject(representation, (TruffleObject) obj); } throw new ClassCastException("Value cannot be represented as " + representation.getName()); } /** * Invokes the symbol. If the symbol represents a function, then it should be invoked with * provided arguments. If the symbol represents a field, then first argument (if provided) * should set the value to the field; the return value should be the actual value of the * field when the invoke method returns. * * @param thiz this/self in language that support such concept; use null to let * the language use default this/self or ignore the value * @param args arguments to pass when invoking the symbol * @return symbol wrapper around the value returned by invoking the symbol, never * null * @throws IOException signals problem during execution * @since 0.9 */ @Deprecated public Value invoke(final Object thiz, final Object... args) throws IOException { return execute(args); } /** * Executes the symbol. If the symbol represents a function, then it should be invoked with * provided arguments. If the symbol represents a field, then first argument (if provided) * should set the value to the field; the return value should be the actual value of the * field when the invoke method returns. * * @param args arguments to pass when invoking the symbol; either wrappers of Java primitive * types (e.g. {@link java.lang.Byte}, {@link java.lang.Short}, * {@link java.lang.Integer}, {@link java.lang.Long}, {@link java.lang.Float}, * {@link java.lang.Double}, {@link java.lang.Character}, * {@link java.lang.Boolean}, and {@link java.lang.String}) or a * {@link TruffleObject object created} by one of the languages) * * @return symbol wrapper around the value returned by invoking the symbol, never * null * @throws IOException signals problem during execution * @since 0.9 */ public Value execute(final Object... args) throws IOException { if (isDirect()) { Object ret = executeDirect(args); return new DirectValue(language, ret); } assertNoTruffle(); get(); ComputeInExecutor invokeCompute = new ComputeInExecutor(executor) { @SuppressWarnings("try") @Override protected Object compute() throws IOException { return executeDirect(args); } }; invokeCompute.perform(); return new ExecutorValue(language, invokeCompute); } @SuppressWarnings("try") private Object executeDirect(Object[] args) throws IOException { if (target == null) { target = SymbolInvokerImpl.createCallTarget(language[0], PolyglotEngine.this, value()); } return target.call(args); } private Object waitForSymbol() throws IOException { assertNoTruffle(); assert checkThread(); return value(); } } private class DirectValue extends Value { private final Object value; DirectValue(TruffleLanguage[] language, Object value) { super(language); this.value = value; } @Override boolean isDirect() { return true; } @Override Object value() { return value; } @Override public String toString() { return "PolyglotEngine.Value[value=" + value + ",computed=true,exception=null]"; } } private class ExecutorValue extends Value { private final ComputeInExecutor compute; ExecutorValue(TruffleLanguage[] language, ComputeInExecutor compute) { super(language); this.compute = compute; } @Override boolean isDirect() { return false; } @Override Object value() throws IOException { return compute.get(); } @Override public String toString() { return "PolyglotEngine.Value[" + compute + "]"; } } /** * Handle for an installed {@linkplain TruffleInstrument instrument}: a client of a running * {@linkplain PolyglotEngine engine} that can observe and inject behavior into interpreters * written using the Truffle framework. The handle provides access to the instrument's metadata * and allows the instrument to be {@linkplain Instrument#setEnabled(boolean) enabled/disabled}. * * @see PolyglotEngine#getInstruments() * @since 0.9 */ public final class Instrument { private final InstrumentCache info; private boolean enabled; Instrument(InstrumentCache cache) { this.info = cache; } /** * @return the id of the instrument * @since 0.9 */ public String getId() { return info.getId(); } /** * @return a human readable name of the installed instrument. * @since 0.9 */ public String getName() { return info.getName(); } /** * @return the version of the installed instrument. * @since 0.9 */ public String getVersion() { return info.getVersion(); } InstrumentCache getCache() { return info; } /** * @return true if the underlying instrument is enabled else false * @since 0.9 */ public boolean isEnabled() { return enabled; } /** * Lookup additional service provided by the instrument. Here is an example how to query for * a hypothetical DebuggerController: {@codesnippet DebuggerExampleTest} * * @param the type of the service * @param type class of the service that is being requested * @return instance of requested type, or null if no such service is available * for the instrument * @since 0.9 */ public T lookup(Class type) { return Access.INSTRUMENT.getInstrumentationHandlerService(instrumentationHandler, this, type); } /** * Enables/disables the installed instrument in the engine. * * @param enabled true to enable false to disable * @since 0.9 */ public void setEnabled(final boolean enabled) { assert checkThread(); if (this.enabled != enabled) { ComputeInExecutor compute = new ComputeInExecutor(executor) { @Override protected Void compute() throws IOException { setEnabledImpl(enabled, true); return null; } }; try { compute.perform(); } catch (IOException ex) { throw new IllegalStateException(ex); } } } void setEnabledImpl(final boolean enabled, boolean cleanup) { if (this.enabled != enabled) { // check again for thread safety if (enabled) { Access.INSTRUMENT.addInstrument(instrumentationHandler, this, getCache().getInstrumentationClass()); } else { Access.INSTRUMENT.disposeInstrument(instrumentationHandler, this, cleanup); } this.enabled = enabled; } } /** @since 0.9 */ @Override public String toString() { return "Instrument [id=" + getId() + ", name=" + getName() + ", version=" + getVersion() + ", enabled=" + enabled + "]"; } } /** * Description of a language registered in {@link PolyglotEngine Truffle virtual machine}. * Languages are registered by {@link Registration} annotation which stores necessary * information into a descriptor inside of the language's JAR file. When a new * {@link PolyglotEngine} is created, it reads all available descriptors and creates * {@link Language} objects to represent them. One can obtain a {@link #getName() name} or list * of supported {@link #getMimeTypes() MIME types} for each language. The actual language * implementation is not initialized until * {@link PolyglotEngine#eval(com.oracle.truffle.api.source.Source) a code is evaluated} in it. * * @since 0.9 */ public class Language { private final Map cache; private final LanguageCache info; private TruffleLanguage.Env env; Language(LanguageCache info) { this.cache = new WeakHashMap<>(); this.info = info; } /** * MIME types recognized by the language. * * @return returns immutable set of recognized MIME types * @since 0.9 */ public Set getMimeTypes() { return info.getMimeTypes(); } /** * Human readable name of the language. Think of C, Ruby, JS, etc. * * @return string giving the language a name * @since 0.9 */ public String getName() { return info.getName(); } /** * Name of the language version. * * @return string specifying the language version * @since 0.9 */ public String getVersion() { return info.getVersion(); } /** * Evaluates provided source. Ignores the particular {@link Source#getMimeType() MIME type} * and forces evaluation in the context of this language. * * @param source code snippet to execute * @return a {@link Value} object that holds result of an execution, never null * @throws IOException thrown to signal errors while processing the code * @since 0.9 */ public Value eval(Source source) throws IOException { assertNoTruffle(); assert checkThread(); return PolyglotEngine.this.eval(this, source); } /** * Returns value representing global object of the language. *

* The object is expected to be TruffleObject (e.g. a native object from the * other language) but technically it can be one of Java primitive wrappers ({@link Integer} * , {@link Double}, {@link Short}, etc.). * * @return the global object or null if the language does not support such * concept * @since 0.9 */ @SuppressWarnings("try") public Value getGlobalObject() { assert checkThread(); ContextStore prev = ExecutionImpl.executionStarted(context); try { Object res = Access.LANGS.languageGlobal(getEnv(true)); if (res == null) { return null; } return new DirectValue(new TruffleLanguage[]{info.getImpl(true)}, res); } finally { ExecutionImpl.executionEnded(prev); } } TruffleLanguage getImpl(boolean create) { getEnv(create); TruffleLanguage impl = info.getImpl(false); return impl; } private Map getArgumentsForLanguage() { if (config == null) { return Collections.emptyMap(); } Map forLanguage = new HashMap<>(); for (Object[] mimeKeyValue : config) { if (getMimeTypes().contains(mimeKeyValue[0])) { forLanguage.put((String) mimeKeyValue[1], mimeKeyValue[2]); } } return Collections.unmodifiableMap(forLanguage); } TruffleLanguage.Env getEnv(boolean create) { if (env == null && create) { env = Access.LANGS.attachEnv(PolyglotEngine.this, info.getImpl(true), out, err, in, instrumenter, getArgumentsForLanguage()); } return env; } /** @since 0.9 */ @Override public String toString() { return "[" + getName() + "@ " + getVersion() + " for " + getMimeTypes() + "]"; } } // end of Language // // Accessor helper methods // TruffleLanguage findLanguage(Class languageClazz) { for (Map.Entry entrySet : langs.entrySet()) { Language languageDescription = entrySet.getValue(); final TruffleLanguage impl = languageDescription.getImpl(false); if (languageClazz.isInstance(impl)) { return impl; } } return null; } TruffleLanguage findLanguage(String mimeType) { Language languageDescription = this.langs.get(mimeType); if (languageDescription != null) { return languageDescription.getImpl(true); } return null; } Env findEnv(Class languageClazz) { for (Map.Entry entrySet : langs.entrySet()) { Language languageDescription = entrySet.getValue(); Env env = languageDescription.getEnv(false); if (env != null && languageClazz.isInstance(languageDescription.getImpl(false))) { return env; } } throw new IllegalStateException("Cannot find language " + languageClazz + " among " + langs); } static class Access { static final Accessor.LanguageSupport LANGS = SPIAccessor.langs(); static final Accessor.InstrumentSupport INSTRUMENT = SPIAccessor.instrumentAccess(); static final Accessor.DebugSupport DEBUG = SPIAccessor.debugAccess(); } private static class SPIAccessor extends Accessor { static LanguageSupport langs() { return SPI.languageSupport(); } static InstrumentSupport instrumentAccess() { return SPI.instrumentSupport(); } static DebugSupport debugAccess() { return SPI.debugSupport(); } @Override protected EngineSupport engineSupport() { return new EngineImpl(); } static final class EngineImpl extends EngineSupport { @Override public boolean isMimeTypeSupported(Object obj, String mimeType) { final PolyglotEngine vm = (PolyglotEngine) obj; return vm.findLanguage(mimeType) != null; } @Override public Env findEnv(Object obj, Class languageClass) { PolyglotEngine vm = (PolyglotEngine) obj; return vm.findEnv(languageClass); } @Override public void dispatchEvent(Object obj, Object event, int type) { PolyglotEngine vm = (PolyglotEngine) obj; vm.dispatch(event, type); } @Override public TruffleLanguage findLanguageImpl(Object obj, Class languageClazz, String mimeType) { final PolyglotEngine vm = (PolyglotEngine) (obj == null ? ExecutionImpl.findVM() : obj); if (vm == null) { throw new IllegalStateException("Accessor.findLanguageImpl access to vm"); } TruffleLanguage language = null; if (languageClazz != null) { language = vm.findLanguage(languageClazz); } if (language == null && mimeType != null) { language = vm.findLanguage(mimeType); } if (language == null) { throw new IllegalStateException("Cannot find language " + languageClazz + " with mimeType" + mimeType + " among " + vm.langs); } return language; } @Override public Object getInstrumenter(Object obj) { final PolyglotEngine vm = (PolyglotEngine) (obj == null ? ExecutionImpl.findVM() : obj); return vm == null ? null : vm.instrumenter; } @Override public Object getInstrumentationHandler(Object obj) { final PolyglotEngine vm = (PolyglotEngine) (obj == null ? ExecutionImpl.findVM() : obj); return vm == null ? null : vm.instrumentationHandler; } @Override public Object importSymbol(Object vmObj, TruffleLanguage ownLang, String globalName) { PolyglotEngine vm = (PolyglotEngine) vmObj; Object g = vm.globals.get(globalName); if (g != null) { return g; } Set uniqueLang = new LinkedHashSet<>(vm.langs.values()); for (Language dl : uniqueLang) { TruffleLanguage l = dl.getImpl(false); TruffleLanguage.Env env = dl.getEnv(false); if (l == ownLang || l == null || env == null) { continue; } Object obj = Access.LANGS.findExportedSymbol(env, globalName, true); if (obj != null) { return obj; } } for (Language dl : uniqueLang) { TruffleLanguage l = dl.getImpl(false); TruffleLanguage.Env env = dl.getEnv(false); if (l == ownLang || l == null || env == null) { continue; } Object obj = Access.LANGS.findExportedSymbol(env, globalName, false); if (obj != null) { return obj; } } return null; } @Override public FindContextNode createFindContextNode(TruffleLanguage lang) { return new FindContextNodeImpl<>(lang); } } } // end of SPIAccessor } class PolyglotEngineSnippets { abstract class YourLang extends TruffleLanguage { public static final String MIME_TYPE = "application/my-test-lang"; } public static PolyglotEngine initializeWithParameters() { // @formatter:off // BEGIN: com.oracle.truffle.api.vm.PolyglotEngineSnippets#initializeWithParameters String[] args = {"--kernel", "Kernel.som", "--instrument", "dyn-metrics"}; PolyglotEngine.Builder builder = PolyglotEngine.newBuilder(); builder.config(YourLang.MIME_TYPE, "CMD_ARGS", args); PolyglotEngine vm = builder.build(); // END: com.oracle.truffle.api.vm.PolyglotEngineSnippets#initializeWithParameters // @formatter:on return vm; } }