Mercurial > hg > truffle
diff graal/com.oracle.truffle.api/src/com/oracle/truffle/api/vm/TruffleVM.java @ 21468:99942eac9c6d
Introducing TruffleVM - a central place to invoke code in any registered TruffleLanguage.
author | Jaroslav Tulach <jaroslav.tulach@oracle.com> |
---|---|
date | Fri, 22 May 2015 13:41:10 +0200 |
parents | |
children | 2dad34a3d7b0 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/vm/TruffleVM.java Fri May 22 13:41:10 2015 +0200 @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2014, 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.TruffleLanguage; +import com.oracle.truffle.api.TruffleLanguage.Registration; +import com.oracle.truffle.api.TruffleLanguage.Env; +import com.oracle.truffle.api.impl.Accessor; +import com.oracle.truffle.api.source.Source; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Virtual machine for Truffle based languages. Use {@link #create()} to instantiate new isolated + * virtual machine ready for execution of various languages. All the languages in a single virtual + * machine see each other exported global symbols and can co-operate. Use {@link #create()} multiple + * times to create different, isolated virtual machines 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 #create() created} by and checks that all subsequent calls are coming from the + * same thread. + */ +public final class TruffleVM { + private static final Logger LOG = Logger.getLogger(TruffleVM.class.getName()); + private static final SPIAccessor SPI = new SPIAccessor(); + private final Thread initThread; + private final Map<String, Language> langs; + + private TruffleVM() { + initThread = Thread.currentThread(); + this.langs = new HashMap<>(); + Enumeration<URL> en; + try { + en = loader().getResources("META-INF/truffle/language"); + } catch (IOException ex) { + throw new IllegalStateException("Cannot read list of Truffle languages", ex); + } + while (en.hasMoreElements()) { + URL u = en.nextElement(); + Properties p; + try { + p = new Properties(); + try (InputStream is = u.openStream()) { + p.load(is); + } + } catch (IOException ex) { + LOG.log(Level.CONFIG, "Cannot process " + u + " as language definition", ex); + continue; + } + Language l = new Language(p); + for (String mimeType : l.getMimeTypes()) { + langs.put(mimeType, l); + } + } + } + + static ClassLoader loader() { + ClassLoader l = TruffleVM.class.getClassLoader(); + if (l == null) { + l = ClassLoader.getSystemClassLoader(); + } + return l; + } + + /** + * Creates new Truffle virtual machine. 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 TruffleVM create() { + return new TruffleVM(); + } + + /** + * Descriptions of languages supported in this Truffle virtual machine. + * + * @return an immutable map with keys being mimetypes and values the {@link Language + * descriptions} of associated languages + */ + public Map<String, 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 + */ + 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); + mimeType = file.getName().endsWith(".c") ? "text/x-c" : Files.probeContentType(file.toPath()); + } else { + URL url = location.toURL(); + s = Source.fromURL(url, location.toString()); + URLConnection conn = url.openConnection(); + mimeType = conn.getContentType(); + } + TruffleLanguage l = getTruffleLang(mimeType); + if (l == null) { + throw new IOException("No language for " + location + " with mime type " + mimeType + " found. Supported types: " + langs.keySet()); + } + return SPI.eval(l, s); + } + + /** + * 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 exceution, possibly <code>null</code> + * @throws IOException thrown to signal errors while processing the code + */ + public Object eval(String mimeType, Reader reader) throws IOException { + checkThread(); + TruffleLanguage l = getTruffleLang(mimeType); + if (l == null) { + throw new IOException("No language for mime type " + mimeType + " found. Supported types: " + langs.keySet()); + } + return SPI.eval(l, Source.fromReader(reader, mimeType)); + } + + /** + * 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 exceution, possibly <code>null</code> + * @throws IOException thrown to signal errors while processing the code + */ + public Object eval(String mimeType, String code) throws IOException { + checkThread(); + TruffleLanguage l = getTruffleLang(mimeType); + if (l == null) { + throw new IOException("No language for mime type " + mimeType + " found. Supported types: " + langs.keySet()); + } + return SPI.eval(l, Source.fromText(code, mimeType)); + } + + /** + * 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 dependant, 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 acces 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 Symbol findGlobalSymbol(String globalName) { + checkThread(); + Object obj = null; + Object global = null; + for (Language dl : langs.values()) { + TruffleLanguage l = dl.getImpl(); + obj = SPI.findExportedSymbol(l, globalName); + if (obj != null) { + global = SPI.languageGlobal(l); + break; + } + } + return obj == null ? null : new Symbol(obj, global); + } + + private void checkThread() { + if (initThread != Thread.currentThread()) { + throw new IllegalStateException("TruffleVM created on " + initThread.getName() + " but used on " + Thread.currentThread().getName()); + } + } + + private TruffleLanguage getTruffleLang(String mimeType) { + checkThread(); + Language l = langs.get(mimeType); + return l == null ? null : l.getImpl(); + } + + /** + * Represents {@link TruffleVM#findGlobalSymbol(java.lang.String) global symbol} provided by one + * of the initialized languages in {@link TruffleVM Truffle virtual machine}. + */ + public class Symbol { + private final Object obj; + private final Object global; + + Symbol(Object obj, Object global) { + this.obj = obj; + this.global = global; + } + + /** + * 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 the value returned by invoking the symbol + * @throws IOException signals problem during execution + */ + public Object invoke(Object thiz, Object... args) throws IOException { + List<Object> arr = new ArrayList<>(); + if (thiz == null) { + if (global != null) { + arr.add(global); + } + } else { + arr.add(thiz); + } + arr.addAll(Arrays.asList(args)); + return SPI.invoke(obj, arr.toArray()); + } + } + + /** + * Description of a language registered in {@link TruffleVM 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 TruffleVM} is created, it + * reads all available descritors and creates {@link Language} objects to represent them. One + * can obtain a {@link #getName() name} or list of supported {@link #getMimeTypes() mimetypes} + * for each language. The actual language implementation is not initialized until + * {@link TruffleVM#eval(java.lang.String, java.lang.String) a code is evaluated} in it. + */ + public final class Language { + private final Properties props; + private TruffleLanguage impl; + + Language(Properties props) { + this.props = props; + } + + /** + * Mimetypes recognized by the language. + * + * @return returns immutable set of recognized mimetypes + */ + public Set<String> getMimeTypes() { + TreeSet<String> ts = new TreeSet<>(); + for (int i = 0;; i++) { + String mt = props.getProperty("mimeType." + i); + if (mt == null) { + break; + } + ts.add(mt); + } + return Collections.unmodifiableSet(ts); + } + + /** + * Human readable name of the language. Think of C, Ruby, JS, etc. + * + * @return string giving the language a name + */ + public String getName() { + return props.getProperty("name"); + } + + TruffleLanguage getImpl() { + if (impl == null) { + String n = props.getProperty("className"); + try { + TruffleLanguage lang = (TruffleLanguage) Class.forName(n, true, loader()).newInstance(); + SPI.attachEnv(TruffleVM.this, lang); + impl = lang; + } catch (Exception ex) { + throw new IllegalStateException("Cannot initialize " + getName() + " language with implementation " + n, ex); + } + } + return impl; + } + + @Override + public String toString() { + return "[" + getName() + " for " + getMimeTypes() + "]"; + } + } // end of Language + + private static class SPIAccessor extends Accessor { + @Override + public Object importSymbol(TruffleVM vm, TruffleLanguage ownLang, String globalName) { + Set<Language> uniqueLang = new LinkedHashSet<>(vm.langs.values()); + for (Language dl : uniqueLang) { + TruffleLanguage l = dl.getImpl(); + if (l == ownLang) { + continue; + } + Object obj = SPI.findExportedSymbol(l, globalName); + if (obj != null) { + return obj; + } + } + return null; + } + + @Override + public Env attachEnv(TruffleVM vm, TruffleLanguage l) { + return super.attachEnv(vm, l); + } + + @Override + public Object eval(TruffleLanguage l, Source s) throws IOException { + return super.eval(l, s); + } + + @Override + public Object findExportedSymbol(TruffleLanguage l, String globalName) { + return super.findExportedSymbol(l, globalName); + } + + @Override + public Object languageGlobal(TruffleLanguage l) { + return super.languageGlobal(l); + } + + @Override + public Object invoke(Object obj, Object[] args) throws IOException { + return super.invoke(obj, args); + } + } // end of SPIAccessor +}