/*
* 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.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
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.CompilationFinal;
import com.oracle.truffle.api.InstrumentInfo;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.impl.Accessor;
import com.oracle.truffle.api.impl.DispatchOutputStream;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.java.JavaInterop;
import com.oracle.truffle.api.nodes.LanguageInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.vm.ComputeInExecutor.Info;
import com.oracle.truffle.api.vm.PolyglotEngine.Builder;
import com.oracle.truffle.api.vm.PolyglotEngine.Value;
import com.oracle.truffle.api.vm.PolyglotRootNode.EvalRootNode;
import com.oracle.truffle.api.vm.PolyglotRuntime.LanguageShared;
/**
* A multi-language execution environment for Truffle-implemented {@linkplain Language languages}
* that supports <em>interoperability</em> among the Truffle languages and with Java, for example
* cross-language calls, foreign object exchange, and shared <em>global symbols</em>. The
* environment also includes a framework for <em>instrumentation</em> that supports both built-in
* services, for example debugging and profiling, as well as API access to dynamic execution state
* for external tools.
* <p>
* For a simple example see {@link #eval(Source) eval(Source)}. The
* <em><a href="{@docRoot}/com/oracle/truffle/tutorial/package-summary.html">Truffle
* Tutorial</a></em> provides much more general information about Truffle. For information
* specifically related to Java applications, the tutorial
* <em><a href="{@docRoot}/com/oracle/truffle/tutorial/embedding/package-summary.html">Embedding
* Truffle Languages in Java</a></em> explains, with examples, how Java code can directly access
* guest language functions, objects, classes, and some complex data structures with Java-typed
* accessors. In the reverse direction, guest language code can access Java objects, classes, and
* constructors.
*
* <h4>Engine Creation</h4>
*
* The {@link PolyglotEngine.Builder Builder} creates new <em>engine</em> instances and allows both
* application- and language-specific configuration. The following example creates a default
* instance.
* <p>
* {@codesnippet PolyglotEngineSnippets#defaultPolyglotEngine}
*
* <h4>Truffle Languages</h4>
*
* An engine supports every {@linkplain Language Truffle language} available on the host JVM class
* path.
* <p>
* Languages are initialized on demand, the first time an engine evaluates code of a matching
* {@linkplain Source#getMimeType() MIME type}. The engine throws an {@link IllegalStateException}
* if no matching language is available. Languages remain initialized for the lifetime of the
* engine.
* <p>
* Specific language environments can be configured, for example in response to command line
* options, by building the engine with combinations of language-specific MIME-key-value settings (
* {@link Builder#config(String, String, Object) Builder.config(String, String, Object)}) and
* pre-registered global symbols ({@link Builder#globalSymbol(String, Object)
* Builder.globalSymbol(String, Object)} )
*
* <h4>Global Symbols</h4>
*
* An engine supports communication among {@linkplain Language languages} via shared named values
* known as <em>global symbols</em>. These typically implement guest language export/import
* statements used for <em>language interoperation</em>.
* <p>
* Each language dynamically manages a namespace of global symbols. The engine provides its own
* (static) namespace, configured when the engine is built (
* {@link Builder#globalSymbol(String, Object) Builder.globalSymbol(String, Object)}).
* <p>
* An engine retrieves global symbols by name, first searching the engine's namespace, and then
* searching all language namespaces in unspecified order, returning the first one found (
* {@link #findGlobalSymbol(String) findGlobalSymbol(String)}). Name collisions across namespaces
* are possible and can only be discovered by explicitly retrieving all global symbols with a
* particular name ({@link #findGlobalSymbols(String) findGlobalSymbols(String)}).
*
* <h4>Truffle Cross-language Interoperation</h4>
*
* <em>Interoperability</em> among executing guest language programs is supported in part by the
* cross-language exchange of <em>global symbols</em> whose {@linkplain #findGlobalSymbol(String)
* retrieval} produces results wrapped {@link Value Value} instances. The {@linkplain Value#get()
* content} of a {@link Value Value} may be a boxed Java primitive type or a <em>foreign object</em>
* (implemented internally as a {@link TruffleObject}). Foreign objects support a message-based
* protocol for access to their contents, possibly including fields and methods.
* <p>
* Foreign objects may be functional, in which case cross-language method/procedure calls are
* possible. Foreign calls return results wrapped in non-null {@link Value Value} instances.
*
* <h4>Truffle-Java Interoperation</h4>
*
* <em>Interoperability</em> is also supported between Java and guest language programs. For example
* a Java client can <em>export</em> global symbols during engine creation (
* {@link Builder#globalSymbol(String, Object) Builder.globalSymbol(String, Object)}) and
* <em>import</em> them at any time ({@link #findGlobalSymbol(String) findGlobalSymbol(String)}).
* The {@linkplain Value#get() content} of a {@link Value Value} (for example retrieved from an
* imported symbol) can be accessed ({@link Value#as(Class) Value.as(Class)}) as an object of
* requested type (a kind of cross-langauge "cast"). A {@link Value Value} determined to be
* functional can be called ({@link Value#execute(Object...) Value.execute(Object...)}) and will
* return the result wrapped in a non-null {@link Value Value} instance.
* <p>
* Java clients <em>export values</em> in two ways:
* <ul>
* <li>bound to globally accessible names ({@link Builder#globalSymbol(String, Object)
* Builder.globalSymbol(String, Object)}), or</li>
* <li>as arguments to foreign method/procedure calls ({@link Value#execute(Object...)
* Value.execute(Object...)}).</li>
* </ul>
* In either case the engine <em>wraps</em> exported non-primitive Java values so that they appear
* to guest languages the same as other foreign objects. In situations where a Java object is
* exported and eventually returned (by import or method/procedure call), the identity of the result
* (obtained by {@link Value#get() Value.get()}) is preserved.
* <p>
* Exporting a Java <em>object</em> grants guest language access to the object's public fields and
* methods. Exporting a Java <em>class</em> grants guest language access to the class's public
* static fields and public constructors.
*
* <h4>Engine Isolation</h4>
*
* Engines are isolated using the {@link PolyglotRuntime runtime} they are
* {@link Builder#runtime(PolyglotRuntime) assocated} with. A runtime is associated with one or more
* polyglot engines. One runtime is a isolated
* <a href="https://en.wikipedia.org/wiki/Multitenancy">tenant</a> on a host Virtual Machine. Other
* than shared host resources such as memory, no aspects of program execution, language
* environments, or global symbols are shared with other runtimes.
*
* <h4>Threading</h4>
*
* Guest language code execution is single-threaded, performed on a thread determined by the
* engine's configuration.
* <p>
* <ul>
* <li>Execution ({@link #eval(Source) eval(Source)}) is by default <em>synchronous</em> (performed
* on the calling thread) and only permitted on the thread that created the engine.</li>
* <li>An engine can be configured with a custom Executor ({@link Builder#executor(Executor)
* Builder.executor(Executor)}) that performs executions on a different thread. In this case the
* engine requires only that all executions are performed on the same thread.</li>
* </ul>
* <p>
* In contrast, instrumentation-based access to engine state is <em>thread-safe</em>, both from
* built-in services such as debugging and profiling as well as from external tools.
*
* @since 0.9
* @see TruffleLanguage More information for language implementors.
*/
@SuppressWarnings({"rawtypes"})
public class PolyglotEngine {
static final Logger LOG = Logger.getLogger(PolyglotEngine.class.getName());
static final PolyglotEngine UNUSABLE_ENGINE = new PolyglotEngine();
private static final SPIAccessor SPI = new SPIAccessor();
/**
* Currently we need to have one global profile to support
* {@link com.oracle.truffle.api.vm.PolyglotEngine.SPIAccessor.EngineImpl#getCurrentVM()}.
*/
static final PolyglotEngineProfile GLOBAL_PROFILE = new PolyglotEngineProfile(null);
static final Object UNSET_CONTEXT = new Object();
private final Thread initThread;
private final PolyglotCache cachedTargets;
private final Map<LanguageShared, Language> sharedToLanguage;
private final Map<String, Language> mimeTypeToLanguage;
/* Used for fast context lookup */
@CompilationFinal(dimensions = 1) final Language[] languageArray;
final PolyglotRuntime runtime;
final InputStream in;
final DispatchOutputStream err;
final DispatchOutputStream out;
final ComputeInExecutor.Info executor;
private volatile 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 List<Object[]> config;
private HashMap<String, Object> globals;
/**
* Private & temporary only constructor.
*/
PolyglotEngine() {
assertNoCompilation();
this.initThread = null;
this.runtime = null;
this.cachedTargets = null;
this.languageArray = null;
this.sharedToLanguage = null;
this.mimeTypeToLanguage = null;
this.in = null;
this.out = null;
this.err = null;
this.executor = null;
}
/**
* Constructor used from the builder.
*/
PolyglotEngine(PolyglotRuntime runtime, Executor executor, InputStream in, DispatchOutputStream out, DispatchOutputStream err, Map<String, Object> globals, List<Object[]> config) {
assertNoCompilation();
this.initThread = Thread.currentThread();
this.runtime = runtime;
this.languageArray = new Language[runtime.getLanguages().size()];
this.cachedTargets = new PolyglotCache(this);
this.sharedToLanguage = new HashMap<>();
this.mimeTypeToLanguage = new HashMap<>();
this.in = in;
this.out = out;
this.err = err;
this.executor = ComputeInExecutor.wrap(executor);
this.globals = new HashMap<>(globals);
this.config = config;
initLanguages();
runtime.notifyEngineCreated();
}
private void initLanguages() {
for (LanguageShared languageShared : runtime.getLanguages()) {
Language newLanguage = new Language(languageShared);
sharedToLanguage.put(languageShared, newLanguage);
assert languageArray[languageShared.languageId] == null : "attempting to overwrite language";
languageArray[languageShared.languageId] = newLanguage;
for (String mimeType : languageShared.cache.getMimeTypes()) {
mimeTypeToLanguage.put(mimeType, newLanguage);
}
}
}
PolyglotEngine enter() {
return runtime.engineProfile.enter(this);
}
void leave(Object prev) {
runtime.engineProfile.leave((PolyglotEngine) prev);
}
Info executor() {
return executor;
}
private boolean isCurrentVM() {
return this == runtime.engineProfile.get();
}
/**
* Returns a builder for creating an engine instance. After any configuration methods have been
* called, the final {@link Builder#build() build()} step creates the engine and installs all
* available languages. For example:
*
* <pre>
* {@link PolyglotEngine} engine = {@link PolyglotEngine}.{@link PolyglotEngine#newBuilder() newBuilder()}
* .{@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()};
* </pre>
*
* @return a builder to create a new engine with all available languages installed
* @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 engine = new PolyglotEngine();
return engine.new Builder();
}
/**
* @return new builder
* @deprecated use {@link #newBuilder()}
* @since 0.9
*/
@Deprecated
public static PolyglotEngine.Builder buildNew() {
return newBuilder();
}
/**
* A builder for creating an engine instance. After any configuration methods have been called,
* the final {@link Builder#build() build()} step creates the engine and installs all available
* languages. For example:
*
* <pre>
* {@link PolyglotEngine} engine = {@link PolyglotEngine}.{@link PolyglotEngine#newBuilder() newBuilder()}
* .{@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()};
* </pre>
*
* @since 0.9
*/
public class Builder {
private OutputStream out;
private OutputStream err;
private InputStream in;
private PolyglotRuntime runtime;
private final Map<String, Object> globals = new HashMap<>();
private Executor executor;
private List<Object[]> arguments;
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.9
*/
public Builder setOut(OutputStream os) {
out = os;
return this;
}
/**
* Configures error output for languages running in the {@link PolyglotEngine engine} being
* built, defaults to {@link System#err}.
*
* @param os the stream to use as output
* @return this builder
* @since 0.9
*/
public Builder setErr(OutputStream os) {
err = os;
return this;
}
/**
* Configures default input for languages running in the {@link PolyglotEngine engine} being
* built, defaults to {@link System#in}.
*
* @param is the stream to use as input
* @return this builder
* @since 0.9
*/
public Builder setIn(InputStream is) {
in = is;
return this;
}
/**
* Adds {@link Language Language}-specific initialization data to the {@link PolyglotEngine
* engine} being built. For example:
*
* {@link com.oracle.truffle.api.vm.PolyglotEngineSnippets#initializeWithParameters}
*
* If the same key is specified multiple times for the same language, only the last one
* specified applies.
*
* @param mimeType identification of the language to which the configuration data is
* provided; any of the language's declared MIME types may be used
*
* @param key to identify a language-specific configuration element
* @param value to parameterize initial state of a language
* @return 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 a global symbol (named value) to be exported by the {@link PolyglotEngine engine}
* being built. Any guest {@link Language Language} can <em>import</em> this symbol, which
* takes precedence over any symbols exported under the same name by languages. Any number
* of symbols may be added; in case of name-collision only the last one added will be
* exported. The namespace of exported global symbols is immutable once the engine is built.
* <p>
* See {@linkplain PolyglotEngine "Truffle-Java Interoperation"} for the implications of
* exporting Java data to guest languages. The following example demonstrates the export of
* both a Java class and a Java object:
*
* {@link com.oracle.truffle.api.vm.PolyglotEngineSnippets#configureJavaInterop}
*
* The <code>mul</code> and <code>compose</code> objects are then available to any guest
* language.
*
* @param name name of the global symbol to register
* @param obj value of the symbol to export
* @return this builder
* @see PolyglotEngine#findGlobalSymbol(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 = JavaInterop.asTruffleValue(obj);
globals.put(name, truffleReady);
return this;
}
/**
* Provides an {@link Executor} for running guest language code asynchronously, on a thread
* other than the calling thread.
* <p>
* By default engines execute both {@link PolyglotEngine#eval(Source)} and
* {@link Value#execute(java.lang.Object...)} synchronously in the calling thread.
* <p>
* A custom {@link Executor} is expected to perform every execution it is given (via
* {@link Executor#execute(Runnable)}) in order of arrival. An arbitrary thread may be used,
* but the engine requires that there be only one.
*
* @param executor the executor of code to be used by {@link PolyglotEngine engine} being
* built
* @return this builder
* @since 0.9
*/
@SuppressWarnings("hiding")
public Builder executor(Executor executor) {
this.executor = executor;
return this;
}
/**
* Associates the {@linkplain #build() to be created} {@link PolyglotEngine engine} with an
* {@link PolyglotRuntime execution runtime}. By default each {@link PolyglotEngine engine}
* gets its own private runtime. If the same {@link PolyglotRuntime runtime} is used to
* create multiple {@link PolyglotEngine engines} then resources and code might be cached
* between these engines. A private/default or disposed runtime cannot be used to construct
* an {@link PolyglotEngine engine}. If attempted an {@link IllegalArgumentException} is
* thrown.
*
* Sample usage:
* <p>
* {@codesnippet com.oracle.truffle.api.vm.PolyglotEngineSnippets#createEngines}
* <p>
*
* @param polyglotRuntime an instance of runtime to associate this engine with
* @return this builder
* @since 0.25
* @see PolyglotRuntime
*/
public Builder runtime(PolyglotRuntime polyglotRuntime) {
checkRuntime(polyglotRuntime);
this.runtime = polyglotRuntime;
return this;
}
/**
* Creates an {@link PolyglotEngine engine} configured by builder methods.
*
* @return a new engine with all available languages installed
* @since 0.9
*/
public PolyglotEngine build() {
assertNoCompilation();
InputStream realIn;
DispatchOutputStream realOut;
DispatchOutputStream realErr;
PolyglotRuntime realRuntime = runtime;
if (realRuntime == null) {
realRuntime = PolyglotRuntime.newBuilder().setIn(in).setOut(out).setErr(err).build(true);
realIn = realRuntime.in;
realOut = realRuntime.out;
realErr = realRuntime.err;
} else {
checkRuntime(realRuntime);
if (out == null) {
realOut = realRuntime.out;
} else {
realOut = SPIAccessor.instrumentAccess().createDispatchOutput(out);
SPIAccessor.engine().attachOutputConsumer(realOut, realRuntime.out);
}
if (err == null) {
realErr = realRuntime.err;
} else {
realErr = SPIAccessor.instrumentAccess().createDispatchOutput(err);
SPIAccessor.engine().attachOutputConsumer(realErr, realRuntime.err);
}
realIn = in == null ? realRuntime.in : in;
}
return new PolyglotEngine(realRuntime, executor, realIn, realOut, realErr, globals, arguments);
}
private void checkRuntime(PolyglotRuntime realRuntime) {
if (realRuntime.disposed) {
throw new IllegalArgumentException("Given runtime already disposed.");
}
if (realRuntime.automaticDispose) {
throw new IllegalArgumentException(
"Cannot reuse private/create runtime of another engine. " +
"Please usine an explicitely created PolyglotRuntime instead.");
}
}
}
/**
* Gets the map: MIME type --> {@linkplain Language metadata} for the matching language
* installed in this engine, whether or not the language has been initialized.
*
* @return an immutable map: MIME type --> metadata for the language that supports the source
* type
* @since 0.9
*/
public Map<String, ? extends Language> getLanguages() {
return Collections.unmodifiableMap(mimeTypeToLanguage);
}
/**
* Gets the map: {@linkplain Instrument#getId() Instrument ID} --> {@link Instrument} loaded in
* this {@linkplain PolyglotEngine engine}, whether the instrument is
* {@linkplain Instrument#isEnabled() enabled} or not.
*
* @return map of currently loaded instruments
* @since 0.9
* @deprecated use {@link #getRuntime()}.{@link PolyglotRuntime#getInstruments()}.
*/
@Deprecated
public Map<String, Instrument> getInstruments() {
return runtime.instruments;
}
/**
* Access to associated runtime.
*
* @return the runtime associated with this engine
* @since 0.25
*/
public PolyglotRuntime getRuntime() {
return runtime;
}
/**
* Evaluates guest language source code, using the installed {@link Language Language} that
* matches the code's {@link Source#getMimeType() MIME type}. Source code is provided to the
* engine as {@link Source} objects, which may wrap references to guest language code (e.g. a
* filename or URL) or may represent code literally as in the example below. The engine returns
* the result wrapped in an instance of {@link Value Value}, for which Java-typed access
* (objects) can be created using {@link Value#as(Class) Value.as(Class)}.
*
* {@link com.oracle.truffle.api.vm.PolyglotEngineSnippets#evalCode}
*
* For sources marked as {@link Source#isInteractive() interactive}, the engine will will do
* more, depending the language. This may include printing the result, in a language-specific
* format, to the engine's {@link PolyglotEngine.Builder#setOut standard output}. It might read
* input values queried by the language.
* <p>
* This method is useful for Java applications that <em>interoperate</em> with guest languages.
* The general strategy is to {@linkplain #eval(Source) evaluate} guest language code that
* produces the desired language element and then {@linkplain Value#as(Class) create} a Java
* object of the appropriate type for Java <em>foreign</em> access to the result. The tutorial
* <a href= "{@docRoot}/com/oracle/truffle/tutorial/embedding/package-summary.html">"Embedding
* Truffle Languages in Java"</a> contains examples.
*
* @param source guest language code
* @return result of the evaluation wrapped in a non-null {@link Value}
* @throws IllegalStateException if no installed language matches the code's MIME type
* @throws Exception thrown to signal errors while processing the code
* @see Value#as(Class)
* @since 0.9
*/
public Value eval(Source source) {
assertNoCompilation();
assert checkThread();
return evalImpl(findLanguage(source.getMimeType(), true), source);
}
private Value evalImpl(final Language l, final Source source) {
assert checkThread();
ComputeInExecutor<Object> compute = new ComputeInExecutor<Object>(executor()) {
@Override
protected Object compute() {
CallTarget evalTarget = l.parserCache.get(source);
if (evalTarget == null) {
evalTarget = PolyglotRootNode.createEval(PolyglotEngine.this, l, source);
l.parserCache.put(source, evalTarget);
}
return evalTarget.call();
}
};
compute.perform();
return new ExecutorValue(l, compute);
}
/**
* Disposes this engine instance and {@link TruffleLanguage#disposeContext(Object) releases all
* resources} allocated by languages active in this engine. If a default/private
* {@link #getRuntime() runtime} is configured for this engine then it is disposed automatically
* with this engine.
* <p>
* Calling any other method on this instance after disposal throws an
* {@link IllegalStateException}.
*
* @since 0.9
*/
public void dispose() {
assert checkThread();
assertNoCompilation();
disposed = true;
ComputeInExecutor<Void> compute = new ComputeInExecutor<Void>(executor()) {
@Override
protected Void compute() {
Object prev = enter();
try {
disposeImpl();
return null;
} finally {
leave(prev);
}
}
};
compute.get();
}
private void disposeImpl() {
for (Language language : getLanguages().values()) {
language.disposeContext();
}
runtime.notifyEngineDisposed();
}
private Object[] debugger() {
return runtime.debugger;
}
/**
* Finds a <em>global symbol</em> by name. Returns the symbol in the engine's namespace of
* {@linkplain Builder#globalSymbol(String, Object) preconfigured} global symbols, if present.
* Otherwise returns the first symbol found, if any, in a language namespace, which are queried
* in an unspecified order.
* <p>
* Symbol names are language dependent. Cross-language name collisions are possible, in which
* case this method only returns one of them (use {@link #findGlobalSymbols(String)} to return
* all of them).
*
* @param globalName a global symbol name
* @return the value of a global symbol with the specified name, <code>null</code> if none
* @since 0.9
*/
public Value findGlobalSymbol(final String globalName) {
assert checkThread();
assertNoCompilation();
for (Object v : findGlobalSymbols(globalName)) {
return (Value) v;
}
return null;
}
/**
* Finds all <em>global symbols</em> with a specified name by searching every language's
* namespace of exported symbols, together with the the engine's namespace of
* {@linkplain Builder#globalSymbol(String, Object) preconfigured} symbols.
* <p>
* The following example shows how this method can be used to retrieve a single global symbol,
* while treating name collisions as an error.
*
* {@link com.oracle.truffle.api.vm.PolyglotEngineSnippets#findAndReportMultipleExportedSymbols}
*
* @param globalName a global symbol name
* @return iterable access to the values of global symbols with the specified name
* @since 0.22
*/
public Iterable<Value> findGlobalSymbols(String globalName) {
assert checkThread();
assertNoCompilation();
return new Iterable<Value>() {
final Iterable<? extends Object> iterable = importSymbol(null, globalName, true);
public Iterator<Value> iterator() {
return new Iterator<PolyglotEngine.Value>() {
final Iterator<? extends Object> iterator = iterable.iterator();
public boolean hasNext() {
ComputeInExecutor<Boolean> invokeCompute = new ComputeInExecutor<Boolean>(executor()) {
@SuppressWarnings("try")
@Override
protected Boolean compute() {
Object prev = enter();
try {
return iterator.hasNext();
} finally {
leave(prev);
}
}
};
return invokeCompute.get().booleanValue();
}
public Value next() {
ComputeInExecutor<Value> invokeCompute = new ComputeInExecutor<Value>(executor()) {
@SuppressWarnings("try")
@Override
protected Value compute() {
Object prev = enter();
try {
return (Value) iterator.next();
} finally {
leave(prev);
}
}
};
return invokeCompute.get();
}
};
}
};
}
private Iterable<? extends Object> importSymbol(Language filterLanguage, String globalName, boolean needsValue) {
class SymbolIterator implements Iterator<Object> {
private final Collection<? extends Language> uniqueLang;
private Object next;
private Iterator<? extends Language> explicit;
private Iterator<? extends Language> implicit;
SymbolIterator(Collection<? extends Language> uniqueLang, Object first) {
this.uniqueLang = uniqueLang;
this.next = (needsValue && first != null) ? new DirectValue(null, first) : first;
}
@Override
public boolean hasNext() {
return findNext() != this;
}
@Override
public Object next() {
Object res = findNext();
if (res == this) {
throw new NoSuchElementException();
}
assert !needsValue || res instanceof Value;
next = null;
return res;
}
private Object findNext() {
if (next != null) {
return next;
}
if (explicit == null) {
explicit = uniqueLang.iterator();
}
while (explicit.hasNext()) {
Language dl = explicit.next();
TruffleLanguage.Env env = dl.getEnv(false);
if (dl != filterLanguage && env != null) {
Object obj = findExportedSymbol(dl, env, globalName, true);
if (obj != null) {
next = obj;
explicit.remove();
return next;
}
}
}
if (implicit == null) {
implicit = uniqueLang.iterator();
}
while (implicit.hasNext()) {
Language dl = implicit.next();
TruffleLanguage.Env env = dl.getEnv(false);
if (dl != filterLanguage && env != null) {
Object obj = findExportedSymbol(dl, env, globalName, false);
if (obj != null) {
next = obj;
return next;
}
}
}
return next = this;
}
private Object findExportedSymbol(Language lang, TruffleLanguage.Env env, String name, boolean onlyExplicit) {
Object value = Access.LANGS.findExportedSymbol(env, name, onlyExplicit);
if (needsValue && value != null) {
value = new DirectValue(lang, value);
}
return value;
}
}
Object globalObj = globals.get(globalName);
final Collection<? extends Language> uniqueLang = getLanguages().values();
return new Iterable<Object>() {
@Override
public Iterator<Object> iterator() {
return new SymbolIterator(new LinkedHashSet<>(uniqueLang), globalObj);
}
};
}
private static void assertNoCompilation() {
CompilerAsserts.neverPartOfCompilation("Methods of PolyglotEngine must not be compiled by Truffle. Use Truffle interoperability or a @TruffleBoundary instead.");
}
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;
}
private Language findLanguage(String mimeType, boolean failOnError) {
Language l = mimeTypeToLanguage.get(mimeType);
if (failOnError && l == null) {
throw new IllegalStateException("No language for MIME type " + mimeType + " found. Supported types: " + mimeTypeToLanguage.keySet());
}
return l;
}
//
// Accessor helper methods
//
Language findLanguage(LanguageShared env) {
return sharedToLanguage.get(env);
}
Env findEnv(Class<? extends TruffleLanguage> languageClazz, boolean failIfNotFound) {
for (Language lang : languageArray) {
Env env = lang.getEnv(false);
if (env != null) {
assert lang.shared.language != null;
TruffleLanguage<?> spi = SPIAccessor.nodesAccess().getLanguageSpi(lang.shared.language);
if (languageClazz.isInstance(spi)) {
return env;
}
}
}
if (failIfNotFound) {
Set<String> languageNames = new HashSet<>();
for (Language lang : languageArray) {
languageNames.add(lang.shared.cache.getClassName());
}
throw new IllegalStateException("Cannot find language " + languageClazz + " among " + languageNames);
} else {
return null;
}
}
/**
* A future value wrapper. A user level wrapper around values returned by evaluation of various
* {@link PolyglotEngine} functions like {@link PolyglotEngine#findGlobalSymbol(String)} and
* {@link PolyglotEngine#eval(Source)} or a value returned by
* {@link #execute(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 Language language;
private CallTarget executeTarget;
private CallTarget asJavaObjectTarget;
Value(Language language) {
this.language = language;
}
abstract boolean isDirect();
abstract Object value();
@SuppressWarnings("unchecked")
private <T> T unwrapJava(Object value) {
CallTarget unwrapTarget = cachedTargets.lookupAsJava(value.getClass());
return (T) unwrapTarget.call(value, Object.class);
}
@SuppressWarnings("unchecked")
private <T> T asJavaObject(Class<T> type, Object value) {
if (asJavaObjectTarget == null) {
asJavaObjectTarget = cachedTargets.lookupAsJava(value == null ? void.class : value.getClass());
}
return (T) asJavaObjectTarget.call(value, type);
}
@SuppressWarnings("try")
private Object executeDirect(Object[] args) {
Object value = value();
if (executeTarget == null) {
executeTarget = cachedTargets.lookupExecute(value.getClass());
}
return executeTarget.call(value, args);
}
/**
* Returns the object represented by this value, possibly null. The <em>raw</em> object can
* either be a wrapped primitive type (e.g. {@link Number}, {@link String},
* {@link Character}, {@link Boolean}) or a {@link TruffleObject} representing more complex
* object created by a language.
*
* @return the object, possibly <code>null</code>
* @throws Exception in case it is not possible to obtain the value of the object
* @since 0.9
*/
public Object get() {
Object result = waitForSymbol();
if (result instanceof TruffleObject) {
result = unwrapJava(ConvertedObject.value(result));
if (result instanceof TruffleObject) {
result = EngineTruffleObject.wrap(PolyglotEngine.this, result);
}
}
return ConvertedObject.isNull(result) ? null : result;
}
/**
* Creates Java-typed foreign access to the object wrapped by this {@link Value Value}, a
* kind of cross-language "cast". Results depend on the requested type:
* <ul>
* <li>For primitive types such as {@link Number}, the value is simply cast and returned.
* </li>
* <li>A {@link String} is produced by the language that returned the value.</li>
* <li>A {@link FunctionalInterface} instance is returned if the value
* {@link Message#IS_EXECUTABLE can be executed}.</li>
* <li>Aggregate types such as {@link List} and {@link Map} are supported, including when
* used in combination with nested generics.</li>
* </ul>
* <p>
* This method is useful for Java applications that <em>interoperate</em> with guest
* language code. The general strategy is to {@linkplain PolyglotEngine#eval(Source)
* evaluate} guest language code that produces the desired language element and then use
* this method to create a Java object of the appropriate type for Java access to the
* result. The tutorial <a href=
* "{@docRoot}/com/oracle/truffle/tutorial/embedding/package-summary.html" >
* "Embedding Truffle Languages in Java"</a> contains examples.
*
* @param <T> the type of the requested view
* @param representation an interface describing the requested access (must be an interface)
* @return instance of the view wrapping the object of this value
* @throws Exception 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
*/
@SuppressWarnings("unchecked")
public <T> T as(final Class<T> representation) {
Object original = waitForSymbol();
Object unwrapped = original;
if (original instanceof TruffleObject) {
Object realOrig = ConvertedObject.original(original);
unwrapped = new ConvertedObject((TruffleObject) realOrig, unwrapJava(realOrig));
}
if (representation == String.class) {
Object unwrappedConverted = ConvertedObject.original(unwrapped);
Object string;
if (language != null) {
PolyglotEngine prev = enter();
try {
string = Access.LANGS.toStringIfVisible(language.getEnv(false), unwrappedConverted, false);
} finally {
leave(prev);
}
} else {
/* Language can be null for PolyglotEngine global config values. */
string = unwrappedConverted.toString();
}
return representation.cast(string);
}
if (ConvertedObject.isInstance(representation, unwrapped)) {
return ConvertedObject.cast(representation, unwrapped);
}
if (original instanceof TruffleObject) {
original = EngineTruffleObject.wrap(PolyglotEngine.this, original);
}
Object javaValue = asJavaObject(representation, original);
if (representation.isPrimitive()) {
return (T) javaValue;
} else {
return representation.cast(javaValue);
}
}
/**
* 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 <code>invoke</code> method returns.
*
* @param thiz this/self in language that support such concept; use <code>null</code> 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
* <code>null</code>
* @throws Exception signals problem during execution
* @since 0.9
*/
@Deprecated
public Value invoke(final Object thiz, final Object... args) {
return execute(args);
}
/**
* Executes this value, depending on its content.
* <ul>
*
* <li>If the value represents a function, makes a <em>foreign function call</em> using
* appropriate Java arguments.</li>
*
* <li>If the value represents a field, then sets the field to the value of the first
* argument, if provided, and returns the (possibly new) value of the field.</li>
* </ul>
* <p>
* This method is useful for Java applications that <em>interoperate</em> with guest
* language code. The general strategy is to {@linkplain PolyglotEngine#eval(Source)
* evaluate} guest language code that produces the desired language element. If that element
* is a guest language function, this method allows direct execution without giving the
* function a Java type. The tutorial <a href=
* "{@docRoot}/com/oracle/truffle/tutorial/embedding/package-summary.html" >
* "Embedding Truffle Languages in Java"</a> contains examples.
*
* @param args arguments to pass when executing the value
* @return result of the execution wrapped in a non-null {@link Value}
* @throws Exception signals problem during execution
* @since 0.9
*/
public Value execute(final Object... args) {
if (isDirect()) {
Object ret = executeDirect(args);
return new DirectValue(language, ret);
}
assertNoCompilation();
get();
ComputeInExecutor<Object> invokeCompute = new ComputeInExecutor<Object>(PolyglotEngine.this.executor()) {
@SuppressWarnings("try")
@Override
protected Object compute() {
return executeDirect(args);
}
};
invokeCompute.perform();
return new ExecutorValue(language, invokeCompute);
}
private Object waitForSymbol() {
assertNoCompilation();
assert PolyglotEngine.this.checkThread();
Object value = value();
assert value != null;
assert !(value instanceof EngineTruffleObject);
return value;
}
/**
* Get a meta-object of this value, if any. The meta-object represents a description of the
* value, reveals it's kind and it's features.
*
* @return a value representing the meta-object, or <code>null</code>
* @since 0.24
*/
public Value getMetaObject() {
if (language == null) {
return null;
}
ComputeInExecutor<Object> invokeCompute = new ComputeInExecutor<Object>(executor()) {
@SuppressWarnings("try")
@Override
protected Object compute() {
Object prev = enter();
try {
return Access.LANGS.findMetaObject(language.getEnv(true), ConvertedObject.original(value()));
} finally {
leave(prev);
}
}
};
Object value = invokeCompute.get();
if (value != null) {
return new DirectValue(language, value);
} else {
return null;
}
}
/**
* Get a source location where this value is declared, if any.
*
* @return a source location of the object, or <code>null</code>
* @since 0.24
*/
public SourceSection getSourceLocation() {
if (language == null) {
return null;
}
ComputeInExecutor<SourceSection> invokeCompute = new ComputeInExecutor<SourceSection>(executor()) {
@SuppressWarnings("try")
@Override
protected SourceSection compute() {
Object prev = enter();
try {
return Access.LANGS.findSourceLocation(language.getEnv(true), ConvertedObject.original(value()));
} finally {
leave(prev);
}
}
};
return invokeCompute.get();
}
}
private class DirectValue extends Value {
private final Object value;
DirectValue(Language language, Object value) {
super(language);
this.value = value;
assert value != null;
}
@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<Object> compute;
ExecutorValue(Language language, ComputeInExecutor<Object> compute) {
super(language);
this.compute = compute;
}
@Override
boolean isDirect() {
return false;
}
@Override
Object value() {
return compute.get();
}
@Override
public String toString() {
return "PolyglotEngine.Value[" + compute + "]";
}
}
/**
* A handle for an <em>instrument</em> installed in an engine, 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 engine.
* <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.
*
* @since 0.9
* @deprecated Use {@link PolyglotRuntime.Instrument}.
*/
@Deprecated
public final class Instrument extends PolyglotRuntime.Instrument {
Instrument(PolyglotRuntime runtime, InstrumentCache cache) {
runtime.super(cache);
}
}
/**
* A handle for a Truffle language installed in a {@link PolyglotEngine}. The handle provides
* access to the language's metadata, including the language's {@linkplain #getName() name},
* {@linkplain #getVersion() version}, and supported {@linkplain #getMimeTypes() MIME types}.
* <p>
* A Truffle language implementation is an extension of the abstract class
* {@link TruffleLanguage}, where more details about interactions between languages and engines
* can be found.
*
* @see PolyglotEngine#getLanguages()
* @since 0.9
*/
public class Language {
private volatile TruffleLanguage.Env env;
final LanguageShared shared;
@CompilationFinal volatile Object context = UNSET_CONTEXT;
private final Map<Source, CallTarget> parserCache;
Language(LanguageShared shared) {
this.shared = shared;
this.parserCache = new WeakHashMap<>();
}
PolyglotEngine engine() {
return PolyglotEngine.this;
}
Object context() {
return context;
}
/**
* Gets the MIME types supported by this language.
*
* @return an immutable set of supported MIME types
* @since 0.9
*/
public Set<String> getMimeTypes() {
return shared.cache.getMimeTypes();
}
/**
* Gets the human-readable name of this language.
*
* @return a human-friendly name
* @since 0.9
*/
public String getName() {
return shared.cache.getName();
}
/**
* Gets the version of this language.
*
* @return a version string
* @since 0.9
*/
public String getVersion() {
return shared.cache.getVersion();
}
/**
* Returns whether this language supports interactive evaluation of {@link Source sources}.
* Such languages should be displayed in interactive environments and presented to the user.
*
* @return <code>true</code> if and only if this language implements an interactive response
* to evaluation of interactive sources.
* @since 0.22
*/
public boolean isInteractive() {
return shared.cache.isInteractive();
}
/**
* Evaluates code using this language, ignoring the code's {@link Source#getMimeType() MIME
* type}.
* <p>
* When evaluating an {@link Source#isInteractive() interactive source} the result of the
* {@link com.oracle.truffle.api.vm.PolyglotEngine#eval evaluation} is
* {@link TruffleLanguage#isVisible(Object, Object) tested to be visible} and if the value
* is visible, it gets {@link TruffleLanguage#toString(Object, Object) converted to string}
* and printed to {@link com.oracle.truffle.api.vm.PolyglotEngine.Builder#setOut standard
* output}.
*
* @param source code to execute
* @return a non-null {@link Value} that holds the result
* @throws Exception thrown to signal errors while processing the code
* @since 0.9
*/
public Value eval(Source source) {
assertNoCompilation();
return evalImpl(this, source);
}
/**
* Returns this language's <em>global object</em>, {@code null} if not supported.
* <p>
* The result is expected to be a {@link TruffleObject} (e.g. a native object from the other
* language) but technically it can also be one of Java's primitive wrappers (
* {@link Integer} , {@link Double}, {@link Short}, etc.).
*
* @return this language's global object, <code>null</code> if the language has none
* @since 0.9
*/
@SuppressWarnings("try")
public Value getGlobalObject() {
assert checkThread();
ComputeInExecutor<Value> compute = new ComputeInExecutor<Value>(executor()) {
@Override
protected Value compute() {
Object prev = enter();
try {
Object res = Access.LANGS.languageGlobal(getEnv(true));
if (res == null) {
return null;
}
return new DirectValue(Language.this, res);
} finally {
leave(prev);
}
}
};
return compute.get();
}
void disposeContext() {
if (env != null) {
synchronized (this) {
Env localEnv = this.env;
assert localEnv != null;
if (localEnv != null) {
try {
Access.LANGS.dispose(localEnv);
} catch (Exception | Error ex) {
LOG.log(Level.SEVERE, "Error disposing " + this, ex);
}
this.env = null;
context = UNSET_CONTEXT;
}
}
}
}
TruffleLanguage.Env getEnv(boolean create) {
TruffleLanguage.Env localEnv = env;
if ((localEnv == null && create)) {
// getEnv is accessed from the instrumentation code so it needs to be
// thread-safe.
synchronized (this) {
localEnv = env;
if (localEnv == null && create) {
localEnv = Access.LANGS.createEnv(this, shared.getLanguageEnsureInitialized(), engine().out, engine().err, engine().in,
getArgumentsForLanguage());
context = Access.LANGS.getContext(localEnv);
env = localEnv;
Access.LANGS.postInitEnv(localEnv);
}
}
}
return localEnv;
}
/** @since 0.9 */
@Override
public String toString() {
return "[" + getName() + "@ " + getVersion() + " for " + getMimeTypes() + "]";
}
private Map<String, Object> getArgumentsForLanguage() {
if (config == null) {
return Collections.emptyMap();
}
Map<String, Object> forLanguage = new HashMap<>();
for (Object[] mimeKeyValue : config) {
if (shared.cache.getMimeTypes().contains(mimeKeyValue[0])) {
forLanguage.put((String) mimeKeyValue[1], mimeKeyValue[2]);
}
}
return Collections.unmodifiableMap(forLanguage);
}
} // end of Language
//
// Accessor helper methods
//
static class Access {
static final Accessor.LanguageSupport LANGS = SPIAccessor.langs();
static final Accessor.Nodes NODES = SPIAccessor.nodesAccess();
static final Accessor.InstrumentSupport INSTRUMENT = SPIAccessor.instrumentAccess();
static final Accessor.JavaInteropSupport JAVA_INTEROP = SPIAccessor.javaInteropAccess();
static Collection<ClassLoader> loaders() {
return SPI.allLoaders();
}
}
static class SPIAccessor extends Accessor {
static LanguageSupport langs() {
return SPI.languageSupport();
}
static EngineSupport engine() {
return EngineImpl.ENGINE;
}
static Nodes nodesAccess() {
return SPI.nodes();
}
static InstrumentSupport instrumentAccess() {
return SPI.instrumentSupport();
}
static JavaInteropSupport javaInteropAccess() {
return SPI.javaInteropSupport();
}
Collection<ClassLoader> allLoaders() {
return loaders();
}
@Override
protected EngineSupport engineSupport() {
return EngineImpl.ENGINE;
}
static final class EngineImpl extends EngineSupport {
static final EngineImpl ENGINE = new EngineImpl();
@Override
public boolean isDisposed(Object vmObject) {
return ((Language) vmObject).engine().disposed;
}
@Override
public Object contextReferenceGet(Object vmObject) {
return findVMObject(vmObject).getCurrentContext();
}
@Override
public Env getEnvForLanguage(Object vmObject, String mimeType) {
return ((Language) vmObject).engine().findLanguage(mimeType, true).getEnv(true);
}
@Override
public Env getEnvForInstrument(Object vmObject, String mimeType) {
PolyglotEngine currentVM = ((Instrument) vmObject).getRuntime().currentVM();
if (currentVM == null) {
throw new IllegalStateException("No current engine found.");
}
Language lang = currentVM.findLanguage(mimeType, true);
Env env = lang.getEnv(true);
assert env != null;
return env;
}
@Override
public <T> T lookup(InstrumentInfo info, Class<T> serviceClass) {
Object vmObject = SPIAccessor.langs().getVMObject(info);
Instrument instrument = (Instrument) vmObject;
return instrument.lookup(serviceClass);
}
@Override
public <S> S lookup(LanguageInfo language, Class<S> type) {
LanguageShared cache = (LanguageShared) SPIAccessor.nodesAccess().getEngineObject(language);
return SPIAccessor.langs().lookup(cache.getLanguageEnsureInitialized(), type);
}
@Override
public Map<String, LanguageInfo> getLanguages(Object vmObject) {
PolyglotRuntime vm;
if (vmObject instanceof Language) {
vm = ((Language) vmObject).shared.getRuntime();
} else if (vmObject instanceof Instrument) {
vm = ((Instrument) vmObject).getRuntime();
} else {
throw new AssertionError();
}
return vm.languageInfos;
}
@Override
public Map<String, InstrumentInfo> getInstruments(Object vmObject) {
PolyglotRuntime vm;
if (vmObject instanceof Language) {
vm = ((Language) vmObject).shared.getRuntime();
} else if (vmObject instanceof Instrument) {
vm = ((Instrument) vmObject).getRuntime();
} else {
throw new AssertionError();
}
return vm.instrumentInfos;
}
@Override
public Env getEnvForInstrument(LanguageInfo language) {
return ((LanguageShared) nodesAccess().getEngineObject(language)).currentLanguage().getEnv(true);
}
@Override
public Object getCurrentVM() {
return PolyglotEngine.GLOBAL_PROFILE.get();
}
@Override
public boolean isEvalRoot(RootNode target) {
if (target instanceof EvalRootNode) {
PolyglotEngine engine = ((EvalRootNode) target).getEngine();
return engine.isCurrentVM();
}
return false;
}
@Override
public boolean isMimeTypeSupported(Object vmObject, String mimeType) {
return ((Language) vmObject).engine().findLanguage(mimeType, false) != null;
}
@Override
public Env findEnv(Object vmObject, Class<? extends TruffleLanguage> languageClass, boolean failIfNotFound) {
return ((PolyglotEngine) vmObject).findEnv(languageClass, failIfNotFound);
}
@Override
public Object getInstrumentationHandler(Object vmObject) {
return ((LanguageShared) vmObject).getRuntime().instrumentationHandler;
}
@Override
public Iterable<? extends Object> importSymbols(Object languageShared, Env env, String globalName) {
Language language = (Language) languageShared;
return language.engine().importSymbol(language, globalName, false);
}
@SuppressWarnings("deprecation")
@Override
public <C> com.oracle.truffle.api.impl.FindContextNode<C> createFindContextNode(TruffleLanguage<C> lang) {
Object vm = getCurrentVM();
if (vm == null) {
throw new IllegalStateException("Cannot access current vm.");
}
return new FindContextNodeImpl<>(findEnv(vm, lang.getClass(), true));
}
@Override
public void registerDebugger(Object vm, Object debugger) {
PolyglotEngine engine = (PolyglotEngine) vm;
assert engine.debugger()[0] == null || engine.debugger()[0] == debugger;
engine.debugger()[0] = debugger;
}
@Override
public Object findOriginalObject(Object truffleObject) {
if (truffleObject instanceof EngineTruffleObject) {
return ((EngineTruffleObject) truffleObject).getDelegate();
}
return truffleObject;
}
@Override
public CallTarget lookupOrRegisterComputation(Object truffleObject, RootNode computation, Object... keys) {
CompilerAsserts.neverPartOfCompilation();
assert keys.length > 0;
Object key;
if (keys.length == 1) {
key = keys[0];
assert TruffleOptions.AOT || assertKeyType(key);
} else {
Pair p = null;
for (Object k : keys) {
assert TruffleOptions.AOT || assertKeyType(k);
p = new Pair(k, p);
}
key = p;
}
if (truffleObject instanceof EngineTruffleObject) {
PolyglotEngine engine = ((EngineTruffleObject) truffleObject).engine();
return engine.cachedTargets.lookupComputation(key, computation);
}
if (computation == null) {
return null;
}
return Truffle.getRuntime().createCallTarget(computation);
}
private static boolean assertKeyType(Object key) {
assert key instanceof Class || key instanceof Method || key instanceof Message : "Unexpected key: " + key;
return true;
}
private static LanguageShared findVMObject(Object obj) {
return ((LanguageShared) obj);
}
}
private static final class Pair {
final Object key;
final Pair next;
Pair(Object key, Pair next) {
this.key = key;
this.next = next;
}
@Override
public int hashCode() {
return this.key.hashCode() + (next == null ? 3754 : next.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Pair other = (Pair) obj;
if (!Objects.equals(this.key, other.key)) {
return false;
}
if (!Objects.equals(this.next, other.next)) {
return false;
}
return true;
}
}
} // end of SPIAccessor
}
class PolyglotEngineSnippets {
abstract class YourLang extends TruffleLanguage<Object> {
public static final String MIME_TYPE = "application/my-test-lang";
}
public static PolyglotEngine defaultPolyglotEngine() {
// @formatter:off
// BEGIN: PolyglotEngineSnippets#defaultPolyglotEngine
PolyglotEngine engine = PolyglotEngine.newBuilder().build();
// END: PolyglotEngineSnippets#defaultPolyglotEngine
// @formatter:on
return engine;
}
public static PolyglotEngine createPolyglotEngine(OutputStream yourOutput, InputStream yourInput) {
// @formatter:off
// BEGIN: PolyglotEngineSnippets#createPolyglotEngine
PolyglotEngine engine = PolyglotEngine.newBuilder().
setOut(yourOutput).
setErr(yourOutput).
setIn(yourInput).
build();
// END: PolyglotEngineSnippets#createPolyglotEngine
// @formatter:on
return engine;
}
public static int evalCode() {
// @formatter:off
// BEGIN: com.oracle.truffle.api.vm.PolyglotEngineSnippets#evalCode
Source src = Source.newBuilder("3 + 39").
mimeType("application/my-test-lang").
name("example.test-lang").
build();
PolyglotEngine engine = PolyglotEngine.newBuilder().build();
Value result = engine.eval(src);
int answer = result.as(Integer.class);
// END: com.oracle.truffle.api.vm.PolyglotEngineSnippets#evalCode
// @formatter:on
return answer;
}
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 engine = builder.build();
// END: com.oracle.truffle.api.vm.PolyglotEngineSnippets#initializeWithParameters
// @formatter:on
return engine;
}
// @formatter:off
// BEGIN: com.oracle.truffle.api.vm.PolyglotEngineSnippets#configureJavaInterop
public static final class Multiplier {
public static int mul(int x, int y) {
return x * y;
}
}
public interface Multiply {
int mul(int x, int y);
}
public static PolyglotEngine configureJavaInterop(Multiply multiply) {
TruffleObject staticAccess = JavaInterop.asTruffleObject(Multiplier.class);
TruffleObject instanceAccess = JavaInterop.asTruffleObject(multiply);
PolyglotEngine engine = PolyglotEngine.newBuilder().
globalSymbol("mul", staticAccess).
globalSymbol("compose", instanceAccess).
build();
return engine;
}
// END: com.oracle.truffle.api.vm.PolyglotEngineSnippets#configureJavaInterop
// @formatter:on
static PolyglotEngine configureJavaInteropWithMul() {
PolyglotEngineSnippets.Multiply multi = new PolyglotEngineSnippets.Multiply() {
@Override
public int mul(int x, int y) {
return x * y;
}
};
return configureJavaInterop(multi);
}
// @formatter:off
// BEGIN: com.oracle.truffle.api.vm.PolyglotEngineSnippets#findAndReportMultipleExportedSymbols
static Value findAndReportMultipleExportedSymbols(
PolyglotEngine engine, String name) {
Value found = null;
for (Value value : engine.findGlobalSymbols(name)) {
if (found != null) {
throw new IllegalStateException(
"Multiple global symbols exported with " + name + " name"
);
}
found = value;
}
return found;
}
// END: com.oracle.truffle.api.vm.PolyglotEngineSnippets#findAndReportMultipleExportedSymbols
// @formatter:on
static PrintStream out = System.out;
static PrintStream err = System.err;
// @formatter:off
@SuppressWarnings("unused")
static void createEngines(String mimeType) {
// BEGIN: com.oracle.truffle.api.vm.PolyglotEngineSnippets#createEngines
PolyglotRuntime runtime = PolyglotRuntime.newBuilder().
setOut(out).setErr(err).build();
Builder builder = PolyglotEngine.newBuilder().
runtime(runtime);
PolyglotEngine engine1 = builder.build();
PolyglotEngine engine2 = builder.build();
PolyglotEngine engine3 = builder.build();
// END: com.oracle.truffle.api.vm.PolyglotEngineSnippets#createEngines
}
// @formatter:on
}