comparison 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
comparison
equal deleted inserted replaced
21467:d4db9d812c8d 21468:99942eac9c6d
1 /*
2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package com.oracle.truffle.api.vm;
26
27 import com.oracle.truffle.api.TruffleLanguage;
28 import com.oracle.truffle.api.TruffleLanguage.Registration;
29 import com.oracle.truffle.api.TruffleLanguage.Env;
30 import com.oracle.truffle.api.impl.Accessor;
31 import com.oracle.truffle.api.source.Source;
32 import java.io.File;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.io.Reader;
36 import java.net.URI;
37 import java.net.URL;
38 import java.net.URLConnection;
39 import java.nio.file.Files;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.Enumeration;
44 import java.util.HashMap;
45 import java.util.LinkedHashSet;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Properties;
49 import java.util.Set;
50 import java.util.TreeSet;
51 import java.util.logging.Level;
52 import java.util.logging.Logger;
53
54 /**
55 * Virtual machine for Truffle based languages. Use {@link #create()} to instantiate new isolated
56 * virtual machine ready for execution of various languages. All the languages in a single virtual
57 * machine see each other exported global symbols and can co-operate. Use {@link #create()} multiple
58 * times to create different, isolated virtual machines completely separated from each other.
59 * <p>
60 * Once instantiated use {@link #eval(java.net.URI)} with a reference to a file or URL or directly
61 * pass code snippet into the virtual machine via {@link #eval(java.lang.String, java.lang.String)}.
62 * Support for individual languages is initialized on demand - e.g. once a file of certain mime type
63 * is about to be processed, its appropriate engine (if found), is initialized. Once an engine gets
64 * initialized, it remains so, until the virtual machine isn't garbage collected.
65 * <p>
66 * The <code>TruffleVM</code> is single-threaded and tries to enforce that. It records the thread it
67 * has been {@link #create() created} by and checks that all subsequent calls are coming from the
68 * same thread.
69 */
70 public final class TruffleVM {
71 private static final Logger LOG = Logger.getLogger(TruffleVM.class.getName());
72 private static final SPIAccessor SPI = new SPIAccessor();
73 private final Thread initThread;
74 private final Map<String, Language> langs;
75
76 private TruffleVM() {
77 initThread = Thread.currentThread();
78 this.langs = new HashMap<>();
79 Enumeration<URL> en;
80 try {
81 en = loader().getResources("META-INF/truffle/language");
82 } catch (IOException ex) {
83 throw new IllegalStateException("Cannot read list of Truffle languages", ex);
84 }
85 while (en.hasMoreElements()) {
86 URL u = en.nextElement();
87 Properties p;
88 try {
89 p = new Properties();
90 try (InputStream is = u.openStream()) {
91 p.load(is);
92 }
93 } catch (IOException ex) {
94 LOG.log(Level.CONFIG, "Cannot process " + u + " as language definition", ex);
95 continue;
96 }
97 Language l = new Language(p);
98 for (String mimeType : l.getMimeTypes()) {
99 langs.put(mimeType, l);
100 }
101 }
102 }
103
104 static ClassLoader loader() {
105 ClassLoader l = TruffleVM.class.getClassLoader();
106 if (l == null) {
107 l = ClassLoader.getSystemClassLoader();
108 }
109 return l;
110 }
111
112 /**
113 * Creates new Truffle virtual machine. It searches for {@link Registration languages
114 * registered} in the system class loader and makes them available for later evaluation via
115 * {@link #eval(java.lang.String, java.lang.String)} methods.
116 *
117 * @return new, isolated virtual machine with pre-registered languages
118 */
119 public static TruffleVM create() {
120 return new TruffleVM();
121 }
122
123 /**
124 * Descriptions of languages supported in this Truffle virtual machine.
125 *
126 * @return an immutable map with keys being mimetypes and values the {@link Language
127 * descriptions} of associated languages
128 */
129 public Map<String, Language> getLanguages() {
130 return Collections.unmodifiableMap(langs);
131 }
132
133 /**
134 * Evaluates file located on a given URL. Is equivalent to loading the content of a file and
135 * executing it via {@link #eval(java.lang.String, java.lang.String)} with a mime type guess
136 * based on the file's extension and/or content.
137 *
138 * @param location the location of a file to execute
139 * @return result of a processing the file, possibly <code>null</code>
140 * @throws IOException exception to signal I/O problems or problems with processing the file's
141 * content
142 */
143 public Object eval(URI location) throws IOException {
144 checkThread();
145 Source s;
146 String mimeType;
147 if (location.getScheme().equals("file")) {
148 File file = new File(location);
149 s = Source.fromFileName(file.getPath(), true);
150 mimeType = file.getName().endsWith(".c") ? "text/x-c" : Files.probeContentType(file.toPath());
151 } else {
152 URL url = location.toURL();
153 s = Source.fromURL(url, location.toString());
154 URLConnection conn = url.openConnection();
155 mimeType = conn.getContentType();
156 }
157 TruffleLanguage l = getTruffleLang(mimeType);
158 if (l == null) {
159 throw new IOException("No language for " + location + " with mime type " + mimeType + " found. Supported types: " + langs.keySet());
160 }
161 return SPI.eval(l, s);
162 }
163
164 /**
165 * Evaluates code snippet. Chooses a language registered for a given mime type (throws
166 * {@link IOException} if there is none). And passes the specified code to it for execution.
167 *
168 * @param mimeType mime type of the code snippet - chooses the right language
169 * @param reader the source of code snippet to execute
170 * @return result of an exceution, possibly <code>null</code>
171 * @throws IOException thrown to signal errors while processing the code
172 */
173 public Object eval(String mimeType, Reader reader) throws IOException {
174 checkThread();
175 TruffleLanguage l = getTruffleLang(mimeType);
176 if (l == null) {
177 throw new IOException("No language for mime type " + mimeType + " found. Supported types: " + langs.keySet());
178 }
179 return SPI.eval(l, Source.fromReader(reader, mimeType));
180 }
181
182 /**
183 * Evaluates code snippet. Chooses a language registered for a given mime type (throws
184 * {@link IOException} if there is none). And passes the specified code to it for execution.
185 *
186 * @param mimeType mime type of the code snippet - chooses the right language
187 * @param code the code snippet to execute
188 * @return result of an exceution, possibly <code>null</code>
189 * @throws IOException thrown to signal errors while processing the code
190 */
191 public Object eval(String mimeType, String code) throws IOException {
192 checkThread();
193 TruffleLanguage l = getTruffleLang(mimeType);
194 if (l == null) {
195 throw new IOException("No language for mime type " + mimeType + " found. Supported types: " + langs.keySet());
196 }
197 return SPI.eval(l, Source.fromText(code, mimeType));
198 }
199
200 /**
201 * Looks global symbol provided by one of initialized languages up. First of all execute your
202 * program via one of your {@link #eval(java.lang.String, java.lang.String)} and then look
203 * expected symbol up using this method.
204 * <p>
205 * The names of the symbols are language dependant, but for example the Java language bindings
206 * follow the specification for method references:
207 * <ul>
208 * <li>"java.lang.Exception::new" is a reference to constructor of {@link Exception}
209 * <li>"java.lang.Integer::valueOf" is a reference to static method in {@link Integer} class
210 * </ul>
211 * Once an symbol is obtained, it remembers values for fast acces and is ready for being
212 * invoked.
213 *
214 * @param globalName the name of the symbol to find
215 * @return found symbol or <code>null</code> if it has not been found
216 */
217 public Symbol findGlobalSymbol(String globalName) {
218 checkThread();
219 Object obj = null;
220 Object global = null;
221 for (Language dl : langs.values()) {
222 TruffleLanguage l = dl.getImpl();
223 obj = SPI.findExportedSymbol(l, globalName);
224 if (obj != null) {
225 global = SPI.languageGlobal(l);
226 break;
227 }
228 }
229 return obj == null ? null : new Symbol(obj, global);
230 }
231
232 private void checkThread() {
233 if (initThread != Thread.currentThread()) {
234 throw new IllegalStateException("TruffleVM created on " + initThread.getName() + " but used on " + Thread.currentThread().getName());
235 }
236 }
237
238 private TruffleLanguage getTruffleLang(String mimeType) {
239 checkThread();
240 Language l = langs.get(mimeType);
241 return l == null ? null : l.getImpl();
242 }
243
244 /**
245 * Represents {@link TruffleVM#findGlobalSymbol(java.lang.String) global symbol} provided by one
246 * of the initialized languages in {@link TruffleVM Truffle virtual machine}.
247 */
248 public class Symbol {
249 private final Object obj;
250 private final Object global;
251
252 Symbol(Object obj, Object global) {
253 this.obj = obj;
254 this.global = global;
255 }
256
257 /**
258 * Invokes the symbol. If the symbol represents a function, then it should be invoked with
259 * provided arguments. If the symbol represents a field, then first argument (if provided)
260 * should set the value to the field; the return value should be the actual value of the
261 * field when the <code>invoke</code> method returns.
262 *
263 * @param thiz this/self in language that support such concept; use <code>null</code> to let
264 * the language use default this/self or ignore the value
265 * @param args arguments to pass when invoking the symbol
266 * @return the value returned by invoking the symbol
267 * @throws IOException signals problem during execution
268 */
269 public Object invoke(Object thiz, Object... args) throws IOException {
270 List<Object> arr = new ArrayList<>();
271 if (thiz == null) {
272 if (global != null) {
273 arr.add(global);
274 }
275 } else {
276 arr.add(thiz);
277 }
278 arr.addAll(Arrays.asList(args));
279 return SPI.invoke(obj, arr.toArray());
280 }
281 }
282
283 /**
284 * Description of a language registered in {@link TruffleVM Truffle virtual machine}. Languages
285 * are registered by {@link Registration} annotation which stores necessary information into a
286 * descriptor inside of the language's JAR file. When a new {@link TruffleVM} is created, it
287 * reads all available descritors and creates {@link Language} objects to represent them. One
288 * can obtain a {@link #getName() name} or list of supported {@link #getMimeTypes() mimetypes}
289 * for each language. The actual language implementation is not initialized until
290 * {@link TruffleVM#eval(java.lang.String, java.lang.String) a code is evaluated} in it.
291 */
292 public final class Language {
293 private final Properties props;
294 private TruffleLanguage impl;
295
296 Language(Properties props) {
297 this.props = props;
298 }
299
300 /**
301 * Mimetypes recognized by the language.
302 *
303 * @return returns immutable set of recognized mimetypes
304 */
305 public Set<String> getMimeTypes() {
306 TreeSet<String> ts = new TreeSet<>();
307 for (int i = 0;; i++) {
308 String mt = props.getProperty("mimeType." + i);
309 if (mt == null) {
310 break;
311 }
312 ts.add(mt);
313 }
314 return Collections.unmodifiableSet(ts);
315 }
316
317 /**
318 * Human readable name of the language. Think of C, Ruby, JS, etc.
319 *
320 * @return string giving the language a name
321 */
322 public String getName() {
323 return props.getProperty("name");
324 }
325
326 TruffleLanguage getImpl() {
327 if (impl == null) {
328 String n = props.getProperty("className");
329 try {
330 TruffleLanguage lang = (TruffleLanguage) Class.forName(n, true, loader()).newInstance();
331 SPI.attachEnv(TruffleVM.this, lang);
332 impl = lang;
333 } catch (Exception ex) {
334 throw new IllegalStateException("Cannot initialize " + getName() + " language with implementation " + n, ex);
335 }
336 }
337 return impl;
338 }
339
340 @Override
341 public String toString() {
342 return "[" + getName() + " for " + getMimeTypes() + "]";
343 }
344 } // end of Language
345
346 private static class SPIAccessor extends Accessor {
347 @Override
348 public Object importSymbol(TruffleVM vm, TruffleLanguage ownLang, String globalName) {
349 Set<Language> uniqueLang = new LinkedHashSet<>(vm.langs.values());
350 for (Language dl : uniqueLang) {
351 TruffleLanguage l = dl.getImpl();
352 if (l == ownLang) {
353 continue;
354 }
355 Object obj = SPI.findExportedSymbol(l, globalName);
356 if (obj != null) {
357 return obj;
358 }
359 }
360 return null;
361 }
362
363 @Override
364 public Env attachEnv(TruffleVM vm, TruffleLanguage l) {
365 return super.attachEnv(vm, l);
366 }
367
368 @Override
369 public Object eval(TruffleLanguage l, Source s) throws IOException {
370 return super.eval(l, s);
371 }
372
373 @Override
374 public Object findExportedSymbol(TruffleLanguage l, String globalName) {
375 return super.findExportedSymbol(l, globalName);
376 }
377
378 @Override
379 public Object languageGlobal(TruffleLanguage l) {
380 return super.languageGlobal(l);
381 }
382
383 @Override
384 public Object invoke(Object obj, Object[] args) throws IOException {
385 return super.invoke(obj, args);
386 }
387 } // end of SPIAccessor
388 }