Skip to content

Commit bafca07

Browse files
committed
[GR-2798] Fix SET_ATTRIB and fast-path for stopifnot.
PullRequest: fastr/2060
2 parents 2e0e90e + 0a2e215 commit bafca07

File tree

8 files changed

+215
-8
lines changed

8 files changed

+215
-8
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,5 @@ lib.install.packages.fastr
157157
lib.install.packages.gnur
158158
/com.oracle.truffle.r.release/doc/
159159
.Rproj.user
160+
com.oracle.truffle.r.test.native/packages/*/*/src/*.so
161+
com.oracle.truffle.r.test.native/packages/*/*/src/*.o

com.oracle.truffle.r.ffi.impl/src/com/oracle/truffle/r/ffi/impl/nodes/AttributesAccessNodes.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.oracle.truffle.api.dsl.Fallback;
3333
import com.oracle.truffle.api.dsl.Specialization;
3434
import com.oracle.truffle.api.library.CachedLibrary;
35+
import com.oracle.truffle.api.object.DynamicObject;
3536
import com.oracle.truffle.api.profiles.ConditionProfile;
3637
import com.oracle.truffle.r.ffi.impl.nodes.AttributesAccessNodesFactory.ATTRIBNodeGen;
3738
import com.oracle.truffle.r.ffi.impl.nodes.AttributesAccessNodesFactory.CopyMostAttribNodeGen;
@@ -43,7 +44,9 @@
4344
import com.oracle.truffle.r.nodes.attributes.GetAttributesNode;
4445
import com.oracle.truffle.r.nodes.attributes.RemoveAttributeNode;
4546
import com.oracle.truffle.r.nodes.attributes.SetAttributeNode;
47+
import com.oracle.truffle.r.nodes.attributes.SetPropertyNode;
4648
import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetRowNamesAttributeNode;
49+
import com.oracle.truffle.r.nodes.function.opt.ShareObjectNode;
4750
import com.oracle.truffle.r.nodes.function.opt.UpdateShareableChildValueNode;
4851
import com.oracle.truffle.r.nodes.unary.CastNode;
4952
import com.oracle.truffle.r.nodes.unary.InternStringNode;
@@ -269,7 +272,9 @@ public static CopyMostAttrib create() {
269272

270273
/**
271274
* Overrides the attributes pairlist of given object with a new pairlist. In FastR, we have to
272-
* convert the pairlist to our representation.
275+
* convert the pairlist to our representation. This doesn't do any validation in GNU-R and
276+
* simply sets the attributes pairlist to given value and some packages assume that they can,
277+
* e.g., fixup any inconsistencies in special attributes like dims afterwards.
273278
*/
274279
public abstract static class SetAttribNode extends FFIUpCallNode.Arg2 {
275280

@@ -279,9 +284,11 @@ public static SetAttribNode create() {
279284

280285
@Specialization
281286
protected Object doIt(RSharingAttributeStorage target, RPairList attributes,
282-
@Cached("create()") SetAttributeNode setAttribNode,
287+
@Cached SetPropertyNode setPropertyNode,
288+
@Cached ShareObjectNode shareObjectNode,
283289
@CachedLibrary(limit = "1") RPairListLibrary plLib) {
284290
clearAttrs(target);
291+
DynamicObject attrs = target.getAttributes();
285292
for (RPairList attr : attributes) {
286293
Object tag = plLib.getTag(attr);
287294
if (!(tag instanceof RSymbol)) {
@@ -291,7 +298,9 @@ protected Object doIt(RSharingAttributeStorage target, RPairList attributes,
291298
RError.warning(NO_CALLER, Message.NO_TAG_IN_SET_ATTRIB, Utils.getTypeName(tag));
292299
continue;
293300
}
294-
setAttribNode.execute(target, ((RSymbol) tag).getName(), plLib.car(attr));
301+
Object value = plLib.car(attr);
302+
shareObjectNode.execute(value);
303+
setPropertyNode.execute(attrs, ((RSymbol) tag).getName(), value);
295304
}
296305
return RNull.instance;
297306
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@
4646
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.GetFastPathNodeGen;
4747
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.IntersectFastPathNodeGen;
4848
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.IsElementFastPathNodeGen;
49+
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.MatchArgFastPath;
50+
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.MatchArgFastPathNodeGen;
4951
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.MatrixFastPathNodeGen;
5052
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SetDiffFastPathNodeGen;
53+
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.StopifnotFastPath;
5154
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SubscriptDataFrameFastPathNodeGen;
5255
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SubsetDataFrameFastPath;
5356
import com.oracle.truffle.r.nodes.builtin.base.fastpaths.SubsetDataFrameFastPathNodeGen;
@@ -917,6 +920,7 @@ public void loadOverrides(MaterializedFrame baseFrame) {
917920
addFastPath(baseFrame, "seq.default", SeqFunctionsFactory.SeqDefaultFastPathNodeGen::create, RVisibility.ON);
918921
addFastPath(baseFrame, "seq", SeqFunctionsFactory.SeqFastPathNodeGen::create, RVisibility.ON);
919922
addFastPath(baseFrame, "match.arg", MatchArgFastPathNodeGen::create, MatchArgFastPath.class);
923+
addFastPath(baseFrame, "stopifnot", StopifnotFastPath::new, StopifnotFastPath.class);
920924

921925
setContainsDispatch(baseFrame, "eval", "[.data.frame", "[[.data.frame", "[<-.data.frame", "[[<-.data.frame");
922926
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
@RBuiltin(name = "identical", kind = INTERNAL, parameterNames = {"x", "y", "num.eq", "single.NA", "attrib.as.set", "ignore.bytecode", "ignore.environment", "ignore.srcref"}, behavior = PURE)
7979
public abstract class Identical extends RBuiltinNode.Arg8 {
8080

81-
protected abstract byte executeByte(Object x, Object y, boolean numEq, boolean singleNA, boolean attribAsSet, boolean ignoreBytecode, boolean ignoreEnvironment, boolean ignoreSrcref);
81+
public abstract byte executeByte(Object x, Object y, boolean numEq, boolean singleNA, boolean attribAsSet, boolean ignoreBytecode, boolean ignoreEnvironment, boolean ignoreSrcref);
8282

8383
@Child private Identical identicalRecursive;
8484
@Child private Identical identicalRecursiveAttr;

com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/MatchArgFastPath.java renamed to com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/fastpaths/MatchArgFastPath.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* or visit www.oracle.com if you need additional information or have any
2121
* questions.
2222
*/
23-
package com.oracle.truffle.r.nodes.builtin.base;
23+
package com.oracle.truffle.r.nodes.builtin.base.fastpaths;
2424

2525
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.logicalValue;
2626
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.stringValue;
@@ -44,7 +44,12 @@
4444
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.typeName;
4545

4646
import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetNamesAttributeNode;
47-
import com.oracle.truffle.r.nodes.builtin.base.MatchArgFastPathNodeGen.MatchArgInternalNodeGen;
47+
import com.oracle.truffle.r.nodes.builtin.base.AsCharacter;
48+
import com.oracle.truffle.r.nodes.builtin.base.AsCharacterNodeGen;
49+
import com.oracle.truffle.r.nodes.builtin.base.Identical;
50+
import com.oracle.truffle.r.nodes.builtin.base.IdenticalNodeGen;
51+
import com.oracle.truffle.r.nodes.builtin.base.PMatch;
52+
import com.oracle.truffle.r.nodes.builtin.base.PMatchNodeGen;
4853
import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
4954
import com.oracle.truffle.r.nodes.function.ClassHierarchyNodeGen;
5055
import com.oracle.truffle.r.nodes.function.FormalArguments;
@@ -288,7 +293,7 @@ public Object execute(VirtualFrame frame) {
288293
}
289294

290295
protected static MatchArgInternal createInternal() {
291-
return MatchArgInternalNodeGen.create();
296+
return MatchArgFastPathNodeGen.MatchArgInternalNodeGen.create();
292297
}
293298

294299
@Specialization(limit = "1", guards = "cache.choicesValue.isSupported(frame, arg)")
@@ -304,7 +309,7 @@ public static final class MatchArgNode extends Node {
304309

305310
public MatchArgNode(VirtualFrame frame, RPromise arg) {
306311
this.choicesValue = new MatchArgChoices(frame, arg);
307-
this.internal = MatchArgInternalNodeGen.create();
312+
this.internal = MatchArgFastPathNodeGen.MatchArgInternalNodeGen.create();
308313
this.promiseHelper = new PromiseHelperNode();
309314
}
310315
}
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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# This code is free software; you can redistribute it and/or modify it
5+
# under the terms of the GNU General Public License version 3 only, as
6+
# published by the Free Software Foundation.
7+
#
8+
# This code is distributed in the hope that it will be useful, but WITHOUT
9+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11+
# version 3 for more details (a copy is included in the LICENSE file that
12+
# accompanied this code).
13+
#
14+
# You should have received a copy of the GNU General Public License version
15+
# 3 along with this work; if not, write to the Free Software Foundation,
16+
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
17+
#
18+
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
19+
# or visit www.oracle.com if you need additional information or have any
20+
# questions.
21+
22+
# Contains unit-tests of the individual R API functions
23+
24+
assertEquals <- function(expected, actual) {
25+
width <- 80L
26+
name <- substr(deparse(sys.call(), width)[[1L]], 1, width)
27+
cat(name, paste(rep('.', width + 3L - nchar(name)), collapse=''))
28+
cat(if (identical(expected, actual)) 'pass' else 'fail', '\n')
29+
}
30+
31+
ignore <- function(...) {}
32+
33+
library(testrffi)
34+
35+
# ---------------------------------------------------------------------------------------
36+
# SET_ATTRIB
37+
38+
x <- c(1,3,10)
39+
assertEquals(NULL, api.SET_ATTRIB(x, pairlist(names=c('a','b','q'))))
40+
assertEquals(c('a','b','q'), names(x))
41+
42+
# there is no validation
43+
x <- c(1,3,10)
44+
assertEquals(NULL, api.SET_ATTRIB(x, as.pairlist(list(names=c('a','b')))))
45+
assertEquals(c('a','b'), names(x))
46+
# note: printing x on GNU-R causes segfault
47+
48+
# ---------------------------------------------------------------------------------------
49+
# Rf_mkCharLenCE, note: last arg is encoding and 0 ~ native encoding
50+
51+
assertEquals("hello world", api.Rf_mkCharLenCE("hello world", 11, 0))
52+
ignore("FastR bug", assertEquals("hello", api.Rf_mkCharLenCE("hello this will be cut away", 5, 0)))
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)