changeset 22380:b4e89154a774

Supporting efficient cross-language eval
author Jaroslav Tulach <jaroslav.tulach@oracle.com>
date Wed, 18 Nov 2015 12:33:52 +0100
parents 299c279c87b3 (current diff) fdf6ad720cdc (diff)
children d3facd428d1d
files truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java
diffstat 7 files changed, 347 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java	Wed Nov 18 09:02:41 2015 +0100
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java	Wed Nov 18 12:33:52 2015 +0100
@@ -385,6 +385,25 @@
         }
 
         /**
+         * Evaluates source of (potentially different) language. The {@link Source#getMimeType()
+         * MIME type) is used to identify the {@link TruffleLanguage} to use to perform the
+         * {@link #parse(com.oracle.truffle.api.source.Source, com.oracle.truffle.api.nodes.Node, java.lang.String...)}.
+         * The names of arguments are parameters for the resulting
+         * {#link CallTarget} that allow the <code>source</code> to reference
+         * the actual parameters passed to {@link CallTarget#call(java.lang.Object...)}.
+         * 
+         * @param source the source to evaluate
+         * @param argumentNames the names of {@link CallTarget#call(java.lang.Object...)}
+         *   arguments that can be referenced from the source
+         * @return the call target representing the parsed result
+         * @throws IOException if the parsing or evaluation fails for some reason
+         */
+        public CallTarget parse(Source source, String... argumentNames) throws IOException {
+            TruffleLanguage<?> language = API.findLanguageImpl(vm, null, source.getMimeType());
+            return language.parse(source, null, argumentNames);
+        }
+
+        /**
          * Input associated with {@link com.oracle.truffle.api.vm.PolyglotEngine} this language is
          * being executed in.
          *
@@ -484,6 +503,11 @@
         }
 
         @Override
+        protected TruffleLanguage<?> findLanguageImpl(Object known, Class<? extends TruffleLanguage> languageClass, String mimeType) {
+            return super.findLanguageImpl(known, languageClass, mimeType);
+        }
+
+        @Override
         protected Object languageGlobal(TruffleLanguage.Env env) {
             return env.langCtx.getLanguageGlobal();
         }
--- a/truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/SLTckTest.java	Wed Nov 18 09:02:41 2015 +0100
+++ b/truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/SLTckTest.java	Wed Nov 18 12:33:52 2015 +0100
@@ -44,10 +44,8 @@
 import com.oracle.truffle.api.vm.PolyglotEngine;
 import com.oracle.truffle.sl.test.instrument.InstrumentationTestMode;
 import com.oracle.truffle.tck.TruffleTCK;
-
+import org.junit.After;
 import static org.junit.Assert.assertTrue;
-
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -161,6 +159,13 @@
     }
 
     @Override
+    protected String multiplyCode(String firstName, String secondName) {
+        return "function multiply(" + firstName + ", " + secondName + ") {\n" +
+            "  return " + firstName + " * " + secondName + ";\n" +
+            "}\n";
+    }
+
+    @Override
     protected String countInvocations() {
         return "count";
     }
@@ -170,6 +175,11 @@
         return null;
     }
 
+    @Override
+    protected String evaluateSource() {
+        return "interopEval";
+    }
+
     //
     // Ignore tests working on floats and double
     //
--- a/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java	Wed Nov 18 09:02:41 2015 +0100
+++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java	Wed Nov 18 12:33:52 2015 +0100
@@ -57,12 +57,16 @@
 import com.oracle.truffle.api.dsl.NodeFactory;
 import com.oracle.truffle.api.dsl.UnsupportedSpecializationException;
 import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.instrument.Visualizer;
 import com.oracle.truffle.api.instrument.WrapperNode;
+import com.oracle.truffle.api.interop.ForeignAccess;
+import com.oracle.truffle.api.interop.Message;
 import com.oracle.truffle.api.nodes.GraphPrintVisitor;
 import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.nodes.NodeInfo;
 import com.oracle.truffle.api.nodes.NodeUtil;
+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.PolyglotEngine;
@@ -210,7 +214,7 @@
     protected SLContext createContext(Env env) {
         final BufferedReader in = new BufferedReader(new InputStreamReader(env.in()));
         final PrintWriter out = new PrintWriter(env.out(), true);
-        SLContext context = new SLContext(this, in, out);
+        SLContext context = new SLContext(this, env, in, out);
         for (NodeFactory<? extends SLBuiltinNode> builtin : builtins) {
             context.installBuiltin(builtin, true);
         }
@@ -410,7 +414,7 @@
     }
 
     @Override
-    protected CallTarget parse(Source code, Node node, String... argumentNames) throws IOException {
+    protected CallTarget parse(Source code, final Node node, String... argumentNames) throws IOException {
         final SLContext c = new SLContext(this);
         final Exception[] failed = {null};
         try {
@@ -419,10 +423,10 @@
         } catch (Exception e) {
             failed[0] = e;
         }
-        return new CallTarget() {
+        RootNode rootNode = new RootNode(SLLanguage.class, null, null) {
             @TruffleBoundary
             @Override
-            public Object call(Object... arguments) {
+            public Object execute(VirtualFrame frame) {
                 if (failed[0] instanceof RuntimeException) {
                     throw (RuntimeException) failed[0];
                 }
@@ -432,17 +436,26 @@
                 Node n = createFindContextNode();
                 SLContext fillIn = findContext(n);
                 final SLFunctionRegistry functionRegistry = fillIn.getFunctionRegistry();
+                int oneAndCnt = 0;
+                SLFunction oneAndOnly = null;
                 for (SLFunction f : c.getFunctionRegistry().getFunctions()) {
                     RootCallTarget callTarget = f.getCallTarget();
                     if (callTarget == null) {
                         continue;
                     }
-                    functionRegistry.lookup(f.getName());
+                    oneAndOnly = functionRegistry.lookup(f.getName());
+                    oneAndCnt++;
                     functionRegistry.register(f.getName(), (SLRootNode) f.getCallTarget().getRootNode());
                 }
+                Object[] arguments = frame.getArguments();
+                if (oneAndCnt == 1 && (arguments.length > 0 || node != null)) {
+                    Node callNode = Message.createExecute(arguments.length).createNode();
+                    return ForeignAccess.execute(callNode, frame, oneAndOnly, arguments);
+                }
                 return null;
             }
         };
+        return Truffle.getRuntime().createCallTarget(rootNode);
     }
 
     @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/builtins/SLEvalBuiltin.java	Wed Nov 18 12:33:52 2015 +0100
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * The Universal Permissive License (UPL), Version 1.0
+ *
+ * Subject to the condition set forth below, permission is hereby granted to any
+ * person obtaining a copy of this software, associated documentation and/or
+ * data (collectively the "Software"), free of charge and under any and all
+ * copyright rights in the Software, and any and all patent rights owned or
+ * freely licensable by each licensor hereunder covering either (i) the
+ * unmodified Software as contributed to or provided by such licensor, or (ii)
+ * the Larger Works (as defined below), to deal in both
+ *
+ * (a) the Software, and
+ *
+ * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
+ * one is included with the Software each a "Larger Work" to which the Software
+ * is contributed by such licensors),
+ *
+ * without restriction, including without limitation the rights to copy, create
+ * derivative works of, display, perform, and distribute the Software and make,
+ * use, sell, offer for sale, import, export, have made, and have sold the
+ * Software and the Larger Work(s), and to sublicense the foregoing rights on
+ * either these or other terms.
+ *
+ * This license is subject to the following condition:
+ *
+ * The above copyright notice and either this complete permission notice or at a
+ * minimum a reference to the UPL must be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.oracle.truffle.sl.builtins;
+
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.nodes.NodeInfo;
+import com.oracle.truffle.api.source.Source;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.sl.SLLanguage;
+import java.io.IOException;
+
+/**
+ * Builtin function to parse a text in other language.
+ */
+@NodeInfo(shortName = "interopEval")
+public abstract class SLEvalBuiltin extends SLBuiltinNode {
+
+    public SLEvalBuiltin() {
+        super(SourceSection.createUnavailable(SLLanguage.builtinKind, "interopEval"));
+    }
+
+    @Specialization
+    public Object interopEval(String mimeType, String code) {
+        Source source = Source.fromText(code, "<unknown>").withMimeType(mimeType);
+        try {
+            return getContext().evalAny(source);
+        } catch (IOException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+}
--- a/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLContext.java	Wed Nov 18 09:02:41 2015 +0100
+++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLContext.java	Wed Nov 18 12:33:52 2015 +0100
@@ -41,6 +41,7 @@
 package com.oracle.truffle.sl.runtime;
 
 import com.oracle.truffle.api.ExecutionContext;
+import com.oracle.truffle.api.TruffleLanguage;
 import com.oracle.truffle.api.dsl.NodeFactory;
 import com.oracle.truffle.api.frame.FrameDescriptor;
 import com.oracle.truffle.api.nodes.NodeInfo;
@@ -54,6 +55,7 @@
 import com.oracle.truffle.sl.builtins.SLAssertTrueBuiltinFactory;
 import com.oracle.truffle.sl.builtins.SLBuiltinNode;
 import com.oracle.truffle.sl.builtins.SLDefineFunctionBuiltinFactory;
+import com.oracle.truffle.sl.builtins.SLEvalBuiltinFactory;
 import com.oracle.truffle.sl.builtins.SLHelloEqualsWorldBuiltinFactory;
 import com.oracle.truffle.sl.builtins.SLNanoTimeBuiltinFactory;
 import com.oracle.truffle.sl.builtins.SLNewObjectBuiltinFactory;
@@ -69,6 +71,7 @@
 import com.oracle.truffle.sl.parser.SLNodeFactory;
 
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.math.BigInteger;
 
@@ -90,19 +93,21 @@
     private final PrintWriter output;
     private final SLFunctionRegistry functionRegistry;
     private final Shape emptyShape;
+    private final TruffleLanguage.Env env;
 
-    public SLContext(SLLanguage language, BufferedReader input, PrintWriter output) {
-        this(language, input, output, true);
+    public SLContext(SLLanguage language, TruffleLanguage.Env env, BufferedReader input, PrintWriter output) {
+        this(language, env, input, output, true);
     }
 
     public SLContext(SLLanguage language) {
-        this(language, null, null, false);
+        this(language, null, null, null, false);
     }
 
-    private SLContext(SLLanguage language, BufferedReader input, PrintWriter output, boolean installBuiltins) {
+    private SLContext(SLLanguage language, TruffleLanguage.Env env, BufferedReader input, PrintWriter output, boolean installBuiltins) {
         this.language = language;
         this.input = input;
         this.output = output;
+        this.env = env;
         this.functionRegistry = new SLFunctionRegistry();
         installBuiltins(installBuiltins);
 
@@ -150,6 +155,7 @@
         installBuiltin(SLAssertTrueBuiltinFactory.getInstance(), registerRootNodes);
         installBuiltin(SLAssertFalseBuiltinFactory.getInstance(), registerRootNodes);
         installBuiltin(SLNewObjectBuiltinFactory.getInstance(), registerRootNodes);
+        installBuiltin(SLEvalBuiltinFactory.getInstance(), registerRootNodes);
     }
 
     public void installBuiltin(NodeFactory<? extends SLBuiltinNode> factory, boolean registerRootNodes) {
@@ -227,4 +233,8 @@
         }
         return a;
     }
+
+    public Object evalAny(Source source) throws IOException {
+        return env.parse(source).call();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.tck/src/com/oracle/truffle/tck/TckLanguage.java	Wed Nov 18 12:33:52 2015 +0100
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2015, 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.tck;
+
+import com.oracle.truffle.api.CallTarget;
+import com.oracle.truffle.api.Truffle;
+import com.oracle.truffle.api.TruffleLanguage;
+import com.oracle.truffle.api.TruffleLanguage.Env;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.instrument.Visualizer;
+import com.oracle.truffle.api.instrument.WrapperNode;
+import com.oracle.truffle.api.interop.ForeignAccess;
+import com.oracle.truffle.api.interop.Message;
+import com.oracle.truffle.api.interop.TruffleObject;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.nodes.RootNode;
+import com.oracle.truffle.api.source.Source;
+import java.io.IOException;
+
+@TruffleLanguage.Registration(mimeType = "application/x-tck", name = "TCK", version = "1.0")
+public final class TckLanguage extends TruffleLanguage<Env> {
+    public static final TckLanguage INSTANCE = new TckLanguage();
+
+    @Override
+    protected Env createContext(Env env) {
+        return env;
+    }
+
+    @Override
+    protected CallTarget parse(Source code, Node context, String... argumentNames) throws IOException {
+        final RootNode root;
+        final String txt = code.getCode();
+        if (txt.startsWith("TCK42:")) {
+            int nextColon = txt.indexOf(":", 6);
+            String mimeType = txt.substring(6, nextColon);
+            Source toParse = Source.fromText(txt.substring(nextColon + 1), "").withMimeType(mimeType);
+            root = new MultiplyNode(toParse);
+        } else {
+            final double value = Double.parseDouble(txt);
+            root = RootNode.createConstantNode(value);
+        }
+        return Truffle.getRuntime().createCallTarget(root);
+    }
+
+    @Override
+    protected Object findExportedSymbol(Env context, String globalName, boolean onlyExplicit) {
+        return null;
+    }
+
+    @Override
+    protected Object getLanguageGlobal(Env context) {
+        return null;
+    }
+
+    @Override
+    protected boolean isObjectOfLanguage(Object object) {
+        return false;
+    }
+
+    @Override
+    protected Visualizer getVisualizer() {
+        return null;
+    }
+
+    @Override
+    protected boolean isInstrumentable(Node node) {
+        return false;
+    }
+
+    @Override
+    protected WrapperNode createWrapperNode(Node node) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected Object evalInContext(Source source, Node node, MaterializedFrame mFrame) throws IOException {
+        throw new IOException();
+    }
+
+    private static final class MultiplyNode extends RootNode implements TruffleObject, ForeignAccess.Factory {
+        private final Source code;
+
+        public MultiplyNode(Source toParse) {
+            super(TckLanguage.class, null, null);
+            this.code = toParse;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            Env env = TckLanguage.INSTANCE.findContext(TckLanguage.INSTANCE.createFindContextNode());
+            if (frame.getArguments().length == 0) {
+                return this;
+            }
+            try {
+                CallTarget call = env.parse(code, (String)frame.getArguments()[1], (String)frame.getArguments()[2]);
+                return call.call(6, 7);
+            } catch (IOException ex) {
+                throw new AssertionError("Cannot parse " + code, ex);
+            }
+        }
+
+        @Override
+        public ForeignAccess getForeignAccess() {
+            return ForeignAccess.create(this);
+        }
+
+        @Override
+        public boolean canHandle(TruffleObject obj) {
+            return obj instanceof MultiplyNode;
+        }
+
+        @Override
+        public CallTarget accessMessage(Message tree) {
+            if (tree == Message.IS_EXECUTABLE) {
+                return Truffle.getRuntime().createCallTarget(RootNode.createConstantNode(Boolean.TRUE));
+            } else if (Message.createExecute(2).equals(tree)) {
+                return Truffle.getRuntime().createCallTarget(this);
+            } else {
+                throw new IllegalArgumentException("" + tree);
+            }
+        }
+
+    }
+}
--- a/truffle/com.oracle.truffle.tck/src/com/oracle/truffle/tck/TruffleTCK.java	Wed Nov 18 09:02:41 2015 +0100
+++ b/truffle/com.oracle.truffle.tck/src/com/oracle/truffle/tck/TruffleTCK.java	Wed Nov 18 12:33:52 2015 +0100
@@ -38,8 +38,16 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * A collection of tests that can certify language implementation to be compliant with most recent
@@ -157,6 +165,31 @@
     }
 
     /**
+     * Name of a function to parse source written in some other language. When the function is
+     * executed, it expects two arguments. First one is MIME type identifying
+     * {@link TruffleLanguage} and the second one is the source code to parse in that language and
+     * execute it. The result of the execution is then returned back to the caller.
+     *
+     * @return name of globally exported symbol to invoke when one wants to execute some code
+     */
+    protected String evaluateSource() {
+        throw new UnsupportedOperationException("evaluateSource() method not implemented");
+    }
+
+    /**
+     * Code snippet to multiplyCode two two variables. The test uses the snippet
+     * as a parameter to your language's {@link TruffleLanguage#parse(com.oracle.truffle.api.source.Source, com.oracle.truffle.api.nodes.Node, java.lang.String...)}
+     * method.
+     *
+     * @param firstName name of the first variable to multiplyCode
+     * @param secondName name of the second variable to multiplyCode
+     * @return code snippet that multiplies the two variables in your language
+     */
+    protected String multiplyCode(String firstName, String secondName) {
+        throw new UnsupportedOperationException("multiply(String,String) method not implemeted!");
+    }
+
+    /**
      * Name of a function that counts number of its invocations in current {@link PolyglotEngine}
      * context. Your function should somehow keep a counter to remember number of its invocations
      * and always increment it. The first invocation should return <code>1</code>, the second
@@ -594,6 +627,35 @@
         assertEquals("Global from the language same with Java obtained one", language.getGlobalObject().get(), global);
     }
 
+    @Test
+    public void testEvaluateSource() throws Exception {
+        Language language = vm().getLanguages().get(mimeType());
+        assertNotNull("Langugage for " + mimeType() + " found", language);
+
+        PolyglotEngine.Value function = vm().findGlobalSymbol(evaluateSource());
+        assertNotNull(evaluateSource() + " found", function);
+
+        double expect = Math.floor(RANDOM.nextDouble() * 100000.0) / 10.0;
+        Object parsed = function.invoke(null, "application/x-tck", "" + expect).get();
+        assertTrue("Expecting numeric result, was:" + expect, parsed instanceof Number);
+        double value = ((Number)parsed).doubleValue();
+        assertEquals("Gets the double", expect, value, 0.01);
+    }
+
+    @Test
+    public void multiplyTwoVariables() throws Exception {
+        final String firstVar = "var" + (char)('A' + RANDOM.nextInt(24));
+        final String secondVar = "var" + (char)('0' + RANDOM.nextInt(10));
+        String mulCode = multiplyCode(firstVar, secondVar);
+        Source source = Source.fromText("TCK42:" + mimeType() + ":" + mulCode, "evaluate " + firstVar + " * " + secondVar)
+            .withMimeType("application/x-tck");
+        final PolyglotEngine.Value evalSource = vm().eval(source);
+        final PolyglotEngine.Value invokeMul = evalSource.invoke(null, firstVar, secondVar);
+        Object result = invokeMul.get();
+        assertTrue("Expecting numeric result, was:" + result, result instanceof Number);
+        assertEquals("Right value", 42, ((Number)result).intValue());
+    }
+
     private PolyglotEngine.Value findGlobalSymbol(String name) throws Exception {
         PolyglotEngine.Value s = vm().findGlobalSymbol(name);
         assert s != null : "Symbol " + name + " is not found!";