diff truffle/com.oracle.truffle.api.vm/src/com/oracle/truffle/api/vm/PolyglotEngine.java @ 22170:796f0fef110e

Renaming to PolyglotEngine
author Jaroslav Tulach <jaroslav.tulach@oracle.com>
date Mon, 21 Sep 2015 10:34:30 +0200
parents truffle/com.oracle.truffle.api.vm/src/com/oracle/truffle/api/vm/Portaal.java@307a4ed5430e
children 41c5f430ce37
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.api.vm/src/com/oracle/truffle/api/vm/PolyglotEngine.java	Mon Sep 21 10:34:30 2015 +0200
@@ -0,0 +1,872 @@
+/*
+ * 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 com.oracle.truffle.api.*;
+import com.oracle.truffle.api.TruffleLanguage.Env;
+import com.oracle.truffle.api.TruffleLanguage.Registration;
+import com.oracle.truffle.api.debug.*;
+import com.oracle.truffle.api.impl.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.interop.java.JavaInterop;
+import com.oracle.truffle.api.source.*;
+import java.io.*;
+import java.net.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.logging.*;
+
+/**
+ * Gate way into the world of {@link TruffleLanguage Truffle languages}. {@link #createNew()
+ * 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 <em>JVM</em>, there can however
+ * be multiple instances (some would say tenants) of {@link PolyglotEngine} running next to each other in a
+ * single <em>JVM</em> with a complete mutual isolation. There is 1:N mapping between <em>JVM</em>
+ * and {@link PolyglotEngine}.
+ * <p>
+ * 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 <em>JVM</em> 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}.
+ * <p>
+ * Use {@link #createNew()} 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 #createNew()} multiple times to create different, isolated portal environment
+ * completely separated from each other.
+ * <p>
+ * Once instantiated use {@link #eval(java.net.URI)} with a reference to a file or URL or directly
+ * pass code snippet into the virtual machine via {@link #eval(java.lang.String, java.lang.String)}.
+ * 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.
+ * <p>
+ * The <code>TruffleVM</code> 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 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<String, Language> langs;
+    private final Reader in;
+    private final Writer err;
+    private final Writer out;
+    private final EventConsumer<?>[] handlers;
+    private final Map<String, Object> globals;
+    private Debugger debugger;
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Real constructor used from the builder.
+     */
+    PolyglotEngine(Executor executor, Map<String, Object> globals, Writer out, Writer err, Reader 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);
+        Map<String, Language> map = new HashMap<>();
+        for (Map.Entry<String, LanguageCache> en : LanguageCache.languages().entrySet()) {
+            map.put(en.getKey(), createLanguage(en));
+        }
+        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()}:
+     *
+     * <pre>
+     * {@link PolyglotEngine} vm = {@link PolyglotEngine}.{@link PolyglotEngine#createNew() createNew()}
+     *     .{@link Builder#stdOut(java.io.Writer) stdOut}({@link Writer yourWriter})
+     *     .{@link Builder#stdErr(java.io.Writer) stdErr}({@link Writer yourWriter})
+     *     .{@link Builder#stdIn(java.io.Reader) stdIn}({@link Reader yourReader})
+     *     .{@link Builder#build() build()};
+     * </pre>
+     *
+     * It searches for {@link Registration languages registered} in the system class loader and
+     * makes them available for later evaluation via
+     * {@link #eval(java.lang.String, java.lang.String)} methods.
+     *
+     * @return new, isolated virtual machine with pre-registered languages
+     */
+    public static PolyglotEngine.Builder createNew() {
+        // making Builder non-static inner class is a
+        // nasty trick to avoid the Builder class to appear
+        // in Javadoc next to TruffleVM class
+        PolyglotEngine vm = new PolyglotEngine();
+        return vm.new Builder();
+    }
+
+    /**
+     * Builder for a new {@link PolyglotEngine}. Call various configuration methods in a chain and at the
+     * end create new {@link PolyglotEngine virtual machine}:
+     *
+     * <pre>
+     * {@link PolyglotEngine} vm = {@link PolyglotEngine}.{@link PolyglotEngine#createNew() createNew()}
+     *     .{@link Builder#stdOut(java.io.Writer) stdOut}({@link Writer yourWriter})
+     *     .{@link Builder#stdErr(java.io.Writer) stdErr}({@link Writer yourWriter})
+     *     .{@link Builder#stdIn(java.io.Reader) stdIn}({@link Reader yourReader})
+     *     .{@link Builder#build() build()};
+     * </pre>
+     */
+    public class Builder {
+        private Writer out;
+        private Writer err;
+        private Reader in;
+        private final List<EventConsumer<?>> handlers = new ArrayList<>();
+        private final Map<String, Object> globals = new HashMap<>();
+        private Executor executor;
+
+        Builder() {
+        }
+
+        /**
+         * Changes the default output for languages running in <em>to be created</em>
+         * {@link PolyglotEngine virtual machine}. The default is to use {@link System#out}.
+         *
+         * @param w the writer to use as output
+         * @return instance of this builder
+         */
+        public Builder stdOut(Writer w) {
+            out = w;
+            return this;
+        }
+
+        /**
+         * Changes the error output for languages running in <em>to be created</em> {@link PolyglotEngine
+         * virtual machine}. The default is to use {@link System#err}.
+         *
+         * @param w the writer to use as output
+         * @return instance of this builder
+         */
+        public Builder stdErr(Writer w) {
+            err = w;
+            return this;
+        }
+
+        /**
+         * Changes the default input for languages running in <em>to be created</em> {@link PolyglotEngine
+         * virtual machine}. The default is to use {@link System#out}.
+         *
+         * @param r the reader to use as input
+         * @return instance of this builder
+         */
+        public Builder stdIn(Reader r) {
+            in = r;
+            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 <code>globalSymbol</code> 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
+         *            <code>TruffleObject</code> for mutual inter-operability
+         * @return instance of this builder
+         * @see PolyglotEngine#findGlobalSymbol(java.lang.String)
+         */
+        public Builder globalSymbol(String name, Object obj) {
+            globals.put(name, obj);
+            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
+         */
+        @SuppressWarnings("deprecation")
+        public PolyglotEngine build() {
+            if (out == null) {
+                out = new OutputStreamWriter(System.out);
+            }
+            if (err == null) {
+                err = new OutputStreamWriter(System.err);
+            }
+            if (in == null) {
+                in = new InputStreamReader(System.in);
+            }
+            Executor nonNullExecutor = executor != null ? executor : new Executor() {
+                @Override
+                public void execute(Runnable command) {
+                    command.run();
+                }
+            };
+            return new TruffleVM(nonNullExecutor, 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<String, ? extends Language> getLanguages() {
+        return Collections.unmodifiableMap(langs);
+    }
+
+    /**
+     * Evaluates file located on a given URL. Is equivalent to loading the content of a file and
+     * executing it via {@link #eval(java.lang.String, java.lang.String)} with a MIME type guess
+     * based on the file's extension and/or content.
+     *
+     * @param location the location of a file to execute
+     * @return result of a processing the file, possibly <code>null</code>
+     * @throws IOException exception to signal I/O problems or problems with processing the file's
+     *             content
+     * @deprecated use {@link #eval(com.oracle.truffle.api.source.Source)}
+     */
+    @Deprecated
+    public Object eval(URI location) throws IOException {
+        checkThread();
+        Source s;
+        String mimeType;
+        if (location.getScheme().equals("file")) {
+            File file = new File(location);
+            s = Source.fromFileName(file.getPath(), true);
+            if (file.getName().endsWith(".c")) {
+                mimeType = "text/x-c";
+            } else if (file.getName().endsWith(".sl")) {
+                mimeType = "application/x-sl";
+            } else if (file.getName().endsWith(".R") || file.getName().endsWith(".r")) {
+                mimeType = "application/x-r";
+            } else {
+                mimeType = Files.probeContentType(file.toPath());
+            }
+        } else {
+            URL url = location.toURL();
+            s = Source.fromURL(url, location.toString());
+            URLConnection conn = url.openConnection();
+            mimeType = conn.getContentType();
+        }
+        Language l = langs.get(mimeType);
+        if (l == null) {
+            throw new IOException("No language for " + location + " with MIME type " + mimeType + " found. Supported types: " + langs.keySet());
+        }
+        return eval(l, s).get();
+    }
+
+    /**
+     * Evaluates code snippet. Chooses a language registered for a given MIME type (throws
+     * {@link IOException} if there is none). And passes the specified code to it for execution.
+     *
+     * @param mimeType MIME type of the code snippet - chooses the right language
+     * @param reader the source of code snippet to execute
+     * @return result of an execution, possibly <code>null</code>
+     * @throws IOException thrown to signal errors while processing the code
+     * @deprecated use {@link #eval(com.oracle.truffle.api.source.Source)}
+     */
+    @Deprecated
+    public Object eval(String mimeType, Reader reader) throws IOException {
+        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.fromReader(reader, mimeType)).get();
+    }
+
+    /**
+     * Evaluates code snippet. Chooses a language registered for a given MIME type (throws
+     * {@link IOException} if there is none). And passes the specified code to it for execution.
+     *
+     * @param mimeType MIME type of the code snippet - chooses the right language
+     * @param code the code snippet to execute
+     * @return result of an execution, possibly <code>null</code>
+     * @throws IOException thrown to signal errors while processing the code
+     * @deprecated use {@link #eval(com.oracle.truffle.api.source.Source)}
+     */
+    @Deprecated
+    public Object eval(String mimeType, String code) throws IOException {
+        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.fromText(code, mimeType)).get();
+    }
+
+    /**
+     * 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 <code>null</code>
+     * @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);
+    }
+
+    private Value eval(final Language l, final Source s) throws IOException {
+        final Debugger[] fillIn = {debugger};
+        final Object[] result = {null, null};
+        final CountDownLatch ready = new CountDownLatch(1);
+        final TruffleLanguage[] lang = {null};
+        executor.execute(new Runnable() {
+            @Override
+            public void run() {
+                evalImpl(fillIn, lang, s, result, l, ready);
+            }
+        });
+        exceptionCheck(result);
+        return createValue(lang[0], result, ready);
+    }
+
+    Value createValue(TruffleLanguage lang, Object[] result, CountDownLatch ready) {
+        return new Value(lang, result, ready);
+    }
+    Language createLanguage(Map.Entry<String, LanguageCache> en) {
+        return new Language(en.getValue());
+    }
+
+
+    @SuppressWarnings("try")
+    private void evalImpl(Debugger[] fillIn, TruffleLanguage<?>[] fillLang, Source s, Object[] result, Language l, CountDownLatch ready) {
+        try (Closeable d = SPI.executionStart(this, fillIn, s)) {
+            TruffleLanguage<?> langImpl = l.getImpl(true);
+            fillLang[0] = langImpl;
+            PolyglotEngine.findDebuggerSupport(langImpl);
+            if (debugger == null) {
+                debugger = fillIn[0];
+            }
+            result[0] = SPI.eval(langImpl, s);
+        } catch (IOException ex) {
+            result[1] = ex;
+        } finally {
+            ready.countDown();
+        }
+    }
+
+    /**
+     * Looks global symbol provided by one of initialized languages up. First of all execute your
+     * program via one of your {@link #eval(java.lang.String, java.lang.String)} and then look
+     * expected symbol up using this method.
+     * <p>
+     * The names of the symbols are language dependent, but for example the Java language bindings
+     * follow the specification for method references:
+     * <ul>
+     * <li>"java.lang.Exception::new" is a reference to constructor of {@link Exception}
+     * <li>"java.lang.Integer::valueOf" is a reference to static method in {@link Integer} class
+     * </ul>
+     * 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 <code>null</code> if it has not been found
+     */
+    public Value findGlobalSymbol(final String globalName) {
+        checkThread();
+        final TruffleLanguage<?>[] lang = {null};
+        final Object[] obj = {globals.get(globalName), null};
+        final CountDownLatch ready = new CountDownLatch(1);
+        if (obj[0] == null) {
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    findGlobalSymbolImpl(obj, globalName, lang, ready);
+                }
+            });
+            try {
+                ready.await();
+            } catch (InterruptedException ex) {
+                LOG.log(Level.SEVERE, null, ex);
+            }
+        } else {
+            ready.countDown();
+        }
+        return obj[0] == null ? null : createValue(lang[0], obj, ready);
+    }
+
+    private void findGlobalSymbolImpl(Object[] obj, String globalName, TruffleLanguage<?>[] lang, CountDownLatch ready) {
+        if (obj[0] == null) {
+            for (Language dl : langs.values()) {
+                TruffleLanguage.Env env = dl.getEnv(false);
+                if (env == null) {
+                    continue;
+                }
+                obj[0] = SPI.findExportedSymbol(env, globalName, true);
+                if (obj[0] != null) {
+                    lang[0] = dl.getImpl(true);
+                    break;
+                }
+            }
+        }
+        if (obj[0] == null) {
+            for (Language dl : langs.values()) {
+                TruffleLanguage.Env env = dl.getEnv(false);
+                if (env == null) {
+                    continue;
+                }
+                obj[0] = SPI.findExportedSymbol(env, globalName, true);
+                if (obj[0] != null) {
+                    lang[0] = dl.getImpl(true);
+                    break;
+                }
+            }
+        }
+        ready.countDown();
+    }
+
+    private void checkThread() {
+        if (initThread != Thread.currentThread()) {
+            throw new IllegalStateException("TruffleVM created on " + initThread.getName() + " but used on " + Thread.currentThread().getName());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    void dispatch(Object ev) {
+        Class type = ev.getClass();
+        if (type == SuspendedEvent.class) {
+            dispatchSuspendedEvent((SuspendedEvent) ev);
+        }
+        if (type == ExecutionEvent.class) {
+            dispatchExecutionEvent((ExecutionEvent) ev);
+        }
+        dispatch(type, ev);
+    }
+
+    @SuppressWarnings("unused")
+    void dispatchSuspendedEvent(SuspendedEvent event) {
+    }
+
+    @SuppressWarnings("unused")
+    void dispatchExecutionEvent(ExecutionEvent event) {
+    }
+
+    @SuppressWarnings("unchecked")
+    <Event> void dispatch(Class<Event> type, Event event) {
+        for (EventConsumer handler : handlers) {
+            if (handler.type == type) {
+                handler.on(event);
+            }
+        }
+    }
+
+    static void exceptionCheck(Object[] result) throws RuntimeException, IOException {
+        if (result[1] instanceof IOException) {
+            throw (IOException) result[1];
+        }
+        if (result[1] instanceof RuntimeException) {
+            throw (RuntimeException) result[1];
+        }
+    }
+
+    /**
+     * 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 value returned by
+     * {@link #invoke(java.lang.Object, java.lang.Object...) sbbsequent of execution}. In case the
+     * {@link PolyglotEngine} has been initialized for
+     * {@link Builder#executor(java.util.concurrent.Executor) asynchronous excution}, the
+     * {@link Value} represents a future - e.g. it is returned immediately, leaving the execution
+     * running on behind.
+     */
+    public class Value {
+        private final TruffleLanguage<?> language;
+        private final Object[] result;
+        private final CountDownLatch ready;
+        private CallTarget target;
+
+        Value(TruffleLanguage<?> language, Object[] result, CountDownLatch ready) {
+            this.language = language;
+            this.result = result;
+            this.ready = ready;
+        }
+
+        /**
+         * Obtains the object represented by this symbol. The <em>raw</em> object can either be a
+         * wrapper about primitive type (e.g. {@link Number}, {@link String}, {@link Character},
+         * {@link Boolean}) or a <em>TruffleObject</em> representing more complex object from a
+         * language. The method can return <code>null</code>.
+         *
+         * @return the object or <code>null</code>
+         * @throws IOException in case it is not possible to obtain the value of the object
+         */
+        public Object get() throws IOException {
+            waitForSymbol();
+            exceptionCheck(result);
+            return result[0];
+        }
+
+        /**
+         * 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)}
+         * just handles primitive types as well.
+         * 
+         * @param <T> 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
+         */
+        public <T> T as(Class<T> representation) throws IOException {
+            Object obj = get();
+            if (representation.isInstance(obj)) {
+                return representation.cast(obj);
+            }
+            return JavaInterop.asJavaObject(representation, (TruffleObject) obj);
+        }
+
+        /**
+         * 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 IOException signals problem during execution
+         */
+        public Value invoke(final Object thiz, final Object... args) throws IOException {
+            get();
+            final Debugger[] fillIn = {debugger};
+            final CountDownLatch done = new CountDownLatch(1);
+            final Object[] res = {null, null};
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    invokeImpl(fillIn, thiz, args, res, done);
+                }
+            });
+            exceptionCheck(res);
+            return createValue(language, res, done);
+        }
+
+        @SuppressWarnings("try")
+        private void invokeImpl(Debugger[] fillIn, Object thiz, Object[] args, Object[] res, CountDownLatch done) {
+            try (final Closeable c = SPI.executionStart(PolyglotEngine.this, fillIn, null)) {
+                if (debugger == null) {
+                    debugger = fillIn[0];
+                }
+                List<Object> arr = new ArrayList<>();
+                if (thiz == null && language != null) {
+                    Object global = SPI.languageGlobal(SPI.findLanguage(PolyglotEngine.this, language.getClass()));
+                    if (global != null) {
+                        arr.add(global);
+                    }
+                } else {
+                    arr.add(thiz);
+                }
+                arr.addAll(Arrays.asList(args));
+                for (;;) {
+                    try {
+                        if (target == null) {
+                            target = SymbolInvokerImpl.createCallTarget(language, result[0], arr.toArray());
+                        }
+                        res[0] = target.call(arr.toArray());
+                        break;
+                    } catch (ArgumentsMishmashException ex) {
+                        target = null;
+                    }
+                }
+            } catch (IOException ex) {
+                res[1] = ex;
+            } catch (RuntimeException ex) {
+                res[1] = ex;
+            } finally {
+                done.countDown();
+            }
+        }
+
+        private void waitForSymbol() throws InterruptedIOException {
+            checkThread();
+            try {
+                ready.await();
+            } catch (InterruptedException ex) {
+                throw (InterruptedIOException) new InterruptedIOException(ex.getMessage()).initCause(ex);
+            }
+        }
+    }
+
+    /**
+     * 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(java.lang.String, java.lang.String) a code is evaluated} in it.
+     */
+    public class Language {
+        private final LanguageCache info;
+        private TruffleLanguage.Env env;
+
+        Language(LanguageCache info) {
+            this.info = info;
+        }
+
+        /**
+         * MIME types recognized by the language.
+         *
+         * @return returns immutable set of recognized MIME types
+         */
+        public Set<String> getMimeTypes() {
+            return info.getMimeTypes();
+        }
+
+        /**
+         * Human readable name of the language. Think of C, Ruby, JS, etc.
+         *
+         * @return string giving the language a name
+         */
+        public String getName() {
+            return info.getName();
+        }
+
+        /**
+         * Name of the language version.
+         *
+         * @return string specifying the language version
+         */
+        public String getVersion() {
+            return info.getVersion();
+        }
+
+        /**
+         * Human readable string that identifies the language and version.
+         *
+         * @return string describing the specific language version
+         */
+        public String getShortName() {
+            return getName() + "(" + getVersion() + ")";
+        }
+
+        TruffleLanguage<?> getImpl(boolean create) {
+            getEnv(create);
+            return info.getImpl(false);
+        }
+
+        TruffleLanguage.Env getEnv(boolean create) {
+            if (env == null && create) {
+                env = SPI.attachEnv(PolyglotEngine.this, info.getImpl(true), out, err, in);
+            }
+            return env;
+        }
+
+        @Override
+        public String toString() {
+            return "[" + getShortName() + " for " + getMimeTypes() + "]";
+        }
+    } // end of Language
+
+    //
+    // Accessor helper methods
+    //
+
+    TruffleLanguage<?> findLanguage(Probe probe) {
+        Class<? extends TruffleLanguage> languageClazz = SPI.findLanguage(probe);
+        for (Map.Entry<String, Language> entrySet : langs.entrySet()) {
+            Language languageDescription = entrySet.getValue();
+            final TruffleLanguage<?> impl = languageDescription.getImpl(false);
+            if (languageClazz.isInstance(impl)) {
+                return impl;
+            }
+        }
+        throw new IllegalStateException("Cannot find language " + languageClazz + " among " + langs);
+    }
+
+    Env findEnv(Class<? extends TruffleLanguage> languageClazz) {
+        for (Map.Entry<String, Language> 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 DebugSupportProvider findDebuggerSupport(TruffleLanguage<?> l) {
+        return SPI.getDebugSupport(l);
+    }
+
+    private static class SPIAccessor extends Accessor {
+        @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<Language> 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 = SPI.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 = SPI.findExportedSymbol(env, globalName, false);
+                if (obj != null) {
+                    return obj;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public Env attachEnv(Object obj, TruffleLanguage<?> language, Writer stdOut, Writer stdErr, Reader stdIn) {
+            PolyglotEngine vm = (PolyglotEngine) obj;
+            return super.attachEnv(vm, language, stdOut, stdErr, stdIn);
+        }
+
+        @Override
+        public Object eval(TruffleLanguage<?> l, Source s) throws IOException {
+            return super.eval(l, s);
+        }
+
+        @Override
+        public Object findExportedSymbol(TruffleLanguage.Env env, String globalName, boolean onlyExplicit) {
+            return super.findExportedSymbol(env, globalName, onlyExplicit);
+        }
+
+        @Override
+        protected Object languageGlobal(TruffleLanguage.Env env) {
+            return super.languageGlobal(env);
+        }
+
+        @Override
+        public ToolSupportProvider getToolSupport(TruffleLanguage<?> l) {
+            return super.getToolSupport(l);
+        }
+
+        @Override
+        public DebugSupportProvider getDebugSupport(TruffleLanguage<?> l) {
+            return super.getDebugSupport(l);
+        }
+
+        @Override
+        protected Class<? extends TruffleLanguage> findLanguage(Probe probe) {
+            return super.findLanguage(probe);
+        }
+
+        @Override
+        protected Env findLanguage(Object obj, Class<? extends TruffleLanguage> languageClass) {
+            PolyglotEngine vm = (PolyglotEngine) obj;
+            return vm.findEnv(languageClass);
+        }
+
+        @Override
+        protected Closeable executionStart(Object obj, Debugger[] fillIn, Source s) {
+            PolyglotEngine vm = (PolyglotEngine) obj;
+            return super.executionStart(vm, fillIn, s);
+        }
+
+        @Override
+        protected void dispatchEvent(Object obj, Object event) {
+            PolyglotEngine vm = (PolyglotEngine) obj;
+            vm.dispatch(event);
+        }
+    } // end of SPIAccessor
+}