diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PromiseHelperNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PromiseHelperNode.java index 88e8721509faa6022076613720524b24c411dc42..ba04478ae079e9bb81f2890f7828f1282f49dd57 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PromiseHelperNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/PromiseHelperNode.java @@ -36,6 +36,7 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.profiles.BranchProfile; import com.oracle.truffle.api.profiles.ConditionProfile; +import com.oracle.truffle.api.profiles.PrimitiveValueProfile; import com.oracle.truffle.api.profiles.ValueProfile; import com.oracle.truffle.r.nodes.InlineCacheNode; import com.oracle.truffle.r.nodes.function.opt.ShareObjectNode; @@ -91,7 +92,7 @@ public class PromiseHelperNode extends RBaseNode { @TruffleBoundary public boolean deoptimizeFrame(MaterializedFrame frame) { boolean deoptOne = false; - for (FrameSlot slot : frame.getFrameDescriptor().getSlots()) { + for (FrameSlot slot : frame.getFrameDescriptor().getSlots().toArray(new FrameSlot[0])) { // We're only interested in RPromises if (slot.getKind() != FrameSlotKind.Object) { continue; @@ -117,7 +118,7 @@ public class PromiseHelperNode extends RBaseNode { } private boolean deoptimize(RPromise promise) { - if (!promise.getState().isDefaultOpt()) { + if (!PromiseState.isDefaultOpt(promise.getState())) { deoptimizeProfile.enter(); EagerPromiseBase eager = (EagerPromiseBase) promise; return eager.deoptimize(); @@ -135,7 +136,7 @@ public class PromiseHelperNode extends RBaseNode { @Children private final WrapArgumentNode[] wrapNodes = new WrapArgumentNode[ArgumentStatePush.MAX_COUNTED_ARGS]; private final ConditionProfile shouldWrap = ConditionProfile.createBinaryProfile(); - private final ValueProfile optStateProfile = ValueProfile.createIdentityProfile(); + @CompilationFinal private PrimitiveValueProfile optStateProfile = PrimitiveValueProfile.createEqualityProfile(); private final ValueProfile isValidAssumptionProfile = ValueProfile.createIdentityProfile(); private final ValueProfile promiseFrameProfile = ValueProfile.createClassProfile(); @@ -155,9 +156,16 @@ public class PromiseHelperNode extends RBaseNode { } Object obj; - PromiseState state = optStateProfile.profile(promise.getState()); - if (state.isDefaultOpt()) { - obj = generateValueDefault(frame, state, promise); + int state = optStateProfile.profile(promise.getState()); + if (PromiseState.isExplicit(state)) { + CompilerDirectives.transferToInterpreter(); + // reset profiles, this is very likely a one-time event + isEvaluatedProfile = ConditionProfile.createBinaryProfile(); + optStateProfile = PrimitiveValueProfile.createEqualityProfile(); + return evaluateSlowPath(frame, promise); + } + if (PromiseState.isDefaultOpt(state)) { + obj = generateValueDefault(frame, promise); } else { obj = generateValueEager(frame, state, (EagerPromiseBase) promise); } @@ -171,7 +179,7 @@ public class PromiseHelperNode extends RBaseNode { } } - private Object generateValueDefault(VirtualFrame frame, PromiseState state, RPromise promise) { + private Object generateValueDefault(VirtualFrame frame, RPromise promise) { // Check for dependency cycle if (isUnderEvaluation(promise)) { throw RError.error(RError.SHOW_CALLER, RError.Message.PROMISE_CYCLE); @@ -181,32 +189,29 @@ public class PromiseHelperNode extends RBaseNode { CompilerDirectives.transferToInterpreterAndInvalidate(); promiseClosureCache = insert(InlineCacheNode.createPromise(FastROptions.PromiseCacheSize.getNonNegativeIntValue())); } + promise.setUnderEvaluation(); if (isInOriginFrame(frame, promise)) { - // state change must happen inside of conditional as isInOriginalFrame checks the - // state - promise.setState(PromiseState.UnderEvaluation); return promiseClosureCache.execute(frame, promise.getClosure()); } else { - promise.setState(PromiseState.UnderEvaluation); Frame promiseFrame = promiseFrameProfile.profile(promise.getFrame()); assert promiseFrame != null; return promiseClosureCache.execute(wrapPromiseFrame(frame, promiseFrame), promise.getClosure()); } } finally { - promise.setState(state); + promise.resetUnderEvaluation(); } } - private Object generateValueEager(VirtualFrame frame, PromiseState state, EagerPromiseBase promise) { - assert state.isEager() || state == PromiseState.Promised; + private Object generateValueEager(VirtualFrame frame, int state, EagerPromiseBase promise) { + assert !PromiseState.isDefaultOpt(state); if (!isDeoptimized(promise)) { Assumption eagerAssumption = isValidAssumptionProfile.profile(promise.getIsValidAssumption()); if (eagerAssumption.isValid()) { - if (state == PromiseState.Promised) { + if (!PromiseState.isEager(state)) { RPromise nextPromise = (RPromise) promise.getEagerValue(); return checkNextNode().evaluate(frame, nextPromise); } else { - assert state.isEager(); + assert PromiseState.isEager(state); return getEagerValue(frame, (EagerPromise) promise); } } else { @@ -218,7 +223,7 @@ public class PromiseHelperNode extends RBaseNode { } } // Call - return generateValueDefault(frame, state, promise); + return generateValueDefault(frame, promise); } public static Object evaluateSlowPath(VirtualFrame frame, RPromise promise) { @@ -227,11 +232,21 @@ public class PromiseHelperNode extends RBaseNode { return promise.getValue(); } + int state = promise.getState(); + if (PromiseState.isExplicit(state)) { + synchronized (promise) { + if (promise.isEvaluated()) { + return promise.getValue(); + } + Object obj = generateValueDefaultSlowPath(frame, promise); + promise.setValue(obj); + return obj; + } + } Object obj; - PromiseState state = promise.getState(); - if (state.isDefaultOpt()) { + if (PromiseState.isDefaultOpt(state)) { // Evaluate guarded by underEvaluation - obj = generateValueDefaultSlowPath(frame, state, promise); + obj = generateValueDefaultSlowPath(frame, promise); } else { obj = generateValueEagerSlowPath(frame, state, (EagerPromiseBase) promise); } @@ -239,13 +254,13 @@ public class PromiseHelperNode extends RBaseNode { return obj; } - private static Object generateValueDefaultSlowPath(VirtualFrame frame, PromiseState state, RPromise promise) { + private static Object generateValueDefaultSlowPath(VirtualFrame frame, RPromise promise) { // Check for dependency cycle if (promise.isUnderEvaluation()) { throw RError.error(RError.SHOW_CALLER, RError.Message.PROMISE_CYCLE); } try { - promise.setState(PromiseState.UnderEvaluation); + promise.setUnderEvaluation(); if (promise.isInOriginFrame(frame)) { return promise.getClosure().eval(frame.materialize()); @@ -257,7 +272,7 @@ public class PromiseHelperNode extends RBaseNode { return promise.getClosure().eval(promiseFrame.materialize()); } } finally { - promise.setState(state); + promise.resetUnderEvaluation(); } } @@ -266,16 +281,15 @@ public class PromiseHelperNode extends RBaseNode { RCaller.createForPromise(RArguments.getCall(promiseFrame), frame)); } - private static Object generateValueEagerSlowPath(VirtualFrame frame, PromiseState state, EagerPromiseBase promise) { - assert state.isEager() || state == PromiseState.Promised; + private static Object generateValueEagerSlowPath(VirtualFrame frame, int state, EagerPromiseBase promise) { + assert !PromiseState.isDefaultOpt(state); if (!promise.isDeoptimized()) { Assumption eagerAssumption = promise.getIsValidAssumption(); if (eagerAssumption.isValid()) { - if (state == PromiseState.Promised) { + if (!PromiseState.isEager(state)) { RPromise nextPromise = (RPromise) promise.getEagerValue(); return evaluateSlowPath(frame, nextPromise); } else { - assert state.isEager(); Object o = promise.getEagerValue(); if (promise.wrapIndex() != ArgumentStatePush.INVALID_INDEX) { return ShareObjectNode.share(o); @@ -290,7 +304,7 @@ public class PromiseHelperNode extends RBaseNode { } } // Call - return generateValueDefaultSlowPath(frame, state, promise); + return generateValueDefaultSlowPath(frame, promise); } /** @@ -298,7 +312,7 @@ public class PromiseHelperNode extends RBaseNode { * <code>null</code> */ public void materialize(RPromise promise) { - if (isOptEagerProfile.profile(promise.getState().isEager()) || isOptPromisedProfile.profile(promise.getState() == PromiseState.Promised)) { + if (isDefaultOptProfile.profile(!PromiseState.isDefaultOpt(promise.getState()))) { EagerPromiseBase eager = (EagerPromiseBase) promise; eager.materialize(); } @@ -332,7 +346,7 @@ public class PromiseHelperNode extends RBaseNode { return isEvaluatedProfile.profile(promise.isEvaluated()); } - private final ConditionProfile isEvaluatedProfile = ConditionProfile.createBinaryProfile(); + @CompilationFinal private ConditionProfile isEvaluatedProfile = ConditionProfile.createBinaryProfile(); private final ConditionProfile underEvaluationProfile = ConditionProfile.createBinaryProfile(); private final ConditionProfile isNullFrameProfile = ConditionProfile.createBinaryProfile(); @@ -342,8 +356,7 @@ public class PromiseHelperNode extends RBaseNode { private final ValueProfile valueProfile = ValueProfile.createClassProfile(); // Eager - private final ConditionProfile isOptEagerProfile = ConditionProfile.createBinaryProfile(); - private final ConditionProfile isOptPromisedProfile = ConditionProfile.createBinaryProfile(); + private final ConditionProfile isDefaultOptProfile = ConditionProfile.createBinaryProfile(); private final ConditionProfile isDeoptimizedProfile = ConditionProfile.createBinaryProfile(); private final ValueProfile eagerValueProfile = ValueProfile.createClassProfile(); diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RMissingHelper.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RMissingHelper.java index 5ccc43ccb139d395bf6965c5e3e15d27ace623bb..2e26f58248200f61c0a9b8aaac51c7b5a925dff7 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RMissingHelper.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RMissingHelper.java @@ -32,7 +32,6 @@ import com.oracle.truffle.r.runtime.data.REmpty; import com.oracle.truffle.r.runtime.data.RMissing; import com.oracle.truffle.r.runtime.data.RPromise; import com.oracle.truffle.r.runtime.data.RPromise.EagerPromiseBase; -import com.oracle.truffle.r.runtime.data.RPromise.PromiseState; /** * This class implements the behavior for {@link RMissing} which is needed inside this module, as it @@ -130,12 +129,11 @@ public class RMissingHelper { return true; } - PromiseState state = promise.getState(); try { if (promise.isEvaluated()) { return false; } - promise.setState(PromiseState.UnderEvaluation); + promise.setUnderEvaluation(); // TODO Profile necessary here??? if (promise instanceof EagerPromiseBase) { EagerPromiseBase eagerPromise = (EagerPromiseBase) promise; @@ -151,7 +149,7 @@ public class RMissingHelper { // promise.materialize(globalMissingPromiseProfile); result = isMissingArgument(promise.getFrame(), rvn.getIdentifier()); } finally { - promise.setState(state); + promise.resetUnderEvaluation(); } } return result; diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/MissingNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/MissingNode.java index e148ee23a00458c5cb45193e1de508c8128a7178..27838bcaf8218bbeef4e95fd0c77d2e54b760066 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/MissingNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/signature/MissingNode.java @@ -46,7 +46,7 @@ import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.RRuntime; import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames; import com.oracle.truffle.r.runtime.data.RPromise; -import com.oracle.truffle.r.runtime.data.RPromise.PromiseState; +import com.oracle.truffle.r.runtime.data.RPromise.EagerPromise; import com.oracle.truffle.r.runtime.nodes.RSyntaxElement; import com.oracle.truffle.r.runtime.nodes.RSyntaxLookup; @@ -142,6 +142,9 @@ public final class MissingNode extends OperatorNode { if (isSymbolNullProfile.profile(symbol == null)) { return false; } else { + if (promise instanceof EagerPromise && !((EagerPromise) promise).isDeoptimized()) { + return false; + } if (recursiveDesc != null) { promiseHelper.materialize(promise); // Ensure that promise holds a frame } @@ -155,16 +158,15 @@ public final class MissingNode extends OperatorNode { if (recursiveDesc == null) { promiseHelper.materialize(promise); // Ensure that promise holds a frame } - PromiseState state = promise.getState(); try { - promise.setState(PromiseState.UnderEvaluation); + promise.setUnderEvaluation(); if (recursive == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); recursive = insert(MissingCheckCache.create(level + 1)); } return recursive.execute(promise.getFrame(), symbol); } finally { - promise.setState(state); + promise.resetUnderEvaluation(); } } } 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 474e23cade152451b208fb3542c420bb9ae65cc4..3e4b0807dd59742ab524841ad766da70654a62a2 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 @@ -49,6 +49,13 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxLookup; @ValueType public class RPromise implements RTypedValue { + private static final int DEFAULT_BIT = 0x1; + private static final int FULL_PROMISE_BIT = 0x2; + private static final int EAGER_BIT = 0x4; + private static final int EXPLICIT_BIT = 0x8; + private static final int UNDER_EVALUATION_BIT = 0x10; + private static final int UNDER_EVALUATION_MASK = 0x0f; + /** * This enum encodes the source, optimization and current state of a promise. */ @@ -57,45 +64,55 @@ public class RPromise implements RTypedValue { * This promise is created for an argument that has been supplied to the function call and * thus has to be evaluated inside the caller frame. */ - Supplied, + Supplied(FULL_PROMISE_BIT), /** * This promise is created for an argument that was 'missing' at the function call and thus * contains it's default value and has to be evaluated inside the _callee_ frame. */ - Default, + Default(DEFAULT_BIT | FULL_PROMISE_BIT), /** * A supplied promise that was optimized to eagerly evaluate its value. */ - EagerSupplied, + EagerSupplied(EAGER_BIT), /** * A default promise that was optimized to eagerly evaluate its value. */ - EagerDefault, + EagerDefault(EAGER_BIT | DEFAULT_BIT), /** * This promise was created to wrap around a parameter value that is a promise itself. */ - Promised, + Promised(0), /** * This promise is not a function argument at all. (Created by 'delayedAssign', for * example). */ - Explicit, + Explicit(EXPLICIT_BIT | FULL_PROMISE_BIT), /** * This promise is currently being evaluated. This necessary to avoid cyclic evaluation, and * can by checked via {@link #isUnderEvaluation()}. */ - UnderEvaluation; + UnderEvaluation(UNDER_EVALUATION_BIT); + + private final int bits; + + PromiseState(int bits) { + this.bits = bits; + } + + public static boolean isDefaultOpt(int state) { + return (state & FULL_PROMISE_BIT) != 0; + } - public boolean isDefaultOpt() { - return this == PromiseState.Default || this == PromiseState.Supplied || this == PromiseState.Explicit || this == PromiseState.UnderEvaluation; + public static boolean isEager(int state) { + return (state & EAGER_BIT) != 0; } - public boolean isEager() { - return this == PromiseState.EagerDefault || this == PromiseState.EagerSupplied; + public static boolean isExplicit(int state) { + return (state & EXPLICIT_BIT) != 0; } } - private PromiseState state; + private int state; /** * @see #getFrame() @@ -104,7 +121,7 @@ public class RPromise implements RTypedValue { @CompilationFinal protected MaterializedFrame execFrame; /** - * Might not be <code>null</code>. + * May not be <code>null</code>. */ private final Closure closure; @@ -117,7 +134,7 @@ public class RPromise implements RTypedValue { * This creates a new tuple (expr, env, closure, value=null), which may later be evaluated. */ RPromise(PromiseState state, MaterializedFrame execFrame, Closure closure) { - this.state = state; + this.state = state.bits; this.execFrame = execFrame; this.closure = closure; } @@ -127,7 +144,7 @@ public class RPromise implements RTypedValue { */ RPromise(PromiseState state, Closure closure, Object value) { assert value != null; - this.state = state; + this.state = state.bits; this.closure = closure; this.value = value; // Not needed as already evaluated: @@ -139,11 +156,11 @@ public class RPromise implements RTypedValue { return RType.Promise; } - public final PromiseState getState() { + public final int getState() { return state; } - public final void setState(PromiseState state) { + public final void setState(int state) { assert !isEvaluated(); this.state = state; } @@ -160,7 +177,7 @@ public class RPromise implements RTypedValue { } public final boolean isDefaultArgument() { - return state == PromiseState.Default || state == PromiseState.EagerDefault; + return (state & DEFAULT_BIT) != 0; } public final boolean isNullFrame() { @@ -234,7 +251,7 @@ public class RPromise implements RTypedValue { */ public final boolean isUnderEvaluation() { assert !isEvaluated(); - return state == PromiseState.UnderEvaluation; + return (state & UNDER_EVALUATION_BIT) != 0; } @Override @@ -464,7 +481,7 @@ public class RPromise implements RTypedValue { } private static RootCallTarget generateCallTarget(RNode expr) { - return RContext.getEngine().makePromiseCallTarget(expr, CLOSURE_WRAPPER_NAME); + return RContext.getEngine().makePromiseCallTarget(expr, CLOSURE_WRAPPER_NAME + System.identityHashCode(expr)); } public RBaseNode getExpr() { @@ -490,4 +507,14 @@ public class RPromise implements RTypedValue { public boolean isS4() { return false; } + + public void setUnderEvaluation() { + assert (state & UNDER_EVALUATION_BIT) == 0; + state |= UNDER_EVALUATION_BIT; + } + + public void resetUnderEvaluation() { + assert (state & UNDER_EVALUATION_BIT) != 0; + state &= UNDER_EVALUATION_MASK; + } } diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_delayedAssign.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_delayedAssign.java index a7aac98a05af286bdceb7aec8ef5ec4790d967e7..e2fd55dd3a7d8782627d65ee5f5f6d1fa7c84dcf 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_delayedAssign.java +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_delayedAssign.java @@ -31,7 +31,7 @@ public class TestBuiltin_delayedAssign extends TestBase { assertEval(Output.IgnoreErrorContext, "{ f <- function() { delayedAssign(\"x\",y); delayedAssign(\"y\",x); g(x, y)}; g <- function(x, y) { x + y }; f() }"); assertEval("{ f <- function() { delayedAssign(\"x\",y); delayedAssign(\"y\",x); list(x, y)}; f() }"); assertEval(Output.IgnoreErrorContext, "{ f <- function() { delayedAssign(\"x\",y); delayedAssign(\"y\",x); paste(x, y)}; f() }"); - assertEval("{ f <- function() { delayedAssign(\"x\",y); delayedAssign(\"y\",x); print(x, y)}; f() }"); + assertEval(Output.IgnoreErrorContext, "{ f <- function() { delayedAssign(\"x\",y); delayedAssign(\"y\",x); print(x, y)}; f() }"); assertEval("{ f <- function() { p <- 0; for (i in 1:10) { if (i %% 2 == 0) { delayedAssign(\"a\", p + 1); } else { a <- p + 1; }; p <- a; }; p }; f() }"); assertEval("{ f <- function() { x <- 4 ; delayedAssign(\"x\", y); y <- 10; x } ; f() }"); assertEval("{ h <- new.env(parent=emptyenv()) ; delayedAssign(\"x\", y, h, h) ; assign(\"y\", 2, h) ; get(\"x\", h) }");