diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java index 7ecb06de9682124a077836b1834a817c7705bf77..ca2133ea918c645f2efcfcac3ae892d6972d27eb 100644 --- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java +++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java @@ -371,10 +371,10 @@ final class REngine implements Engine, Engine.Timings { RContext.setThreadLocalInstance(newContext); try { Object lastValue = RNull.instance; - for (int i = 0; i < statements.size(); i++) { - RSyntaxNode node = statements.get(i); + for (int i = 0; i < calls.length; i++) { if (calls[i] == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); + RSyntaxNode node = statements.get(i); calls[i] = insert(Truffle.getRuntime().createDirectCallNode(doMakeCallTarget(node.asRNode(), RSource.Internal.REPL_WRAPPER.string, printResult, true))); } lastValue = calls[i].call(new Object[]{executionFrame != null ? executionFrame : newContext.stateREnvironment.getGlobalFrame()}); diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/RArgsValuesAndNamesMR.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/RArgsValuesAndNamesMR.java index 82f194dcdc6c9aae2fd1b4a221be85eedfaa681e..c62c3b20335d81ad01044fb019e7a0433ef9c43f 100644 --- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/RArgsValuesAndNamesMR.java +++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/RArgsValuesAndNamesMR.java @@ -45,6 +45,13 @@ public class RArgsValuesAndNamesMR { } } + @Resolve(message = "GET_SIZE") + public abstract static class RArgsValuesAndNamesGetSizeNode extends Node { + protected Object access(RArgsValuesAndNames receiver) { + return receiver.getLength(); + } + } + @Resolve(message = "IS_NULL") public abstract static class RArgsValuesAndNamesIsNullNode extends Node { protected Object access(@SuppressWarnings("unused") RArgsValuesAndNames receiver) { @@ -52,6 +59,13 @@ public class RArgsValuesAndNamesMR { } } + @Resolve(message = "READ") + public abstract static class RArgsValuesAndNamesReadNode extends Node { + protected Object access(RArgsValuesAndNames receiver, int index) { + return receiver.getArgument(index); + } + } + @CanResolve public abstract static class RArgsValuesAndNamesCheck extends Node { diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/REnvironmentMR.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/REnvironmentMR.java index 94e27017981917fb8c9f1b4dcf3b84dcbcc1e967..e8104c310ff3334ca899365ce8fd1c27a0a2b6a8 100644 --- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/REnvironmentMR.java +++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/REnvironmentMR.java @@ -97,6 +97,34 @@ public class REnvironmentMR { } } + @Resolve(message = "KEYS") + public abstract static class REnvironmentKeysNode extends Node { + + protected Object access(REnvironment receiver) { + return receiver.ls(true, null, true); + } + } + + @Resolve(message = "KEY_INFO") + public abstract static class REnvironmentKeyInfoNode extends Node { + + private static final int READABLE = 1 << 1; + private static final int WRITABLE = 1 << 2; + + protected Object access(@SuppressWarnings("unused") VirtualFrame frame, REnvironment receiver, String identifier) { + Object val = receiver.get(identifier); + if (val == null) { + return 0; + } + + int info = READABLE; + if (!receiver.isLocked() && !receiver.bindingIsLocked(identifier)) { + info += WRITABLE; + } + return info; + } + } + @CanResolve public abstract static class REnvironmentCheck extends Node { diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/RPromiseMR.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/RPromiseMR.java index 269143e33d5aa41dd48a18f2aec5c30fb7c6d009..589fd7f8c78e1c4c3c5fbb4cb5bf2721d8832ea0 100644 --- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/RPromiseMR.java +++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/interop/RPromiseMR.java @@ -22,40 +22,125 @@ */ package com.oracle.truffle.r.engine.interop; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.CanResolve; import com.oracle.truffle.api.interop.MessageResolution; import com.oracle.truffle.api.interop.Resolve; import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.r.engine.TruffleRLanguageImpl; +import com.oracle.truffle.r.ffi.impl.interop.NativePointer; +import com.oracle.truffle.r.nodes.function.PromiseHelperNode; +import com.oracle.truffle.r.runtime.RRuntime; +import com.oracle.truffle.r.runtime.context.RContext; +import com.oracle.truffle.r.runtime.context.RContext.RCloseable; +import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RNull; import com.oracle.truffle.r.runtime.data.RPromise; @MessageResolution(receiverType = RPromise.class) public class RPromiseMR { + + private static final String PROP_VALUE = "value"; + private static final String PROP_IS_EVALUATED = "isEvaluated"; + private static final String PROP_IS_EAGER = "isEager"; + @Resolve(message = "IS_BOXED") public abstract static class RPromiseIsBoxedNode extends Node { protected Object access(@SuppressWarnings("unused") RPromise receiver) { - return true; + return false; } } - @Resolve(message = "HAS_SIZE") - public abstract static class RPromiseHasSizeNode extends Node { + @Resolve(message = "IS_NULL") + public abstract static class RPromiseIsNullNode extends Node { protected Object access(@SuppressWarnings("unused") RPromise receiver) { return false; } } - @Resolve(message = "IS_NULL") - public abstract static class RPromiseIsNullNode extends Node { + @Resolve(message = "READ") + public abstract static class RPromiseReadNode extends Node { + + protected Object access(@SuppressWarnings("unused") VirtualFrame frame, RPromise receiver, String field) { + if (PROP_IS_EAGER.equals(field)) { + return RRuntime.asLogical(RPromise.PromiseState.isEager(receiver.getState())); + } + if (PROP_IS_EVALUATED.equals(field)) { + return RRuntime.asLogical(receiver.isEvaluated()); + } + if (PROP_VALUE.equals(field)) { + // only read value if evaluated + if (receiver.isEvaluated()) { + return receiver.getValue(); + } + return RNull.instance; + } + throw UnknownIdentifierException.raise(field); + } + } + + @Resolve(message = "WRITE") + public abstract static class RPromiseWriteNode extends Node { + + @SuppressWarnings("try") + protected Object access(@SuppressWarnings("unused") VirtualFrame frame, RPromise receiver, String field, Object valueObj) { + if (PROP_IS_EVALUATED.equals(field)) { + if (!(valueObj instanceof Boolean)) { + throw UnsupportedTypeException.raise(new Object[]{valueObj}); + } + + boolean newVal = (boolean) valueObj; + + if (!receiver.isEvaluated() && newVal) { + try (RCloseable c = RContext.withinContext(TruffleRLanguageImpl.getCurrentContext())) { + PromiseHelperNode.evaluateSlowPath(receiver); + } + } else if (receiver.isEvaluated() && !newVal) { + try (RCloseable c = RContext.withinContext(TruffleRLanguageImpl.getCurrentContext())) { + receiver.resetValue(); + } + + } + return RRuntime.asLogical(receiver.isEvaluated()); + } + throw UnknownIdentifierException.raise(field); + } + } + + @Resolve(message = "KEYS") + public abstract static class RPromiseKeysNode extends Node { + protected Object access(@SuppressWarnings("unused") RPromise receiver) { - return false; + return RDataFactory.createStringVector(new String[]{PROP_VALUE, PROP_IS_EVALUATED, PROP_IS_EAGER}, true); + } + } + + @Resolve(message = "KEY_INFO") + public abstract static class RPromiseKeyInfoNode extends Node { + + private static final int READABLE = 1 << 1; + private static final int WRITABLE = 1 << 2; + + @SuppressWarnings("try") + protected Object access(@SuppressWarnings("unused") VirtualFrame frame, @SuppressWarnings("unused") RPromise receiver, String identifier) { + if (PROP_IS_EAGER.equals(identifier) || PROP_VALUE.equals(identifier)) { + return READABLE; + } + + if (PROP_IS_EVALUATED.equals(identifier)) { + return READABLE + WRITABLE; + } + return 0; } } - @Resolve(message = "UNBOX") - public abstract static class RPromiseUnboxNode extends Node { + @Resolve(message = "TO_NATIVE") + public abstract static class RPromiseToNativeNode extends Node { protected Object access(RPromise receiver) { - return receiver.getValue(); + return new NativePointer(receiver); } } diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java index 7d4472033f6520900409cbccebb4933a2633da1d..341d8f9acd9856a6af3435377f670126cf1796d7 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java @@ -423,6 +423,7 @@ public class BasePackage extends RBuiltinPackage { add(FastRInterop.IsExternal.class, FastRInteropFactory.IsExternalNodeGen::create); add(FastRInterop.JavaClass.class, FastRInteropFactory.JavaClassNodeGen::create); add(FastRInterop.JavaClassName.class, FastRInteropFactory.JavaClassNameNodeGen::create); + add(FastRInterop.JavaAddClasspathEntry.class, FastRInteropFactory.JavaAddClasspathEntryNodeGen::create); add(FastRInterop.IsForeignArray.class, FastRInteropFactory.IsForeignArrayNodeGen::create); add(FastRInterop.NewJavaArray.class, FastRInteropFactory.NewJavaArrayNodeGen::create); add(FastRInterop.ToJavaArray.class, FastRInteropFactory.ToJavaArrayNodeGen::create); diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java index dfd0f56b4b0b838e32dd325082c79f321c61e51c..d87fd82091ed6cbb1fc7a5d44dc98354595db5a7 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java @@ -39,6 +39,7 @@ import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE; import java.io.File; import java.io.IOException; import java.lang.reflect.Array; +import java.net.MalformedURLException; import java.util.logging.Level; import java.util.logging.Logger; @@ -83,7 +84,6 @@ import com.oracle.truffle.r.runtime.data.RList; import com.oracle.truffle.r.runtime.data.RMissing; import com.oracle.truffle.r.runtime.data.RNull; import com.oracle.truffle.r.runtime.data.RRaw; -import com.oracle.truffle.r.runtime.data.RTypedValue; import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector; @@ -120,7 +120,7 @@ public class FastRInterop { @SuppressWarnings("unused") @Specialization(guards = {"cachedMimeType != null", "cachedMimeType.equals(mimeType)", "cachedSource != null", "cachedSource.equals(source)"}) - protected Object evalCached(String mimeType, String source, @SuppressWarnings("unused") RMissing path, + protected Object evalCached(String mimeType, String source, RMissing path, @Cached("mimeType") String cachedMimeType, @Cached("source") String cachedSource, @Cached("createCall(mimeType, source)") DirectCallNode call) { @@ -133,9 +133,10 @@ public class FastRInterop { return parse(mimeType, source).call(); } + @SuppressWarnings("unused") @Specialization() @TruffleBoundary - protected Object eval(@SuppressWarnings("unused") RMissing mimeType, String source, @SuppressWarnings("unused") RMissing path) { + protected Object eval(RMissing mimeType, String source, RMissing path) { throw RError.error(this, RError.Message.INVALID_ARG, "mimeType"); } @@ -440,7 +441,8 @@ public class FastRInterop { @TruffleBoundary public TruffleObject javaClass(String clazz, boolean silent) { try { - return JavaInterop.asTruffleObject(Class.forName(clazz.replaceAll("/", "."))); + ClassLoader interopClassLoader = RContext.getInstance().getInteropClassLoader(); + return JavaInterop.asTruffleObject(interopClassLoader.loadClass(clazz.replaceAll("/", "."))); } catch (ClassNotFoundException | SecurityException | IllegalArgumentException e) { if (silent) { return RNull.instance; @@ -450,6 +452,36 @@ public class FastRInterop { } } + @RBuiltin(name = "java.addClasspathEntry", visibility = OFF, kind = PRIMITIVE, parameterNames = {"value", "silent"}, behavior = COMPLEX) + public abstract static class JavaAddClasspathEntry extends RBuiltinNode.Arg2 { + + static { + Casts casts = new Casts(JavaAddClasspathEntry.class); + casts.arg("value").mustBe(stringValue()).asStringVector(); + casts.arg("silent").mapMissing(Predef.constant(RRuntime.LOGICAL_FALSE)).mustBe(logicalValue().or(Predef.nullValue())).asLogicalVector().mustBe(singleElement()).findFirst().mustBe( + notLogicalNA()).map(Predef.toBoolean()); + } + + @Specialization + @TruffleBoundary + public TruffleObject addEntries(RAbstractStringVector value, boolean silent) { + try { + RContext ctx = RContext.getInstance(); + String[] entriesArr = new String[value.getLength()]; + for (int i = 0; i < value.getLength(); i++) { + entriesArr[i] = value.getDataAt(i); + } + ctx.addInteropClasspathEntries(entriesArr); + return value; + } catch (MalformedURLException e) { + if (silent) { + return RNull.instance; + } + throw error(RError.Message.GENERIC, "error while adding classpath entry: " + e.getMessage()); + } + } + } + @ImportStatic({RRuntime.class}) @RBuiltin(name = "java.class", visibility = ON, kind = PRIMITIVE, parameterNames = {"class"}, behavior = COMPLEX) public abstract static class JavaClassName extends RBuiltinNode.Arg1 { diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java index b8f37f4b22ff8ed51c3e44747111c401049df864..d12bdff737d486141602d4b7d42c4a1d42bc3dae 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java @@ -26,7 +26,11 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.Closeable; import java.lang.ref.WeakReference; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.EnumSet; import java.util.HashMap; import java.util.TimeZone; @@ -248,6 +252,9 @@ public final class RContext implements RTruffleObject { private PrimitiveMethodsInfo primitiveMethodsInfo; + /** Class loader for Java interop. */ + private ClassLoader interopClassLoader = getClass().getClassLoader(); + /** * Set to {@code true} when in embedded mode to allow other parts of the system to determine * whether embedded mode is in effect, <b>before</b> the initial context is created. @@ -809,4 +816,19 @@ public final class RContext implements RTruffleObject { } }; + public ClassLoader getInteropClassLoader() { + return interopClassLoader; + } + + /** + * Adds entries to the Java interop class loader. This will effectively create a new class + * loader with the previous one as parent. + */ + public void addInteropClasspathEntries(String... entries) throws MalformedURLException { + URL[] urls = new URL[entries.length]; + for (int i = 0; i < entries.length; i++) { + urls[i] = Paths.get(entries[i]).toUri().toURL(); + } + interopClassLoader = URLClassLoader.newInstance(urls, interopClassLoader); + } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RPromise.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RPromise.java index cdb65dbbb957235d9dfec9dc55625d5da653694b..d00b8e455b51a5c1f9798159a6c029ea78f64191 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RPromise.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RPromise.java @@ -203,6 +203,17 @@ public class RPromise implements RTypedValue { return value; } + /** + * Discards any previously evaluated value if this is not an eager promise. This is for + * debugging purposes only! + */ + public final void resetValue() { + // Only non-eager promises can be reset. + if (!PromiseState.isEager(state)) { + value = null; + } + } + /** * Used in case the {@link RPromise} is evaluated outside. * diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/interop/R2Foreign.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/interop/R2Foreign.java index 044a6e17d93da9ca5fc7e79a67f8846d6f870d44..84139962e0c9a827b4d07e0f93614741b74ea5e4 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/interop/R2Foreign.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/interop/R2Foreign.java @@ -24,15 +24,12 @@ package com.oracle.truffle.r.runtime.interop; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.interop.TruffleObject; -import com.oracle.truffle.api.interop.java.JavaInterop; import com.oracle.truffle.r.runtime.RRuntime; import com.oracle.truffle.r.runtime.data.RInteropScalar.RInteropByte; import com.oracle.truffle.r.runtime.data.RInteropScalar.RInteropChar; import com.oracle.truffle.r.runtime.data.RInteropScalar.RInteropFloat; import com.oracle.truffle.r.runtime.data.RInteropScalar.RInteropLong; import com.oracle.truffle.r.runtime.data.RInteropScalar.RInteropShort; -import com.oracle.truffle.r.runtime.data.RNull; import com.oracle.truffle.r.runtime.data.RRaw; import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; @@ -91,7 +88,7 @@ public abstract class R2Foreign extends RBaseNode { } @Specialization(guards = "vec.getLength() == 1") - public String doStrignVector(RAbstractStringVector vec) { + public String doStringVector(RAbstractStringVector vec) { return vec.getDataAt(0); } @@ -120,12 +117,6 @@ public abstract class R2Foreign extends RBaseNode { return obj.getValue(); } - @Specialization - public TruffleObject doNull(@SuppressWarnings("unused") RNull obj) { - // TODO this is java interop specific - return JavaInterop.asTruffleObject(null); - } - @Fallback public static Object doObject(Object obj) { return obj; diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/fastr/TestJavaInterop.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/fastr/TestJavaInterop.java index 1254ab8c5d07ab638ac3f471ddafa3eec96652ee..6ac5634afde57abf29bc2ace4d80fa77dac4f8e5 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/fastr/TestJavaInterop.java +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/fastr/TestJavaInterop.java @@ -22,7 +22,6 @@ */ package com.oracle.truffle.r.test.library.fastr; -import com.oracle.truffle.r.nodes.builtin.base.printer.DoubleVectorPrinter; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -38,6 +37,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.oracle.truffle.r.nodes.builtin.base.printer.DoubleVectorPrinter; import com.oracle.truffle.r.nodes.builtin.fastr.FastRInterop; import com.oracle.truffle.r.runtime.RType; import com.oracle.truffle.r.test.TestBase; @@ -265,7 +265,7 @@ public class TestJavaInterop extends TestBase { assertEvalFastR("tc <- new.java.class('" + Long.class.getName() + "'); t <- new.external(tc, as.external.long(1)); t", "1"); assertEvalFastR("tc <- new.java.class('" + Short.class.getName() + "'); t <- new.external(tc, as.external.short(1)); t", "1"); assertEvalFastR("tc <- new.java.class('" + String.class.getName() + "'); t <- new.external(tc, 'abc'); t", "'abc'"); - assertEvalFastR("tc <- new.java.class('" + TestNullClass.class.getName() + "'); t <- new.external(tc, NULL); class(t)", "'" + RType.TruffleObject.getName() + "'"); + assertEvalFastR(Ignored.Unknown, "tc <- new.java.class('" + TestNullClass.class.getName() + "'); t <- new.external(tc, NULL); class(t)", "'" + RType.TruffleObject.getName() + "'"); } @Test @@ -647,7 +647,7 @@ public class TestJavaInterop extends TestBase { } @Test - public void testIf() throws IllegalArgumentException, IllegalAccessException { + public void testIf() throws IllegalArgumentException { TestClass t = new TestClass(); assertEvalFastR(CREATE_TRUFFLE_OBJECT + "if(to$fieldBoolean) print('OK')", "if(T) print('OK')"); @@ -671,7 +671,7 @@ public class TestJavaInterop extends TestBase { } @Test - public void testWhile() throws IllegalArgumentException, IllegalAccessException { + public void testWhile() throws IllegalArgumentException { TestClass t = new TestClass(); assertEvalFastR(CREATE_TRUFFLE_OBJECT + "while(to$fieldBoolean) {print('OK'); break;}", "while(T) {print('OK'); break;}"); @@ -782,10 +782,11 @@ public class TestJavaInterop extends TestBase { StringBuilder sb = new StringBuilder(); sb.append(asXXX); sb.append("("); + Object val = o; if (asXXX.equals("as.character") && (o instanceof Double || o instanceof Float)) { - o = DoubleVectorPrinter.encodeReal(((Number) o).doubleValue()); + val = DoubleVectorPrinter.encodeReal(((Number) o).doubleValue()); } - sb.append(getRValue(o)); + sb.append(getRValue(val)); sb.append(')'); return sb.toString(); } @@ -802,7 +803,7 @@ public class TestJavaInterop extends TestBase { return sb.toString(); } - private String getBooleanPrefix(Object value, int i) { + private static String getBooleanPrefix(Object value, int i) { if (value.getClass().getComponentType() == Boolean.TYPE && (boolean) Array.get(value, i)) { return " "; } @@ -813,7 +814,7 @@ public class TestJavaInterop extends TestBase { return ""; } - private String toArrayClassName(String className, int dims) { + private static String toArrayClassName(String className, int dims) { StringBuilder sb = new StringBuilder(); sb.append("'"); for (int i = 0; i < dims; i++) { diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java index 134b0b01d3aeaa3f7ebe0ea709c59639fd178f46..ecc44d636bcc3f2e2f4b00347bacc0ddbbf2ee2f 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java @@ -38,6 +38,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import com.oracle.truffle.api.debug.Breakpoint; @@ -154,6 +155,39 @@ public class FastRDebugTest { assertEquals("Factorial computed OK", 2, n.intValue()); } + /** + * Test is currently ignored because of missing functionality in Truffle. + */ + @Test + @Ignore + public void testConditionalBreakpoint() throws Throwable { + final Source source = RSource.fromTextInternal("main <- function() {\n" + + " for(i in seq(10)) {\n" + + " print(i)\n" + + " }\n" + + "}\n", + RSource.Internal.DEBUGTEST_DEBUG); + engine.eval(source); + + run.addLast(() -> { + assertNull(suspendedEvent); + assertNotNull(debuggerSession); + Breakpoint breakpoint = Breakpoint.newBuilder(source).lineIs(3).build(); + breakpoint.setCondition("i == 5"); + debuggerSession.install(breakpoint); + }); + + assertLocation(3, "print(i)", "i", "5"); + continueExecution(); + + // Init before eval: + performWork(); + + final Source evalSrc = RSource.fromTextInternal("main()\n", RSource.Internal.DEBUGTEST_DEBUG); + engine.eval(evalSrc); + assertExecutedOK(); + } + @Test public void stepInStepOver() throws Throwable { final Source factorial = createFactorial(); diff --git a/documentation/tutorials/debugging/InteropDebugging/JS/main.js b/documentation/tutorials/debugging/InteropDebugging/JS/main.js new file mode 100644 index 0000000000000000000000000000000000000000..a1d81827fbda13ae1b5ee5f906f78c4f8a1ec3b3 --- /dev/null +++ b/documentation/tutorials/debugging/InteropDebugging/JS/main.js @@ -0,0 +1,5 @@ +var greeting = "Ahoy"; +var greet = function (x) { + console.log(x); +} +greet(greeting); \ No newline at end of file diff --git a/documentation/tutorials/debugging/InteropDebugging/R/main.r b/documentation/tutorials/debugging/InteropDebugging/R/main.r new file mode 100644 index 0000000000000000000000000000000000000000..bff4979782d79865eb19346a5948be20224ec229 --- /dev/null +++ b/documentation/tutorials/debugging/InteropDebugging/R/main.r @@ -0,0 +1,40 @@ +# +# Copyright (c) 2017, 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. +# +# 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. +# + +print("Hello, World! (from file)") +print("Creating a Java object in FastR") + +clazz <- .fastr.java.class("java.util.Date") +obj <- .fastr.interop.new(clazz, .fastr.interop.toLong(as.integer(Sys.time())*1000)) +print(obj$toString()) + +# add classpath entry to be able to use our class +java.addClasspathEntry("build/classes") +clazz <- .fastr.java.class("com.oracle.truffle.r.JavaMessage") +obj <- .fastr.interop.new(clazz, "Hi there") +print(obj$getMessage()) + +JS_MIME_TYPE <- "application/javascript" +.fastr.interop.eval(JS_MIME_TYPE, 'var s = "Hello from Javascript"; print(s)') +.fastr.interop.evalFile("JS/main.js", JS_MIME_TYPE) + diff --git a/documentation/tutorials/debugging/InteropDebugging/manifest.mf b/documentation/tutorials/debugging/InteropDebugging/manifest.mf new file mode 100644 index 0000000000000000000000000000000000000000..328e8e5bc3b7f1f7bad2bc0751a933e00c801983 --- /dev/null +++ b/documentation/tutorials/debugging/InteropDebugging/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/documentation/tutorials/debugging/InteropDebugging/src/com/oracle/truffle/r/JavaMessage.java b/documentation/tutorials/debugging/InteropDebugging/src/com/oracle/truffle/r/JavaMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..a6ea29c3593eadf08671ee6bda744263f420bb97 --- /dev/null +++ b/documentation/tutorials/debugging/InteropDebugging/src/com/oracle/truffle/r/JavaMessage.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, 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. + * + * 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. + */ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.oracle.truffle.r; + +public class JavaMessage { + + private String message; + + public JavaMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + @Override + public String toString() { + return message; + } + +} diff --git a/documentation/tutorials/debugging/InteropDebugging/src/com/oracle/truffle/r/Main.java b/documentation/tutorials/debugging/InteropDebugging/src/com/oracle/truffle/r/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..300a6e6390f598af2856a26f78a8fb2bfab53278 --- /dev/null +++ b/documentation/tutorials/debugging/InteropDebugging/src/com/oracle/truffle/r/Main.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017, 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. + * + * 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. + */ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.oracle.truffle.r; + +import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.vm.PolyglotEngine; +import java.io.File; +import java.io.IOException; + +public class Main { + + private static final String R_MIME_TYPE = "application/x-r"; + + /** + * @param args the command line arguments + * @throws java.io.IOException + */ + public static void main(String[] args) throws IOException { + PolyglotEngine newVM = PolyglotEngine.newBuilder().config(R_MIME_TYPE, "debugContext", null).build(); + newVM.eval(fromString("print('Hello, World! (from string)')")); + newVM.eval(fromFile("R/main.r")); + } + + private static Source fromString(String code) { + return Source.newBuilder(code).name("<shell_input>").mimeType(R_MIME_TYPE).interactive().build(); + } + + private static Source fromFile(String path) throws IOException { + return Source.newBuilder(new File(path)).mimeType(R_MIME_TYPE).build(); + } + +} diff --git a/documentation/tutorials/debugging/R/binSearch.r b/documentation/tutorials/debugging/R/binSearch.r new file mode 100644 index 0000000000000000000000000000000000000000..4290216371d9b8f4725d7d2ee94d6cabf7dd1c5c --- /dev/null +++ b/documentation/tutorials/debugging/R/binSearch.r @@ -0,0 +1,37 @@ +# +# Copyright (c) 2017, 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. +# +# 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. +# +# Returns the index of the value in the sorted vector. +binSearch <- function(vec, value) { + low <- 1L + high <- length(vec)+1L + while(low < high) { + idx <- as.integer((low + high) / 2L) + if(vec[[idx]] == value) { + return (idx) + } else if(vec[[idx]] < value) { + low <- idx + } else { + high <- idx + } + } +} diff --git a/documentation/tutorials/debugging/R/dummy.r b/documentation/tutorials/debugging/R/dummy.r new file mode 100644 index 0000000000000000000000000000000000000000..a9904460ee5e673f58340dc3aca3f55eb169eee7 --- /dev/null +++ b/documentation/tutorials/debugging/R/dummy.r @@ -0,0 +1,32 @@ +# +# Copyright (c) 2017, 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. +# +# 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. +# + +bar <- function(x) print(x) + +fun <- function(x) { + print("Hello") + for(i in seq(3)) print(i) + bar("World") + print(x) +} + diff --git a/documentation/tutorials/debugging/R/dump.r b/documentation/tutorials/debugging/R/dump.r new file mode 100644 index 0000000000000000000000000000000000000000..fc8a8b5b3c81dc6c8c6df58681101e4005e347e0 --- /dev/null +++ b/documentation/tutorials/debugging/R/dump.r @@ -0,0 +1,34 @@ +# +# Copyright (c) 2017, 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. +# +# 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. +# + +gen_data <- function() { + res <- list(rep(0, 100)) + for(i in seq_along(res)) { + res[[i]] <- i %% 5 + } + res +} + +library(jsonlite) +jsonStr <- toJSON(gen_data()) +cat(jsonStr) diff --git a/documentation/tutorials/debugging/tutorial.md b/documentation/tutorials/debugging/tutorial.md new file mode 100644 index 0000000000000000000000000000000000000000..6fb1eaf747af581308f67e0121c723adb071dac2 --- /dev/null +++ b/documentation/tutorials/debugging/tutorial.md @@ -0,0 +1,148 @@ +# FastR NetBeans Debugging Tutorial + +## Getting started + +Read the documentation in *documentation/debugging.md* in FastR's GitHub repository. +In order to suppress annoying stack traces when you (accidentally) enter values in the variables view, start FastR with option `--J @-Dtruffle.nbdebug.supressLangErrs=true`. + +Let's start with a simple example. +```R +source("R/binSearch.r") +binSearch(1:100, 1) +binSearch(1:100, 100) +binSearch(1:100, 50) +binSearch(1:100, 67) +binSearch(1:100, 0) +binSearch(1:100, 101) # why does this not stop +``` + +Set a line breakpoint in function binSearch, step through the loop iterations and find the problem. + +## Debugging Packages 1 +Packages are usually loaded lazily from a binary file containing the serialized code and data of the package. +Therefore, you can usually only install a function breakpoint to package code. +However, FastR keeps the package source such that you can set a line breakpoint in the package's source files. + +Determine the path where packages are installed: +```R +.libPaths() +``` + +To demonstrate this, start FastR in debug mode and attach the NetBeans debugger. +Then enter `.libPaths()` to determine where your R packages are installed. + +For example, let's debug package *jsonlite* (so, if you haven't installed it, do so by typing `install.packages("jsonlite")`). +This tutorial assumes *jsonlite* in version 1.5. +Go to the installation directory from package *jsonlite* and open the file *jsonlite/R/toJSON.R*. +Set a line breakpoint at line 32 which calls the generic `asJSON` function. +Now, run our script *dump.r* using `source("R/dump.r")`. +As soon as the debugging cursor stops at the line breakpoint, step into the call of function *asJSON* to find out which of the concrete implementations is actually executed. + +## Debugging Packages 2 +For some reason, it may be that packages do not have source code available. +In this case installing line breakpoints is not straigt forward. +Therefore, FastR provides a facility to query the source of an R function. +As in GnuR, if the source is not available for the function, the function's body is deparsed and a string representation is generated. +FastR then generates a temporary source file containing the deparsed source code. +This temporary source file can be queried using function `.fastr.srcinfo`. + +Let's work through an example: +```R +source("R/dummy.r") +fun +attributes(fun) +``` + +Actually, *fun* has just been sourced and we would expect that there is a *srcref* attribute. +Let's have a look what function *source* is actually doing: + +```R +> .fastr.srcinfo(source) +[1] "/tmp/deparse/source-58f3b608a4.r#1" +``` + +This output means that FastR generated a temporary source file "/deparse/source-58f3b608a4.r" and function *source* starts at line 1. +Open the file in NetBeans (File -> Open File ...) and set a breakpoint at line 62. + +```R +source("R/dummy.r") +``` + +Open the __Evaluate Expression__ view under menu Debug -> Evaluate Expression. +Type *isTRUE(keep.source)* and press <ctrl> + <enter>. +The result of the evaluation should be TRUE. So, why there are no source reference attributes? +Continue stepping until line 89 (*.Internal(parse(file, n = -1, NULL, "?", srcfile, encoding))*). +This line calls the internal parse function which we unfortunately cannot step into because internal functions do not have R code. +They are implemented in Java or C. +Now, step over line 89 and evaluate the expression: *attributes(exprs)* +The results shows that the resulting expressions actually have source reference attributes. +Now, keep stepping until line 132 (*ei <- exprs[i]*) which copies one of the elements of the parsed expression. +Now, evaluate expression: *attributes(exprs)* +It turns out that the subexpression does not have any attributes. +The reason is that FastR does not create source reference attributes in the parser because the source information is stored differently. + +## Inspecting Promises +Promises are in particular difficult to handle during debugging. +The maxime of debugging is to not modify the program as it could change its behavior and make any debugging difficult. +However, this means that you will often not be able to inspect a promise until it is too late since a promise is evaluated at the time it is used. +FastR allows to inspect promises in the variables view. +Every promise (a function parameter) has three fields: `value`, `isEvaluated`, and `isEager` +If `isEager` is `true`, then you will immediately see the value of the promise. An eager promise is a special kind where FastR eagerly evaluated the value for performance reasons. +In this case it is safe to have the value already available without changing the semantics of the program. +If `isEager` is `FALSE`, then `isEvaluated` will initially also be `FALSE` and `value` will be `NULL`. +As soon as the executed function uses the parameter, the promise will be evaluated and `isEvaluated` becomes `TRUE`. +Since the function may never use the value, it is possible to inspect the promise's value by manually setting `isEvaluated` to `TRUE` in the variables view. +The promise is now evaluated and its value can be inspected. +In order to reset the promise to its state before, you can simply set `isEvaluated` to `FALSE` again. + +## GraalVM-featured +FastR is part of the Graal/Truffle world and it is therefore easily possible to write R applications that interact with other programming languages like Java. +FastR has its dedicated Java interoperability API that allows to create and use Java objects. +The NetBeans debugger is also capable of stepping over language boundaries. + +### Preparation + +1. Download GraalVM from [Oracle Technology Network (OTN)](http://www.oracle.com/technetwork/oracle-labs/program-languages/downloads/index.html) and extract the archive. +2. Open NetBeans and add GraalVM as Java Platform: + 1. Tools -> Java Platforms + 2. Add Platform ... + 3. Java Standard Edition -> Next + 4. Navigate to the extracted folder of GraalVM and select the __jdk__ subfolder and click *Next*. + 5. Specify an appropriate platform name like __GraalVM JDK__ and click finish. +3. Open the NetBeans project *InteropDebugging* and ensure that it uses __GraalVM JDK__ as platform: + 1. Right click on the project and select *Properties*. + 2. Select *Libraries* and choose __GraalVM JDK__ in the dropdown menu labeled with *Java Platform:*. +4. To be able to build the project, ensure that the library *truffle-api.jar* is imported correctly. + * The easiest way is to copy or link the wohle GraalVM into the project's root folder using the folder name `graalvm`. + * Otherwise: + 1. Right click on the project and select *Properties*. + 2. Then select entry *Libraries*, select the *Compile* tab and look for *Classpath*. + 3. Click on *...* and add file *graalvm/lib/truffle/truffle-api.jar*, where *graalvm* is the folder where you extracted the downloaded GraalVM into. +5. Clean and build project *InteropDebugging*. + +### Inter-language Debugging + +File `Main.java` creates a `PolyglotEngine` object that can execute R code. This is basically the FastR engine. +The engine object can now run R code by creating a source object (representing R code) and submitting the source to the engine. +The expression `fromString("print('Hello, World! (from string)')")` creates a source code from a string. +Expression `fromFile("R/main.r")` creates source code from file *R/main.r*. + +Now, set a line breakpoint at line 46 (the second eval expression), build the project and run the Java application using GraalVM on the command line: +`graalvm-<version>/bin/graalvm -J:-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -cp build/classes com.oracle.truffe.r.Main` +Attach the NetBeans debugger as described in *documentation/debugging.md* via Debug -> Attach Debugger. +Once the debugger breaks at line 46, you can step into the R application (Debug -> Step Into). +The debugging cursor will next arrive in file *R/main.r*. + +File *R/main.r* also uses language interoperability to create a Java object and to run JavaScript code. +We can easily debug the executed Java code by setting a breakpoint in method `java.util.Date.toString`. +During stepping through the R program, you will also step into the Java code. + +Next, lines 31 to 35 in *R/main.r* instantiate an object of a class in our NetBeans Java project. +Before we can use our class *JavaMessage*, we need to add this project to the class path for the Java interoperability. +This is done by statement `java.addClasspathEntry("build/classes")`. +You can now also set a breakpoint in the `getMessage()` method and the debugger will halt on this breakpoint if the R expression `obj$getMessage()` is evaluated. + +Lines 38 and 39 further evaluate code of a different language, namely JavaScript. +If you have stepped to this call, you will be able to step into the JavaScript program. +You can then continue your debugging activities in the JavaScript program and you will return to the origin. +