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 4c03ebeafe358d7a88cc8456ae5ee198783faad7..a35464e857e2ca00684f30ac5faa669344e3cda5 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 @@ -158,7 +158,7 @@ final class REngine implements Engine, Engine.Timings { if (context.getKind() == RContext.ContextKind.SHARE_NOTHING) { initializeNonShared(); } - initializeRNG(); + context.stateRNG.initializeDotRandomSeed(context); } private void initializeNonShared() { @@ -208,10 +208,10 @@ final class REngine implements Engine, Engine.Timings { } } - private void initializeRNG() { + public void initializeRNG() { assert REnvironment.globalEnv() != null; RFunction fun = context.lookupBuiltin(".fastr.set.seed"); - ActiveBinding dotRandomSeed = new ActiveBinding(RType.Any, fun); + ActiveBinding dotRandomSeed = new ActiveBinding(RType.Any, fun, true); Frame frame = REnvironment.globalEnv().getFrame(); FrameSlot slot = FrameSlotChangeMonitor.findOrAddFrameSlot(frame.getFrameDescriptor(), RRNG.RANDOM_SEED, FrameSlotKind.Object); FrameSlotChangeMonitor.setActiveBinding(frame, slot, dotRandomSeed, false, null); diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/GetFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/GetFunctions.java index e8547dc985cbad602368033b7b425c51b9b78a82..c1c66e7f6d09059fe7f3a70fcc8f35dc0e766857 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/GetFunctions.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/GetFunctions.java @@ -112,7 +112,7 @@ public class GetFunctions { protected Object getAndCheck(VirtualFrame frame, String x, REnvironment env, RType modeType, String modeString, boolean fail) throws RError { Object obj = checkPromise(frame, env.get(x), x); - if (obj != null && RRuntime.checkType(obj, modeType)) { + if (obj != null && obj != RMissing.instance && RRuntime.checkType(obj, modeType)) { return obj; } else { if (fail) { @@ -136,7 +136,7 @@ public class GetFunctions { } } } - if (r == null && fail) { + if ((r == null || r == RMissing.instance) && fail) { unknownObject(x, modeType, modeString); } } diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/RNGFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/RNGFunctions.java index b1d788fb5c56c67eeaeaaafd87a24c66a9b081ea..2a762f5116d84d7d5f8521786e0d8a025d2dea2b 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/RNGFunctions.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/RNGFunctions.java @@ -119,7 +119,8 @@ public class RNGFunctions { for (int i = 0; i < arr.length; i++) { arr[i] = data.getDataAt(i); } - RContext.getInstance().stateRNG.currentSeeds = arr; + + RContext.getInstance().stateRNG.setCurrentSeeds(arr); return RNull.instance; } @@ -130,28 +131,22 @@ public class RNGFunctions { @Specialization(guards = {"isSetOperation(data)"}) @TruffleBoundary protected RNull setSeed(Object data) { - RContext.getInstance().stateRNG.currentSeeds = data; + RContext.getInstance().stateRNG.setCurrentSeeds(data); return RNull.instance; } @Specialization @TruffleBoundary protected Object getSeed(@SuppressWarnings("unused") RMissing data) { - Object seeds = RContext.getInstance().stateRNG.currentSeeds; - if (seeds == RMissing.instance) { - /* - * Some R functions, such as initDefaultClusterOptions in package parallel (since - * R3.4.0), read directly the value of the .Random.seed variable from the global - * environment. They expect NULL (not MISSING) to be returned if - * .GlobalEnv$.Random.seed is not initialized. - */ - return RNull.instance; - } + Object seeds = RContext.getInstance().stateRNG.getCurrentSeeds(); + assert seeds != RMissing.instance; if (seeds instanceof int[]) { int[] seedsArr = (int[]) seeds; return RDataFactory.createIntVector(seedsArr, RDataFactory.INCOMPLETE_VECTOR); } - assert seeds != null; + if (seeds == null) { + return RNull.instance; + } return seeds; } } diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/LocalReadVariableNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/LocalReadVariableNode.java index 809ee9face9979c4f155c1da2e4172ea185a92a4..12cbdee57697a38ca4988baeaebd72f4e6ab5d7c 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/LocalReadVariableNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/LocalReadVariableNode.java @@ -120,7 +120,11 @@ public final class LocalReadVariableNode extends Node { } // special treatment for active binding: call bound function if (!containsNoActiveBindingAssumption.isValid() && ActiveBinding.isActiveBinding(result)) { - return ((ActiveBinding) result).readValue(); + Object readValue = ((ActiveBinding) result).readValue(); + if (readValue == RMissing.instance) { + return null; + } + return readValue; } if (forceResult) { diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java index 7ba6b410c2b3af05e23eac3ea4a29cc96968e07a..7fe705bfcbefc98aad3148be1e091e9ffab3e09e 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java @@ -262,7 +262,11 @@ public final class ReadVariableNode extends RBaseNode { result = promiseHelper.evaluate(frame, (RPromise) result); } if (isActiveBindingProfile.profile(ActiveBinding.isActiveBinding(result))) { - return ((ActiveBinding) result).readValue(); + Object readValue = ((ActiveBinding) result).readValue(); + if (readValue == RMissing.instance) { + throw error(mode == RType.Function ? RError.Message.UNKNOWN_FUNCTION : RError.Message.UNKNOWN_OBJECT, identifier); + } + return readValue; } return result; } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/ActiveBinding.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/ActiveBinding.java index 2fda68c09347c0694ba0396078276920e2dae205..9e9da206a7f3a3a20a7f4826e3d724fe2c802250 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/ActiveBinding.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/ActiveBinding.java @@ -28,6 +28,7 @@ import com.oracle.truffle.r.runtime.RCaller; import com.oracle.truffle.r.runtime.RType; import com.oracle.truffle.r.runtime.context.RContext; import com.oracle.truffle.r.runtime.data.RFunction; +import com.oracle.truffle.r.runtime.data.RMissing; import com.oracle.truffle.r.runtime.data.RTruffleObject; import com.oracle.truffle.r.runtime.env.REnvironment; @@ -39,10 +40,17 @@ public class ActiveBinding implements RTruffleObject { private final RType expectedType; private final RFunction function; + private boolean initialized = false; + private boolean hidden = false; - public ActiveBinding(RType expectedType, RFunction fun) { + public ActiveBinding(RType expectedType, RFunction fun, boolean hidden) { this.expectedType = Objects.requireNonNull(expectedType); this.function = Objects.requireNonNull(fun); + this.hidden = hidden; + } + + public ActiveBinding(RType expectedType, RFunction fun) { + this(expectedType, fun, false); } public RFunction getFunction() { @@ -62,11 +70,36 @@ public class ActiveBinding implements RTruffleObject { return "active binding"; } + public boolean isHidden() { + return hidden; + } + + public boolean isInitialized() { + return initialized; + } + public Object writeValue(Object value) { - return RContext.getEngine().evalFunction(function, REnvironment.baseEnv().getFrame(), RCaller.createInvalid(null), true, null, value); + Object result = RContext.getEngine().evalFunction(function, REnvironment.baseEnv().getFrame(), RCaller.createInvalid(null), true, null, value); + initialized = true; + return result; } public Object readValue() { + if (hidden && !initialized) { + return RMissing.instance; + } return RContext.getEngine().evalFunction(function, REnvironment.baseEnv().getFrame(), RCaller.createInvalid(null), true, null); } + + public void setInitialized() { + initialized = true; + } + + public static boolean isListed(Object value) { + if (isActiveBinding(value)) { + ActiveBinding binding = (ActiveBinding) value; + return !binding.hidden || binding.initialized; + } + return true; + } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/REnvTruffleFrameAccess.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/REnvTruffleFrameAccess.java index d6d1b687c96a339e268f9952692ea4f5074d3143..5e0167b6bb22711ae5f1f6023326b8a20a4ab487 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/REnvTruffleFrameAccess.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/env/frame/REnvTruffleFrameAccess.java @@ -39,6 +39,7 @@ import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.RError.Message; import com.oracle.truffle.r.runtime.RRuntime; import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RMissing; import com.oracle.truffle.r.runtime.data.RStringVector; import com.oracle.truffle.r.runtime.env.REnvironment; import com.oracle.truffle.r.runtime.env.REnvironment.PutException; @@ -75,7 +76,10 @@ public final class REnvTruffleFrameAccess extends REnvFrameAccess { Object value = FrameSlotChangeMonitor.getValue(slot, frame); // special treatment for active binding: call bound function if (ActiveBinding.isActiveBinding(value)) { - return ((ActiveBinding) value).readValue(); + Object readValue = ((ActiveBinding) value).readValue(); + // special case: if the active binding returns RMissing, then this should behave + // like the variable does not exist. + return readValue != RMissing.instance ? readValue : null; } return value; } @@ -161,7 +165,8 @@ public final class REnvTruffleFrameAccess extends REnvFrameAccess { for (int i = 0; i < names.length; i++) { String name = names[i]; FrameSlot frameSlot = fd.findFrameSlot(name); - if (FrameSlotChangeMonitor.getValue(frameSlot, frame) == null) { + Object value = FrameSlotChangeMonitor.getValue(frameSlot, frame); + if (value == null || !ActiveBinding.isListed(value)) { continue; } if (REnvironment.includeName(name, allNames, pattern)) { diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/rng/RRNG.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/rng/RRNG.java index 42e5cfb447cd6341ed564e3e7a6ca09c4b874fde..ac18529887f7829e77c676548de99c8af0738f78 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/rng/RRNG.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/rng/RRNG.java @@ -11,17 +11,26 @@ */ package com.oracle.truffle.r.runtime.rng; +import java.lang.ref.WeakReference; import java.util.function.Supplier; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.frame.Frame; +import com.oracle.truffle.api.frame.FrameSlot; +import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.RRuntime; +import com.oracle.truffle.r.runtime.RType; import com.oracle.truffle.r.runtime.context.RContext; +import com.oracle.truffle.r.runtime.data.RFunction; import com.oracle.truffle.r.runtime.data.RIntVector; import com.oracle.truffle.r.runtime.data.RMissing; import com.oracle.truffle.r.runtime.data.RNull; import com.oracle.truffle.r.runtime.data.RTypedValue; +import com.oracle.truffle.r.runtime.env.REnvironment; +import com.oracle.truffle.r.runtime.env.frame.ActiveBinding; +import com.oracle.truffle.r.runtime.env.frame.FrameSlotChangeMonitor; import com.oracle.truffle.r.runtime.ffi.BaseRFFI; import com.oracle.truffle.r.runtime.rng.mm.MarsagliaMulticarry; import com.oracle.truffle.r.runtime.rng.mt.MersenneTwister; @@ -102,13 +111,14 @@ public class RRNG { private RandomNumberGenerator currentGenerator; private final RandomNumberGenerator[] allGenerators; private NormKind currentNormKind; + private WeakReference<ActiveBinding> dotRandomSeedBinding; /** * Stores the current RNG seed. The type is Object because the user may assign an arbitrary * value to variable {@value RRNG#RANDOM_SEED}. Allowed types are therefore any R value or * an {@code int[]}. */ - public Object currentSeeds = RMissing.instance; + private Object currentSeeds = null; private ContextStateImpl() { this.currentNormKind = DEFAULT_NORM_KIND; @@ -175,6 +185,31 @@ public class RRNG { public static ContextStateImpl newContextState() { return new ContextStateImpl(); } + + public void initializeDotRandomSeed(RContext context) { + assert REnvironment.globalEnv() != null; + RFunction fun = context.lookupBuiltin(".fastr.set.seed"); + ActiveBinding dotRandomSeed = new ActiveBinding(RType.Any, fun, true); + Frame frame = REnvironment.globalEnv().getFrame(); + FrameSlot slot = FrameSlotChangeMonitor.findOrAddFrameSlot(frame.getFrameDescriptor(), RRNG.RANDOM_SEED, FrameSlotKind.Object); + FrameSlotChangeMonitor.setActiveBinding(frame, slot, dotRandomSeed, false, null); + dotRandomSeedBinding = new WeakReference<>(dotRandomSeed); + } + + public void setCurrentSeeds(Object seeds) { + if (this.currentSeeds == null) { + ActiveBinding activeBinding = dotRandomSeedBinding.get(); + if (activeBinding != null) { + activeBinding.setInitialized(); + } + } + this.currentSeeds = seeds; + } + + public Object getCurrentSeeds() { + return this.currentSeeds; + } + } private static ContextStateImpl getContextState() { @@ -284,7 +319,7 @@ public class RRNG { @TruffleBoundary private static Object getDotRandomSeed() { - return RContext.getInstance().stateRNG.currentSeeds; + return RContext.getInstance().stateRNG.getCurrentSeeds(); } /** @@ -424,6 +459,6 @@ public class RRNG { public static void putRNGState() { int[] seeds = currentGenerator().getSeeds(); seeds[0] = currentKind().ordinal() + 100 * currentNormKind().ordinal(); - RContext.getInstance().stateRNG.currentSeeds = seeds; + RContext.getInstance().stateRNG.setCurrentSeeds(seeds); } } diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test index 062dabd99f219076bebdca2c43ad0000b500f88c..ba1df429f3d39ad44ede2563e5d988c4dd90e975 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test @@ -153836,6 +153836,64 @@ $variables list(k, y, z) +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#exists('.Random.seed', envir = .GlobalEnv, inherits = FALSE) +[1] FALSE + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ .GlobalEnv$.Random.seed } +NULL + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ .Random.seed <- c(1,2,3); .GlobalEnv$.Random.seed } +[1] 1 2 3 + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ .Random.seed <- c(1,2,3); .Random.seed } +[1] 1 2 3 + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ .Random.seed <- c(1,2,3); print(.Random.seed) } +[1] 1 2 3 + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed#Output.IgnoreErrorContext# +#{ .Random.seed } +Error: object '.Random.seed' not found + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed#Output.IgnoreErrorContext# +#{ get('.Random.seed', envir = .GlobalEnv, inherits = FALSE) } +Error in get(".Random.seed", envir = .GlobalEnv, inherits = FALSE) : + object '.Random.seed' not found + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed#Output.IgnoreErrorContext# +#{ get('.Random.seed', envir = .GlobalEnv, inherits = TRUE) } +Error in get(".Random.seed", envir = .GlobalEnv, inherits = TRUE) : + object '.Random.seed' not found + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ get0('.Random.seed', envir = .GlobalEnv, inherits = FALSE) } +NULL + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ get0('.Random.seed', envir = .GlobalEnv, inherits = TRUE) } +NULL + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed#Output.IgnoreErrorContext# +#{ print(.Random.seed) } +Error in print(.Random.seed) : object '.Random.seed' not found + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ runif(1); length(.GlobalEnv$.Random.seed) } +[1] 626 + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ runif(1); length(.Random.seed) } +[1] 626 + +##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testDotRandomSeed# +#{ runif(1); print(length(.Random.seed)) } +[1] 626 + ##com.oracle.truffle.r.test.library.stats.TestRandGenerationFunctions.testFunctions1#Output.IgnoreWhitespace# #set.seed(10); rsignrank(12, c(NA, NaN, 1/0, -1/0, -1, 1, 0.3, -0.6, 0.0653, 0.000123, 32e-80, 10)) [1] NA NA 0 NA NA 1 0 NA 0 0 0 21 diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/stats/TestRandGenerationFunctions.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/stats/TestRandGenerationFunctions.java index 56ff6bd83d9b39bc643fd66d3b1dab532d5d6a85..ff5da6387474b357da1d7fd033e1a8451627eaf6 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/stats/TestRandGenerationFunctions.java +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/stats/TestRandGenerationFunctions.java @@ -101,4 +101,24 @@ public class TestRandGenerationFunctions extends TestBase { public void testGenerators() { assertEval("for(gen in c(\"Buggy Kinderman-Ramage\", \"Ahrens-Dieter\", \"Box-Muller\", \"Inversion\", \"Kinderman-Ramage\", \"default\")) { print(paste0(gen, \":\")); RNGkind(NULL,gen); set.seed(42); print(rnorm(30)); }"); } + + @Test + public void testDotRandomSeed() { + assertEval(Output.IgnoreErrorContext, "{ .Random.seed }"); + assertEval(Output.IgnoreErrorContext, "{ print(.Random.seed) }"); + assertEval(Output.IgnoreErrorContext, "{ get('.Random.seed', envir = .GlobalEnv, inherits = FALSE) }"); + assertEval("{ get0('.Random.seed', envir = .GlobalEnv, inherits = FALSE) }"); + assertEval(Output.IgnoreErrorContext, "{ get('.Random.seed', envir = .GlobalEnv, inherits = TRUE) }"); + assertEval("{ get0('.Random.seed', envir = .GlobalEnv, inherits = TRUE) }"); + + assertEval("exists('.Random.seed', envir = .GlobalEnv, inherits = FALSE)"); + assertEval("{ .GlobalEnv$.Random.seed }"); + + assertEval("{ runif(1); length(.Random.seed) }"); + assertEval("{ runif(1); print(length(.Random.seed)) }"); + assertEval("{ runif(1); length(.GlobalEnv$.Random.seed) }"); + assertEval("{ .Random.seed <- c(1,2,3); .Random.seed }"); + assertEval("{ .Random.seed <- c(1,2,3); print(.Random.seed) }"); + assertEval("{ .Random.seed <- c(1,2,3); .GlobalEnv$.Random.seed }"); + } }