
com.oracle.truffle.api.vm.PolyglotEngine Maven / Gradle / Ivy
/*
* Copyright (c) 2014, 2015, 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.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import com.oracle.truffle.api.CallTarget;
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.debug.Debugger;
import com.oracle.truffle.api.debug.ExecutionEvent;
import com.oracle.truffle.api.debug.SuspendedEvent;
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.instrument.Instrumenter;
import com.oracle.truffle.api.instrument.Probe;
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;
import java.util.WeakHashMap;
import java.util.logging.Level;
/**
* 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}.
*
* Use {@link #buildNew()} to create new isolated portal ready for execution of various languages.
* All the languages in a single portal see each other exported global symbols and can cooperate.
* Use {@link #buildNew()} multiple times to create different, isolated portal environment
* 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 isn't garbage collected.
*
* 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.
*/
@SuppressWarnings("rawtypes")
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 Instrumenter instrumenter;
private final Debugger debugger;
private boolean disposed;
/**
* Private & temporary only constructor.
*/
PolyglotEngine() {
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.debugger = null;
}
/**
* Real constructor used from the builder.
*/
PolyglotEngine(Executor executor, Map globals, OutputStream out, OutputStream err, InputStream in, EventConsumer>[] handlers) {
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 = SPI.createInstrumenter(this);
this.debugger = SPI.createDebugger(this, this.instrumenter);
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;
}
/**
* 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
*/
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()}
*/
@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()};
*
*/
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;
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
*/
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
*/
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
*/
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
*/
public Builder onEvent(EventConsumer> handler) {
handler.getClass();
handlers.add(handler);
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}
*/
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
*/
@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
*/
public PolyglotEngine build() {
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]));
}
}
/**
* 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
*/
public Map getLanguages() {
return Collections.unmodifiableMap(langs);
}
/**
* 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
*/
public Value eval(Source source) throws IOException {
String mimeType = source.getMimeType();
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}.
*/
public void dispose() {
checkThread();
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 {
SPI.dispose(impl, language.getEnv(true));
} catch (Exception | Error ex) {
LOG.log(Level.SEVERE, "Error disposing " + impl, 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};
ComputeInExecutor