From 607f4108bd9393ef066ccdfc327b659d6b3a0824 Mon Sep 17 00:00:00 2001 From: stepan <stepan.sindelar@oracle.com> Date: Fri, 10 Nov 2017 18:11:33 +0100 Subject: [PATCH] Support S4 objects with .Data or .xData in subscribe operations --- .../vector/CachedExtractVectorNode.java | 15 ++++- .../vector/CachedReplaceVectorNode.java | 17 +++++- .../nodes/access/vector/CachedVectorNode.java | 1 + .../access/vector/ExtractS4ObjectNode.java | 61 +++++++++++++++++++ .../access/vector/ReplaceS4ObjectNode.java | 53 ++++++++++++++++ .../access/vector/ReplaceVectorNode.java | 2 +- .../access/vector/WriteIndexedVectorNode.java | 2 +- .../com/oracle/truffle/r/runtime/RError.java | 2 + .../truffle/r/test/ExpectedTestOutput.test | 43 +++++++++++++ .../com/oracle/truffle/r/test/S4/TestS4.java | 8 +++ 10 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java create mode 100644 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java index f6e1fdd15a..903947939f 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java @@ -53,6 +53,7 @@ import com.oracle.truffle.r.runtime.data.RList; import com.oracle.truffle.r.runtime.data.RLogical; import com.oracle.truffle.r.runtime.data.RNull; import com.oracle.truffle.r.runtime.data.RPromise; +import com.oracle.truffle.r.runtime.data.RS4Object; import com.oracle.truffle.r.runtime.data.RString; import com.oracle.truffle.r.runtime.data.RStringVector; import com.oracle.truffle.r.runtime.data.RTypedValue; @@ -89,6 +90,8 @@ final class CachedExtractVectorNode extends CachedVectorNode { @Child private ExtractDimNamesNode extractDimNames; + @Child private ExtractS4ObjectNode extractS4ObjectNode; + private final ConditionProfile resultHasDimensions = ConditionProfile.createBinaryProfile(); private final ConditionProfile promiseInEnvironment = ConditionProfile.createBinaryProfile(); @@ -109,7 +112,7 @@ final class CachedExtractVectorNode extends CachedVectorNode { this.exact = logicalAsBoolean(exact, DEFAULT_EXACT); this.dropDimensions = logicalAsBoolean(dropDimensions, DEFAULT_DROP_DIMENSION); this.positionsCheckNode = new PositionsCheckNode(mode, vectorType, convertedPositions, this.exact, false, recursive); - if (error == null && vectorType != RType.Null && vectorType != RType.Environment) { + if (error == null && vectorType != RType.Null && vectorType != RType.Environment && vectorType != RType.S4Object) { this.writeVectorNode = WriteIndexedVectorNode.create(vectorType, convertedPositions.length, true, false, false, false); } } @@ -148,6 +151,8 @@ final class CachedExtractVectorNode extends CachedVectorNode { * later. */ return doEnvironment((REnvironment) castVector, positions); + case S4Object: + return doS4Object((RS4Object) castVector, positions); case Integer: vector = (RAbstractContainer) castVector; break; @@ -268,6 +273,14 @@ final class CachedExtractVectorNode extends CachedVectorNode { throw error(RError.Message.WRONG_ARGS_SUBSET_ENV); } + private Object doS4Object(RS4Object object, Object[] positions) { + if (extractS4ObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + extractS4ObjectNode = insert(new ExtractS4ObjectNode(mode, exact, dropDimensions)); + } + return extractS4ObjectNode.execute(object, positions); + } + private boolean isMissingSingleDimension() { return numberOfDimensions == 1 && positionsCheckNode.isMissing(); } diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java index 73b47fda38..7158f0f986 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java @@ -57,6 +57,7 @@ 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.RPairList; +import com.oracle.truffle.r.runtime.data.RS4Object; import com.oracle.truffle.r.runtime.data.RScalarVector; import com.oracle.truffle.r.runtime.data.RShareable; import com.oracle.truffle.r.runtime.data.RStringVector; @@ -94,6 +95,7 @@ final class CachedReplaceVectorNode extends CachedVectorNode { private final boolean updatePositionNames; private final boolean isValueGt1; + private final boolean ignoreRecursive; @Child private WriteIndexedVectorNode writeVectorNode; @Child private PositionsCheckNode positionsCheckNode; @@ -102,7 +104,7 @@ final class CachedReplaceVectorNode extends CachedVectorNode { @Child private DeleteElementsNode deleteElementsNode; @Child private SetNamesAttributeNode setNamesNode; - CachedReplaceVectorNode(ElementAccessMode mode, RTypedValue vector, Object[] positions, Class<?> valueClass, RType valueType, boolean updatePositionNames, boolean recursive, boolean isValueGt1) { + CachedReplaceVectorNode(ElementAccessMode mode, RTypedValue vector, Object[] positions, Class<?> valueClass, RType valueType, boolean updatePositionNames, boolean recursive, boolean ignoreRecursive, boolean isValueGt1) { super(mode, vector, positions, recursive); if (numberOfDimensions == 1 && positions[0] instanceof String || positions[0] instanceof RAbstractStringVector) { @@ -111,6 +113,7 @@ final class CachedReplaceVectorNode extends CachedVectorNode { this.updatePositionNames = false; } + this.ignoreRecursive = ignoreRecursive; this.vectorClass = vector.getClass(); this.valueClass = valueClass; this.valueType = valueType; @@ -199,6 +202,8 @@ final class CachedReplaceVectorNode extends CachedVectorNode { break; case Environment: return doEnvironment((REnvironment) castVector, positions, castValue); + case S4Object: + return doS4Object((RS4Object) castVector, positions, castValue); case Language: repType = RContext.getRRuntimeASTAccess().getRepType((RLanguage) castVector); vector = RContext.getRRuntimeASTAccess().asList((RLanguage) castVector); @@ -483,6 +488,16 @@ final class CachedReplaceVectorNode extends CachedVectorNode { return env; } + @Child private ReplaceS4ObjectNode replaceS4ObjectNode; + + private Object doS4Object(RS4Object obj, Object[] positions, Object originalValues) { + if (replaceS4ObjectNode == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + replaceS4ObjectNode = insert(new ReplaceS4ObjectNode(mode, ignoreRecursive)); + } + return replaceS4ObjectNode.execute(obj, positions, originalValues); + } + @NodeInfo(cost = NONE) public abstract static class ValueProfileNode extends Node { diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java index bc1808ef44..595c1452ef 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java @@ -146,6 +146,7 @@ abstract class CachedVectorNode extends RBaseNode { case PairList: case Environment: case Expression: + case S4Object: return true; default: return false; diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java new file mode 100644 index 0000000000..70df313158 --- /dev/null +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package com.oracle.truffle.r.nodes.access.vector; + +import static com.oracle.truffle.r.runtime.RError.Message.OP_NOT_DEFINED_FOR_S4_CLASS; + +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.r.nodes.objects.GetS4DataSlot; +import com.oracle.truffle.r.nodes.objects.GetS4DataSlotNodeGen; +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.RType; +import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.data.RS4Object; +import com.oracle.truffle.r.runtime.data.RTypedValue; +import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector; + +public class ExtractS4ObjectNode extends Node { + @Child private GetS4DataSlot getS4DataSlotNode = GetS4DataSlotNodeGen.create(RType.Environment); + @Child private ExtractVectorNode extract; + private final boolean exact; + private final boolean dropDimensions; + + public ExtractS4ObjectNode(ElementAccessMode accessMode, boolean exact, boolean dropDimensions) { + this.extract = ExtractVectorNode.create(accessMode, true); + this.exact = exact; + this.dropDimensions = dropDimensions; + } + + public Object execute(RS4Object obj, Object[] positions) { + RTypedValue dataSlot = getS4DataSlotNode.executeObject(obj); + if (dataSlot == RNull.instance) { + throw RError.error(RError.SHOW_CALLER, OP_NOT_DEFINED_FOR_S4_CLASS, "$"); + } + return extract.execute(dataSlot, positions, createLogical(exact), createLogical(dropDimensions)); + } + + private static RAbstractLogicalVector createLogical(boolean b) { + return RDataFactory.createLogicalVectorFromScalar(b); + } +} diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java new file mode 100644 index 0000000000..afc04f304e --- /dev/null +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java @@ -0,0 +1,53 @@ +/* + * 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. + */ +package com.oracle.truffle.r.nodes.access.vector; + +import static com.oracle.truffle.r.runtime.RError.Message.NO_METHOD_ASSIGNING_SUBSET_S4; + +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.r.nodes.objects.GetS4DataSlot; +import com.oracle.truffle.r.nodes.objects.GetS4DataSlotNodeGen; +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.RType; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.data.RS4Object; +import com.oracle.truffle.r.runtime.data.RTypedValue; + +public class ReplaceS4ObjectNode extends Node { + @Child private GetS4DataSlot getS4DataSlotNode = GetS4DataSlotNodeGen.create(RType.Environment); + @Child private ReplaceVectorNode replaceVectorNode; + + public ReplaceS4ObjectNode(ElementAccessMode mode, boolean ignoreRecursive) { + replaceVectorNode = ReplaceVectorNode.create(mode, ignoreRecursive); + } + + public Object execute(RS4Object obj, Object[] positions, Object values) { + RTypedValue dataSlot = getS4DataSlotNode.executeObject(obj); + if (dataSlot == RNull.instance) { + throw RError.error(RError.SHOW_CALLER, NO_METHOD_ASSIGNING_SUBSET_S4); + } + // No need to update the data slot, the value is env and they have reference semantics. + replaceVectorNode.execute(dataSlot, positions, values); + return obj; + } +} diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java index cef08a1982..f295281ded 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java @@ -189,7 +189,7 @@ public abstract class ReplaceVectorNode extends RBaseNode { protected static CachedReplaceVectorNode createDefaultCached(ReplaceVectorNode node, Object vector, Object[] positions, Object value) { return new CachedReplaceVectorNode(node.mode, (RTypedValue) vector, positions, value.getClass(), RRuntime.isForeignObject(value) ? RType.TruffleObject : ((RTypedValue) value).getRType(), true, - node.recursive, CachedReplaceVectorNode.isValueLengthGreaterThanOne(value)); + node.recursive, node.ignoreRecursive, CachedReplaceVectorNode.isValueLengthGreaterThanOne(value)); } public ElementAccessMode getMode() { diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java index ab744903fc..aecab8b403 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java @@ -373,7 +373,7 @@ abstract class WriteIndexedVectorNode extends Node { case List: return new WriteListAction(setListElementAsObject, isReplace); default: - throw RInternalError.shouldNotReachHere(); + throw RInternalError.shouldNotReachHere("WriteIndexedScalarNode for " + type); } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java index b8abebdd2f..1253fd0cf7 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java @@ -635,6 +635,7 @@ public final class RError extends RuntimeException implements TruffleException { POS_NOT_ALLOWED_WITH_NUMERIC("pos argument not allowed with a numeric value"), OBJ_CANNOT_BE_ATTRIBUTED("external object cannot be attributed"), CANNOT_COERCE_EXTERNAL_OBJECT_TO_VECTOR("no method for coercing this external object to a %s"), + NO_METHOD_ASSIGNING_SUBSET_S4("no method for assigning subsets of this S4 class"), CANNOT_COERCE_S4_TO_VECTOR("no method for coercing this S4 class to a vector"), // the following list is incomplete (but like GNU-R) INVALID_FORMAT_DOUBLE("invalid format '%s'; use format %%f, %%e, %%g or %%a for numeric objects"), @@ -723,6 +724,7 @@ public final class RError extends RuntimeException implements TruffleException { RNG_SYMBOL("%s not found in user rng library"), CUMMAX_UNDEFINED_FOR_COMPLEX("'cummax' not defined for complex numbers"), CUMMIN_UNDEFINED_FOR_COMPLEX("'cummin' not defined for complex numbers"), + OP_NOT_DEFINED_FOR_S4_CLASS("%s operator not defined for this S4 class"), NMAX_LESS_THAN_ONE("'nmax' must be positive"), CHAR_VEC_ARGUMENT("a character vector argument expected"), QUOTE_G_ONE("only the first character of 'quote' will be used"), 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 ed3a0521e0..53c9acfb6a 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 @@ -604,6 +604,49 @@ Error in validObject(.Object) : invalid class “SingleInt†object: FALSE #{ setClass('WrappedIntVec', representation(n = 'numeric')); x0 <- new('WrappedIntVec', n = 1); x1 <- x0; x1@n <- 2; x0@n == x1@n } [1] FALSE +##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign# +#{ setClass('TestS4CornerCases', representation(fld = 'character')); obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.Data') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.Data')), obj$fld2); } +[[1]] +An object of class "TestS4CornerCases" +Slot "fld": +[1] "xyz" + + +[[2]] +[[2]]$fld2 +[1] "value" + + +[[3]] +[1] "value" + + +##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign# +#{ setClass('TestS4CornerCases', representation(fld = 'character')); obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.xData') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.xData')), obj$fld2); } +[[1]] +An object of class "TestS4CornerCases" +Slot "fld": +[1] "xyz" + + +[[2]] +[[2]]$fld2 +[1] "value" + + +[[3]] +[1] "value" + + +##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#Output.IgnoreErrorContext# +#{ setClass('TestS4CornerCases', representation(fld = 'character')); obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2 <- 'value'; } +Error in `$<-`(`*tmp*`, fld2, value = "value") : + no method for assigning subsets of this S4 class + +##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#Output.IgnoreErrorContext# +#{ setClass('TestS4CornerCases', representation(fld = 'character')); obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2; } +Error in obj$fld2 : $ operator not defined for this S4 class + ##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext# # { x<-42; attr(x, "foo")<-7; x@foo } Error: trying to get slot "foo" from an object of a basic class ("numeric") with no slots diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java index e054c71b60..ea788cf637 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java @@ -187,4 +187,12 @@ public class TestS4 extends TestRBase { assertEval("makeActiveBinding('someSymbol10', function(x) { if(missing(x)) print('get0') else print('set0'); NULL }, .GlobalEnv); someSymbol10; someSymbol10 <- 1; makeActiveBinding('someSymbol10', function(x) { if(missing(x)) print('get1') else print('set1'); NULL }, .GlobalEnv); someSymbol10; someSymbol10 <- 1"); assertEval("makeActiveBinding('var_a', function(x) { if(missing(x)) { print('get'); return(123) } else { print('set'); return(x) } }, .GlobalEnv); inherits(var_a, 'numeric')"); } + + @Test + public void testRegularFieldAssign() { + assertEval(Output.IgnoreErrorContext, "{ setClass('TestS4CornerCases', representation(fld = 'character')); obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2 <- 'value'; }"); + assertEval(Output.IgnoreErrorContext, "{ setClass('TestS4CornerCases', representation(fld = 'character')); obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2; }"); + assertEval("{ setClass('TestS4CornerCases', representation(fld = 'character')); obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.Data') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.Data')), obj$fld2); }"); + assertEval("{ setClass('TestS4CornerCases', representation(fld = 'character')); obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.xData') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.xData')), obj$fld2); }"); + } } -- GitLab