Skip to content

Commit b188bd0

Browse files
committed
Add fast-path for stopifnot
1 parent 3f922d6 commit b188bd0

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.IsElementFastPathNodeGen;
4949
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.MatrixFastPathNodeGen;
5050
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SetDiffFastPathNodeGen;
51+
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.StopifnotFastPath;
5152
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SubscriptDataFrameFastPathNodeGen;
5253
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SubsetDataFrameFastPath;
5354
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SubsetDataFrameFastPathNodeGen;
@@ -917,6 +918,7 @@ public void loadOverrides(MaterializedFrame baseFrame) {
917918
addFastPath(baseFrame, "seq.default", SeqFunctionsFactory.SeqDefaultFastPathNodeGen::create, RVisibility.ON);
918919
addFastPath(baseFrame, "seq", SeqFunctionsFactory.SeqFastPathNodeGen::create, RVisibility.ON);
919920
addFastPath(baseFrame, "match.arg", MatchArgFastPathNodeGen::create, MatchArgFastPath.class);
921+
addFastPath(baseFrame, "stopifnot", StopifnotFastPath::new, StopifnotFastPath.class);
920922

921923
setContainsDispatch(baseFrame, "eval", "[.data.frame", "[[.data.frame", "[<-.data.frame", "[[<-.data.frame");
922924
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 3 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 3 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 3 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
package com.oracle.truffle.r.nodes.builtin.base.fastpaths;
24+
25+
import static com.oracle.truffle.r.runtime.builtins.RBehavior.COMPLEX;
26+
import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.SUBSTITUTE;
27+
28+
import com.oracle.truffle.api.frame.VirtualFrame;
29+
import com.oracle.truffle.api.profiles.ValueProfile;
30+
import com.oracle.truffle.r.nodes.binary.BoxPrimitiveNode;
31+
import com.oracle.truffle.r.nodes.function.PromiseHelperNode;
32+
import com.oracle.truffle.r.runtime.RRuntime;
33+
import com.oracle.truffle.r.runtime.RVisibility;
34+
import com.oracle.truffle.r.runtime.builtins.RBuiltin;
35+
import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
36+
import com.oracle.truffle.r.runtime.data.RMissing;
37+
import com.oracle.truffle.r.runtime.data.RNull;
38+
import com.oracle.truffle.r.runtime.data.RPromise;
39+
import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
40+
import com.oracle.truffle.r.runtime.nodes.RFastPathNode;
41+
42+
/**
43+
* {@code stopifnot} is often used to check some unlikely conditions. It is complex R function that
44+
* always uses stack introspection and deparsing even if the condition evaluates to {@code TRUE},
45+
* which is very expensive for "unlikely" condition check. This fast-path tries to avoid all this
46+
* complexity for cases when the condition is single value that evaluates to {@code TRUE}.
47+
*/
48+
@RBuiltin(name = "stopifnot", kind = SUBSTITUTE, parameterNames = {"...", "exprs", "local"}, nonEvalArgs = {0, 1, 2}, behavior = COMPLEX, visibility = RVisibility.OFF)
49+
public class StopifnotFastPath extends RFastPathNode {
50+
@Child private PromiseHelperNode promiseHelperNode = new PromiseHelperNode();
51+
@Child private BoxPrimitiveNode boxPrimitiveNode = BoxPrimitiveNode.create();
52+
private final ValueProfile resultProfile = ValueProfile.createClassProfile();
53+
54+
@Override
55+
public Object execute(VirtualFrame frame, Object... args) {
56+
if (!(args[0] instanceof RArgsValuesAndNames) || args[1] != RMissing.instance || args[2] != RMissing.instance) {
57+
// Arguments "exprs" and "local" have default values
58+
return null;
59+
}
60+
RArgsValuesAndNames dotdotdot = (RArgsValuesAndNames) args[0];
61+
if (dotdotdot.getLength() != 1 || !(dotdotdot.getArgument(0) instanceof RPromise)) {
62+
return null;
63+
}
64+
RPromise resultPromise = (RPromise) dotdotdot.getArgument(0);
65+
Object result = resultProfile.profile(boxPrimitiveNode.execute(promiseHelperNode.evaluate(frame, resultPromise)));
66+
if (!(result instanceof RAbstractLogicalVector)) {
67+
// We fallback to the R code, the promise will be already evaluated and not re-evaluated
68+
// Since the other arguments have only default values, there could not be any visible
69+
// side effect between now and the point when the promise should have been really
70+
// evaluated later in stopifnot R code
71+
return null;
72+
}
73+
RAbstractLogicalVector resultVec = (RAbstractLogicalVector) result;
74+
if (resultVec.getLength() != 1 || !RRuntime.fromLogical(resultVec.getDataAt(0))) {
75+
return null;
76+
}
77+
return RNull.instance;
78+
}
79+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 3 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 3 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 3 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
package com.oracle.truffle.r.test.builtins;
24+
25+
import com.oracle.truffle.r.test.TestBase;
26+
import org.junit.Test;
27+
28+
/**
29+
* {@code stopifnot} is implemented in R code, but FastR has a fast-path replacement, which is
30+
* tested here.
31+
*/
32+
public class TestBuiltin_stopifnot extends TestBase {
33+
@Test
34+
public void testStopifnotFastPathWithWarning() {
35+
assertEval("{ foo <- function(a) { if (a != 42L) warning('my warning'); a }; stopifnot(foo(33) < 42) }");
36+
}
37+
38+
@Test
39+
public void testStopifnotFastPathBailoutWithSideEffect() {
40+
assertEval("{ global <- 42; foo <- function() { if (global == 42) { global <<- 44; F } else T }; tryCatch(stopifnot(foo()), error=function(x) cat(x$message,'\\n')); global }");
41+
}
42+
43+
@Test
44+
public void testStopifnotFastPathConditionThrowsError() {
45+
assertEval("{ foo <- function() { stop('my error') }; stopifnot(foo()) }");
46+
}
47+
48+
@Test
49+
public void testStopifnotBasicUsage() {
50+
assertEval("stopifnot(4 < 5, 7 < 10, T)");
51+
assertEval("stopifnot(4 < 5, 7 > 10, T)");
52+
assertEval("stopifnot(exprs = { 4 < 5; 7 < 10; T })");
53+
assertEval("stopifnot(exprs = { 4 < 5; 7 > 10; T })");
54+
assertEval("stopifnot(1 == 1, all.equal(pi, 3.14159265), 1 < 2)");
55+
}
56+
}

0 commit comments

Comments
 (0)