From 1a9d2899598f0d97fd64a330db6697b956b721e9 Mon Sep 17 00:00:00 2001 From: Zbynek Slajchrt <zbynek.slajchrt@oracle.com> Date: Thu, 23 Feb 2017 19:32:53 +0100 Subject: [PATCH] Cast pipeline related issues fixed in a batch of builtins --- .../truffle/r/library/grid/GridFunctions.java | 7 + .../truffle/r/library/stats/BinDist.java | 7 +- .../oracle/truffle/r/library/stats/Cdist.java | 20 ++- .../truffle/r/library/stats/Covcor.java | 5 +- .../truffle/r/library/stats/Cutree.java | 7 +- .../truffle/r/library/stats/DoubleCentre.java | 6 +- .../r/library/stats/RMultinomNode.java | 5 +- .../r/library/stats/RandFunctionsNodes.java | 14 +- .../r/library/stats/SplineFunctions.java | 4 +- .../r/library/stats/StatsFunctionsNodes.java | 10 +- .../casts/fluent/HeadPhaseBuilder.java | 2 +- .../com/oracle/truffle/r/runtime/RError.java | 3 +- .../truffle/r/test/ExpectedTestOutput.test | 53 +++++++ .../test/builtins/TestBuiltin_SplineCoef.java | 40 +++++ .../r/test/builtins/TestBuiltin_rhyper.java | 41 +++++ .../test/builtins/TestBuiltin_rmultinom.java | 41 +++++ documentation/dev/casts.md | 143 ++++++++++-------- 17 files changed, 315 insertions(+), 93 deletions(-) create mode 100644 com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_SplineCoef.java create mode 100644 com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rhyper.java create mode 100644 com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rmultinom.java diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/grid/GridFunctions.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/grid/GridFunctions.java index 6623a2c7ca..35dc425469 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/grid/GridFunctions.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/grid/GridFunctions.java @@ -17,7 +17,9 @@ import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.stringValue; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.r.library.stats.Cdist; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; +import com.oracle.truffle.r.nodes.builtin.NodeWithArgumentCasts.Casts; import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames; import com.oracle.truffle.r.runtime.data.RDataFactory; @@ -36,6 +38,11 @@ public class GridFunctions { public abstract static class InitGrid extends RExternalBuiltinNode.Arg1 { @Child GridRFFI.InitGridNode initGridNode = RFFIFactory.getRFFI().getGridRFFI().createInitGridNode(); + static { + Casts casts = new Casts(InitGrid.class); + casts.arg(0).mustBe(REnvironment.class); + } + @Specialization @TruffleBoundary protected Object initGrid(REnvironment gridEvalEnv) { diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/BinDist.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/BinDist.java index a5b037c384..fa604b7662 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/BinDist.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/BinDist.java @@ -15,6 +15,9 @@ */ package com.oracle.truffle.r.library.stats; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.missingValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.emptyDoubleVector; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.gt0; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.intNA; import static com.oracle.truffle.r.runtime.RError.NO_CALLER; @@ -39,8 +42,8 @@ public abstract class BinDist extends RExternalBuiltinNode.Arg5 { static { Casts casts = new Casts(BinDist.class); - casts.arg(0).asDoubleVector(); - casts.arg(1).asDoubleVector(); + casts.arg(0).mustBe(missingValue().not()).returnIf(nullValue(), emptyDoubleVector()).asDoubleVector(); + casts.arg(1).mustBe(missingValue().not()).returnIf(nullValue(), emptyDoubleVector()).asDoubleVector(); casts.arg(2).asDoubleVector().findFirst(); casts.arg(3).asDoubleVector().findFirst(); casts.arg(4).asIntegerVector().findFirst().mustBe(gt0().and(intNA().not()), NO_CALLER, Message.INVALID_ARGUMENT, "n"); diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Cdist.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Cdist.java index be4422e729..a06533fff0 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Cdist.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Cdist.java @@ -12,6 +12,8 @@ */ package com.oracle.truffle.r.library.stats; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.missingValue; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.instanceOf; import static com.oracle.truffle.r.runtime.nmath.MathConstants.DBL_MIN; @@ -39,7 +41,7 @@ public abstract class Cdist extends RExternalBuiltinNode.Arg4 { static { Casts casts = new Casts(Cdist.class); - casts.arg(0).asDoubleVector(); + casts.arg(0).mustBe(nullValue().not(), RError.Message.VECTOR_IS_TOO_LARGE).mustBe(missingValue().not()).asDoubleVector(); casts.arg(1).asIntegerVector().findFirst(); casts.arg(2).mustBe(instanceOf(RList.class)); casts.arg(3).asDoubleVector().findFirst(); @@ -61,13 +63,15 @@ public abstract class Cdist extends RExternalBuiltinNode.Arg4 { DynamicObject resultAttrs = result.initAttributes(); RStringVector names = (RStringVector) getNamesAttrNode.execute(list); - for (int i = 0; i < names.getLength(); i++) { - String name = names.getDataAt(i); - Object listValue = list.getDataAt(i); - if (name.equals(RRuntime.CLASS_ATTR_KEY)) { - setClassAttrNode.execute(result, listValue instanceof RStringVector ? (RStringVector) listValue : RDataFactory.createStringVectorFromScalar((String) listValue)); - } else { - setAttrNode.execute(resultAttrs, name, listValue); + if (names != null) { + for (int i = 0; i < names.getLength(); i++) { + String name = names.getDataAt(i); + Object listValue = list.getDataAt(i); + if (name.equals(RRuntime.CLASS_ATTR_KEY)) { + setClassAttrNode.execute(result, listValue instanceof RStringVector ? (RStringVector) listValue : RDataFactory.createStringVectorFromScalar((String) listValue)); + } else { + setAttrNode.execute(resultAttrs, name, listValue); + } } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Covcor.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Covcor.java index 05fc0538c7..011ee2add3 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Covcor.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Covcor.java @@ -11,6 +11,7 @@ */ package com.oracle.truffle.r.library.stats; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.eq; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.toBoolean; import static com.oracle.truffle.r.runtime.RError.NO_CALLER; @@ -48,8 +49,8 @@ public abstract class Covcor extends RExternalBuiltinNode.Arg4 { static { Casts casts = new Casts(Covcor.class); - casts.arg(0).mustNotBeNull(SHOW_CALLER, Message.IS_NULL, "x").asDoubleVector(); - casts.arg(1).asDoubleVector(); + casts.arg(0).mustNotBeMissing().mustBe(nullValue().not(), SHOW_CALLER, Message.IS_NULL, "x").asDoubleVector(); + casts.arg(1).mustNotBeMissing().asDoubleVector(); casts.arg(2).asIntegerVector().findFirst().mustBe(eq(4), Message.NYI, "covcor: other method than 4 not implemented."); casts.arg(3).asLogicalVector().findFirst().map(toBoolean()); } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Cutree.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Cutree.java index a2de828f38..d4f616e70c 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Cutree.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/Cutree.java @@ -10,6 +10,9 @@ */ package com.oracle.truffle.r.library.stats; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.emptyIntegerVector; + import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetDimAttributeNode; @@ -24,8 +27,8 @@ public abstract class Cutree extends RExternalBuiltinNode.Arg2 { static { Casts casts = new Casts(Cutree.class); - casts.arg(0).asIntegerVector(); - casts.arg(1).asIntegerVector(); + casts.arg(0).mustNotBeMissing().mapIf(nullValue(), emptyIntegerVector()).asIntegerVector(); + casts.arg(1).mustNotBeMissing().mapIf(nullValue(), emptyIntegerVector()).asIntegerVector(); } @Specialization diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/DoubleCentre.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/DoubleCentre.java index 0eb3928748..34bfd0b02d 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/DoubleCentre.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/DoubleCentre.java @@ -10,10 +10,14 @@ */ package com.oracle.truffle.r.library.stats; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.missingValue; + import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetDimAttributeNode; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; +import com.oracle.truffle.r.runtime.RError; import com.oracle.truffle.r.runtime.data.RDoubleVector; import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; @@ -21,7 +25,7 @@ public abstract class DoubleCentre extends RExternalBuiltinNode.Arg1 { static { Casts casts = new Casts(DoubleCentre.class); - casts.arg(0).asDoubleVector(); + casts.arg(0).mustBe(missingValue().not()).mustBe(nullValue().not(), RError.SHOW_CALLER, RError.Message.MACRO_CAN_BE_APPLIED_TO, "REAL()", "numeric", "NULL").asDoubleVector(); } @Specialization diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/RMultinomNode.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/RMultinomNode.java index c1ea604e1f..dedf931e57 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/RMultinomNode.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/RMultinomNode.java @@ -12,6 +12,9 @@ */ package com.oracle.truffle.r.library.stats; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.emptyDoubleVector; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.missingValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.notIntNA; import static com.oracle.truffle.r.runtime.RError.SHOW_CALLER; import static com.oracle.truffle.r.runtime.RError.Message.NA_IN_PROB_VECTOR; @@ -52,7 +55,7 @@ public abstract class RMultinomNode extends RExternalBuiltinNode.Arg3 { Casts casts = new Casts(RMultinomNode.class); casts.arg(0).asIntegerVector().findFirst().mustBe(notIntNA(), SHOW_CALLER, Message.INVALID_FIRST_ARGUMENT_NAME, "n"); casts.arg(1).asIntegerVector().findFirst().mustBe(notIntNA(), SHOW_CALLER, Message.INVALID_SECOND_ARGUMENT_NAME, "size"); - casts.arg(2).asDoubleVector(); + casts.arg(2).mustBe(missingValue().not(), SHOW_CALLER, Message.ARGUMENT_MISSING, "prob").mapIf(nullValue(), emptyDoubleVector()).asDoubleVector(); } @Specialization diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/RandFunctionsNodes.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/RandFunctionsNodes.java index d365281f27..1a5d082b10 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/RandFunctionsNodes.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/RandFunctionsNodes.java @@ -13,6 +13,8 @@ package com.oracle.truffle.r.library.stats; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.missingValue; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVectorValue; import static com.oracle.truffle.r.runtime.RError.SHOW_CALLER; import static com.oracle.truffle.r.runtime.RError.Message.INVALID_UNNAMED_ARGUMENTS; @@ -260,9 +262,9 @@ public final class RandFunctionsNodes { static { Casts casts = new Casts(RandFunction3Node.class); ConvertToLength.addLengthCast(casts); - casts.arg(1).asDoubleVector(); - casts.arg(2).asDoubleVector(); - casts.arg(3).asDoubleVector(); + casts.arg(1).mustBe(nullValue().not(), RError.SHOW_CALLER, RError.Message.INVALID_UNNAMED_ARGUMENTS).mustBe(missingValue().not(), RError.Message.ARGUMENT_MISSING, "a").asDoubleVector(); + casts.arg(2).mustBe(nullValue().not(), RError.SHOW_CALLER, RError.Message.INVALID_UNNAMED_ARGUMENTS).mustBe(missingValue().not(), RError.Message.ARGUMENT_MISSING, "b").asDoubleVector(); + casts.arg(3).mustBe(nullValue().not(), RError.SHOW_CALLER, RError.Message.INVALID_UNNAMED_ARGUMENTS).mustBe(missingValue().not(), RError.Message.ARGUMENT_MISSING, "c").asDoubleVector(); } @Specialization @@ -290,8 +292,8 @@ public final class RandFunctionsNodes { static { Casts casts = new Casts(RandFunction2Node.class); ConvertToLength.addLengthCast(casts); - casts.arg(1).asDoubleVector(); - casts.arg(2).asDoubleVector(); + casts.arg(1).mustBe(nullValue().not(), RError.SHOW_CALLER, RError.Message.INVALID_UNNAMED_ARGUMENTS).mustBe(missingValue().not(), RError.Message.ARGUMENT_MISSING, "a").asDoubleVector(); + casts.arg(2).mustBe(nullValue().not(), RError.SHOW_CALLER, RError.Message.INVALID_UNNAMED_ARGUMENTS).mustBe(missingValue().not(), RError.Message.ARGUMENT_MISSING, "b").asDoubleVector(); } @Specialization @@ -319,7 +321,7 @@ public final class RandFunctionsNodes { static { Casts casts = new Casts(RandFunction1Node.class); ConvertToLength.addLengthCast(casts); - casts.arg(1).asDoubleVector(); + casts.arg(1).mustBe(nullValue().not(), RError.SHOW_CALLER, RError.Message.INVALID_UNNAMED_ARGUMENTS).mustBe(missingValue().not(), RError.Message.ARGUMENT_MISSING, "a").asDoubleVector(); } @Specialization diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/SplineFunctions.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/SplineFunctions.java index ac97fefd9b..3a33af53fc 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/SplineFunctions.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/SplineFunctions.java @@ -50,8 +50,8 @@ public class SplineFunctions { chain(asIntegerVector()).with(findFirst().integerElement(INT_NA)).end(), chain(asIntegerVector()).with(findFirst().integerElement(INT_NA)).with( Predef.shouldBe(integerValue(), NO_CALLER, NA_INTRODUCED_COERCION)).end()); - casts.arg(1).allowMissing().asDoubleVector(); - casts.arg(2).allowMissing().asDoubleVector(); + casts.arg(1).mustNotBeMissing().asDoubleVector(); + casts.arg(2).mustNotBeMissing().asDoubleVector(); } @Specialization diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/StatsFunctionsNodes.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/StatsFunctionsNodes.java index e3d7bba69b..9798af5abb 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/StatsFunctionsNodes.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/StatsFunctionsNodes.java @@ -13,6 +13,10 @@ package com.oracle.truffle.r.library.stats; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.numericValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.emptyDoubleVector; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.instanceOf; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.missingValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.toBoolean; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; @@ -359,7 +363,9 @@ public final class StatsFunctionsNodes { static { Casts casts = new Casts(Approx.class); - casts.arg(2).asDoubleVector(); + casts.arg(0).mustBe(instanceOf(RDoubleVector.class)); + casts.arg(1).mustBe(instanceOf(RDoubleVector.class)); + casts.arg(2).mustBe(missingValue().not()).mapIf(nullValue(), emptyDoubleVector()).asDoubleVector(); casts.arg(3).asIntegerVector().findFirst(); casts.arg(4).asDoubleVector().findFirst(); casts.arg(5).asDoubleVector().findFirst(); @@ -367,7 +373,7 @@ public final class StatsFunctionsNodes { } @Specialization - protected RDoubleVector approx(RDoubleVector x, RDoubleVector y, RDoubleVector v, int method, double yl, double yr, double f) { + protected RDoubleVector approx(RDoubleVector x, RDoubleVector y, RAbstractDoubleVector v, int method, double yl, double yr, double f) { int nx = x.getLength(); int nout = v.getLength(); double[] yout = new double[nout]; diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/casts/fluent/HeadPhaseBuilder.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/casts/fluent/HeadPhaseBuilder.java index 6d0d72eb97..54e31bfbf8 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/casts/fluent/HeadPhaseBuilder.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/builtin/casts/fluent/HeadPhaseBuilder.java @@ -56,7 +56,7 @@ public final class HeadPhaseBuilder<T> extends ArgCastBuilder<T, HeadPhaseBuilde return new HeadPhaseBuilder<>(pipelineBuilder()); } - public <S extends T, R> HeadPhaseBuilder<Object> returnIf(Filter<? super T, S> argFilter, Mapper<S, R> trueBranchMapper) { + public <S extends T, R> HeadPhaseBuilder<T> returnIf(Filter<? super T, S> argFilter, Mapper<S, R> trueBranchMapper) { pipelineBuilder().appendMapIf(argFilter, trueBranchMapper, true); return new HeadPhaseBuilder<>(pipelineBuilder()); } 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 7cb1afe159..9ec55327d1 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 @@ -792,7 +792,8 @@ public final class RError extends RuntimeException { UNIMPLEMENTED_TYPE_IN_R("type \"%s\" unimplemented in R"), NOT_AN_OUTPUT_RAW_CONNECTION("'con' is not an output rawConnection"), NOT_A_RAW_CONNECTION("'con' is not a rawConnection"), - SEEK_OUTSITE_RAW_CONNECTION("attempt to seek outside the range of the raw connection"); + SEEK_OUTSITE_RAW_CONNECTION("attempt to seek outside the range of the raw connection"), + VECTOR_IS_TOO_LARGE("vector is too large"); public final String message; final boolean hasArgs; 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 9a22b902f0..735f194785 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 @@ -1366,6 +1366,34 @@ Error in Recall(10) : 'Recall' called from outside a closure #argv <- structure(list(f = function(f, ...) f(...), x = list(.Primitive('log'), .Primitive('exp'), .Primitive('acos'), .Primitive('cos')), init = 0, right = TRUE), .Names = c('f', 'x', 'init', 'right'));do.call('Reduce', argv) [1] 0 +##com.oracle.truffle.r.test.builtins.TestBuiltin_SplineCoef.testNull# +#.Call(stats:::C_SplineCoef, 0, 0, NULL) +Error: inputs of different lengths + +##com.oracle.truffle.r.test.builtins.TestBuiltin_SplineCoef.testSimple# +#.Call(stats:::C_SplineCoef, 0, 0, 0) +$method +[1] 0 + +$n +[1] 1 + +$x +[1] 0 + +$y +[1] 0 + +$b +[1] 0 + +$c +[1] 0 + +$d +[1] 0 + + ##com.oracle.truffle.r.test.builtins.TestBuiltin_Syschmod.testSyschmod1#Ignored.SideEffects# #argv <- list(character(0), structure(integer(0), class = 'octmode'), TRUE); .Internal(Sys.chmod(argv[[1]], argv[[2]], argv[[3]])) @@ -44522,6 +44550,18 @@ logical(0) #argv <- structure(list(x = c('#FF0000FF', '#FFFF00FF', '#00FF00FF')), .Names = 'x');do.call('rev', argv) [1] "#00FF00FF" "#FFFF00FF" "#FF0000FF" +##com.oracle.truffle.r.test.builtins.TestBuiltin_rhyper.testNullAndMissing# +#rmultinom(0,0,0) +Error in rmultinom(0, 0, 0) : no positive probabilities + +##com.oracle.truffle.r.test.builtins.TestBuiltin_rhyper.testNullAndMissing# +#rmultinom(0,0,0,NULL) +Error in rmultinom(0, 0, 0, NULL) : unused argument (NULL) + +##com.oracle.truffle.r.test.builtins.TestBuiltin_rhyper.testSimple# +#rhyper(0,0,0,0) +integer(0) + ##com.oracle.truffle.r.test.builtins.TestBuiltin_rm.basicTests# # e <- new.env(); e$a <- 42; rm(list='a', envir=e); e$a NULL @@ -44554,6 +44594,19 @@ Error in rm(tmp, envir = NULL) : use of NULL environment is defunct #tmp <- 42; rm(tmp, inherits='asd') Error in rm(tmp, inherits = "asd") : invalid 'inherits' argument +##com.oracle.truffle.r.test.builtins.TestBuiltin_rmultinom.testNullAndMissing# +#rmultinom(1, 1) +Error in rmultinom(1, 1) : argument "prob" is missing, with no default + +##com.oracle.truffle.r.test.builtins.TestBuiltin_rmultinom.testNullAndMissing# +#rmultinom(1, 1, NULL) +Error in rmultinom(1, 1, NULL) : no positive probabilities + +##com.oracle.truffle.r.test.builtins.TestBuiltin_rmultinom.testSimple# +#rmultinom(1,1,1) + [,1] +[1,] 1 + ##com.oracle.truffle.r.test.builtins.TestBuiltin_round.testRound# #{ round(-1.5) } [1] -2 diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_SplineCoef.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_SplineCoef.java new file mode 100644 index 0000000000..07f495341c --- /dev/null +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_SplineCoef.java @@ -0,0 +1,40 @@ +/* + * 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.test.builtins; + +import org.junit.Test; + +import com.oracle.truffle.r.test.TestBase; + +public class TestBuiltin_SplineCoef extends TestBase { + + @Test + public void testSimple() { + assertEval(".Call(stats:::C_SplineCoef, 0, 0, 0)"); + } + + @Test + public void testNull() { + assertEval(".Call(stats:::C_SplineCoef, 0, 0, NULL)"); + } +} diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rhyper.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rhyper.java new file mode 100644 index 0000000000..dbe91a4bc2 --- /dev/null +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rhyper.java @@ -0,0 +1,41 @@ +/* + * 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.test.builtins; + +import org.junit.Test; + +import com.oracle.truffle.r.test.TestBase; + +public class TestBuiltin_rhyper extends TestBase { + + @Test + public void testSimple() { + assertEval("rhyper(0,0,0,0)"); + } + + @Test + public void testNullAndMissing() { + assertEval("rmultinom(0,0,0,NULL)"); + assertEval("rmultinom(0,0,0)"); + } +} diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rmultinom.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rmultinom.java new file mode 100644 index 0000000000..caa8e9c018 --- /dev/null +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rmultinom.java @@ -0,0 +1,41 @@ +/* + * 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.test.builtins; + +import org.junit.Test; + +import com.oracle.truffle.r.test.TestBase; + +public class TestBuiltin_rmultinom extends TestBase { + + @Test + public void testSimple() { + assertEval("rmultinom(1,1,1)"); + } + + @Test + public void testNullAndMissing() { + assertEval("rmultinom(1, 1, NULL)"); + assertEval("rmultinom(1, 1)"); + } +} diff --git a/documentation/dev/casts.md b/documentation/dev/casts.md index a343943853..0ec57e6e9f 100644 --- a/documentation/dev/casts.md +++ b/documentation/dev/casts.md @@ -1,8 +1,8 @@ ## Introduction -Cast Pipelines (CP) are used to convert, validate and analyse input arguments of FastR builtins. The aim is to make the code in builtin specializations cleaner by relieving them from the burden of repeated argument handling that often leads to duplicate boilerplate code. Ideally, all argument handling code should be concentrated in a single method in a builtin. However, in certain situations, e.g. when a validation code evaluates more than one argument, the validation code cannot be moved to that single method. +Cast Pipelines (CP) are used to convert, validate and analyze input arguments of FastR builtins. The aim is to make the code in builtin specializations cleaner by relieving them from the burden of repeated argument handling that often leads to duplicate boilerplate code. Besides that, the declarative nature of CP allows for static analysis of pipelines, which is used to diagnose builtins by a special tool (mx rbdiag). -CP provides an API through which a _pipeline_ can be constructed declaratively for each builtin argument. This pipeline consists of one or more steps representing specific operations with the given argument. Because of the declarative character of the argument processing pipelines it is possible to retrieve additional information from the pipelines, which may be further used in tests or code analysis. For instance, each pipeline provides a set of output types, which may be used for coverage analysis of the builtin specializations, i.e. to check if no specialization is missing or unused. Also, in many cases it is possible to determine specific argument values from pipelines, such as default values, limit values of value intervals, and allowed or forbidden values (i.e. corner-case argument values). These specific values may be collected as argument samples and used to create automated tests of builtins. The argument samples are naturally divided into the positive and negative sample sets. The positive samples can be used to test the builtin's functionality and compare the result with the result of the builtin's GnuR counterpart. On the the other hand, the negative samples, which are assumed to cause an error in the FastR builtin, can be used to determine if the GnuR version fails too and produces the same error; if not, the pipeline must be redesigned to reflect the original. +CP provides an API through which a _pipeline_ can be constructed declaratively for each builtin argument. This pipeline consists of one or more steps representing specific operations with the given argument. Because of the declarative character of the argument processing pipelines it is possible to retrieve additional information from the pipelines, which may be further used in tests or code analysis. For instance, each pipeline provides a set of output types, which may be used for coverage analysis of the builtin specializations, i.e. to check if no specialization is missing or unused. Also, in many cases it is possible to determine specific argument values from pipelines, such as default values, limit values of value intervals, and allowed or forbidden values (a.k.a. corner-case argument values). These specific values may be collected as argument samples and used to create automated tests of builtins (a.k.a. chimney-sweeping). The argument samples are naturally divided into the positive and negative sample sets. The positive samples can be used to test the builtin's functionality and compare the result with the result of the builtin's GnuR counterpart. On the the other hand, the negative samples, which are assumed to cause an error in the FastR builtin, can be used to determine if the GnuR version fails too and produces the same error; if not, the pipeline must be redesigned to reflect the original. The following sections deal with the description of the CP API and with some implementation details. @@ -10,13 +10,15 @@ The following sections deal with the description of the CP API and with some imp ### Basics: usage in builtins -CP API is an extension of the `CastBuilder` class, whose instance is passed as the argument of the `createCasts` method that can be overridden in a builtin class to define custom argument conversions for the builtin. The body of the `createCasts` method is the place where the cast pipelines are constructed for every argument of the builtin requiring some conversion or validation, as shown in the following listing. +CP API is available through the `NodeWithArgumentCasts.Casts` class. For every builtin there is one instance of that class instantiated in the static block of the builtin class. The static block is also the place where the cast pipelines are constructed for every argument of the builtin requiring some conversion or validation, as shown in the following listing: ```java - @Override - protected void createCasts(CastBuilder casts) { + // in MyBuiltin class + static { + Casts casts = new Casts(MyBuiltin.class); + casts.arg("X").mustBe(numericValue(), RError.Message.X_NUMERIC); - + casts.arg("m").asIntegerVector(). findFirst(). notNA(); @@ -31,15 +33,24 @@ CP API is an extension of the `CastBuilder` class, whose instance is passed as t } ``` -The CP API part for the pipeline construction is designed in the fluent-builder style. A new pipeline for an argument is initialized by calling the `arg` method on the `CastBuilder` instance. The `arg` method accepts either the index of the argument or the name of the argument, while the latter case is preffered. Following the `arg` method is a series of steps, each inserting a special Truffle node (`CastNode`) into the cast pipeline of the argument. The flow of a pipeline can be divided into four phases: _pre-initial_, _initial_, _coerced_ and _head_, while the last three are optional and each phase may consist of zero or more steps. +In case the builtin does not declare any cast pipeline from any reason, it should declare this fact as follows: + +```java + // in MyBuiltin class + static { + Casts.noCasts(MyBuiltin.class); + } +``` + +The CP API for the pipeline construction is designed in the fluent-builder style. A new pipeline for an argument is initialized by calling the `arg` method on the `Casts` instance. The `arg` method accepts either the index of the argument or the name of the argument, while the latter case is preffered. Following the `arg` method is a series of steps, in which each step inserts, behind the scenes, a special Truffle nodes (`CastNode`) into the cast pipeline. The flow of a pipeline can be divided into four phases: _pre-initial_, _initial_, _coerced_ and _head_, while the last three are optional and each phase may consist of zero or more steps. -In the **pre-initial** phase one can configure the overall behavior of the pipeline. Currently, only the default handling of `RNull` and `RMissing` values can be overridden (the default behavior is explained below). The pipeline can be configured using `PreinitialPhaseBuilder.conf(Consumer)` or any other method of the `PreinitialPhaseBuilder` class, e.g. `PreinitialPhaseBuilder.allowNull()`. +In the **pre-initial** phase, in addition to the **initial** phase, one can configure the overall properties and behaviour of the pipeline (see `PipelineConfigBuilder`). The pipeline can be configured using the `conf(Consumer)` step (declared in `PreinitialPhaseBuilder`). -An argument enters the **initial** phase as a generic object, which may be subjected to various assertions and conversions. The argument may, but may not, exit the initial phase as an instance of a specific type. The pipeline declared in the following listing specifies the default error of the pipeline and inserts one assertion node checking whether the argument is a non-null string. The argument exits this pipeline as a string represented by the `RAbstractStringVector` class. If any of the two conditions fails, the default error is raised. +The **initial** and **pre-initial** phases handle the input argument as a generic object, which may be subjected to various assertions and conversions. The argument may, but may not, exit the initial phase as an instance of a specific type. The pipeline declared in the following listing specifies the default error of the pipeline and inserts one assertion node checking whether the argument is a non-null string. The argument exits this pipeline as a string represented by the `RAbstractStringVector` class. If any of the two conditions fails, the default error is raised. ```java casts.arg("m").defaultError(RError.Message.MUST_BE_STRING, "msg1"). - mustBe(notNull().and(stringValue())); + mustBe(nullValue().not().and(stringValue())); ``` The argument enters the **coerced** phase after having been processed by a node inserted by one of the `as_X_Vector` pipeline steps, where `X` is the type of the resulting vector. The input type of the argument corresponds to the used `as_X_Vector` step. The following example illustrates a pipeline, where the initial phase consists of one step (`asIntegerVector`) and the coerced phase has no step. @@ -58,15 +69,15 @@ The next listing shows a pipeline having two steps belonging to the initial phas findFirst(); ``` -The `singleElement()` condition requires that the string vector contain exactly one element. The `findFirst()` step retrieves the first element (the head) while exiting the coerced phase and entering the head phase. +The `singleElement()` condition requires that the string vector contain exactly one element. The `findFirst()` step retrieves the first element of the vector argument (the head) while exiting the coerced phase and entering the head phase. -In the **head** phase, the argument value corresponds to the first element of the vector processed in the preceding coerced phase and may be handled basically by the same steps as in the initial phase, except the `as_X_Vector` steps. The head phase in the following example consists of two steps - `mustBe(notLogicalNA())` and `map(toBoolean())` - where the former asserts that the head of the logical vector argument must not be `NA` and the latter converts the logical value from the vector's head to the corresponding `boolean` value. The `findFirst` step in the coerced phase picks the head of the vector or returns the `RRuntime.LOGICAL_FALSE` in case the vector is empty. +In the **head** phase, the argument value corresponds to the first element of the vector processed in the preceding coerced phase and may be handled basically by the same steps as in the initial phase, except the `as_X_Vector` steps. The head phase in the following example consists of two steps - `mustBe(logicalNA().not())` and `map(toBoolean())` - where the former asserts that the head of the logical vector argument must not be `NA` and the latter converts the logical value from the vector's head to the corresponding `boolean` value. The `findFirst` step in the coerced phase picks the head of the vector or returns the `RRuntime.LOGICAL_FALSE` in case the vector is empty. ```java casts.arg("na.encode"). asLogicalVector(). findFirst(RRuntime.LOGICAL_FALSE). - mustBe(notLogicalNA()). + mustBe(logicalNA().not()). map(toBoolean()); ``` @@ -80,6 +91,14 @@ import com.oracle.truffle.r.nodes.builtin.casts.fluent.CastNodeBuilder.newCastBu CastNode myCastNode = newCastBuilder().asStringVector().findFirst("default").buildCastNode(); ``` +### Class `Predef` + +The `CastBuilder.Predef` class defines the `cast pipelines` DSL. By importing statically its content, all language elements, such as filters and mappers, become available. + +```java +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.* +``` + ### Steps The following subsections are dealing with the specific types of pipeline steps. @@ -92,16 +111,12 @@ There are two steps that actually insert no node into the pipeline. The `default There are two modal steps - `mustBe` and `shouldBe` - that establish assertions on the argument value. If the assertion fails, the former raises an error, while the latter outputs a warning. If no message is specified, the default one is used. These steps may be used in all phases. -The assertion conditions are passed as the first argument of those steps and must match the context argument type, which is `java.lang.Object` initially and may be made more specific further in the pipeline by certain steps, among which is also the `mustBe` one. The conditions are objects implementing the `ArgumentFilter<T, R extends T>` interface. Under normal circumstances, the user is not supposed to implement custom conditions. Instead, there is a set of predefined conditions in the `CastBuilder.Predef` class, which should contain all conditions occuring in the original GnuR code, although some may be missing. - -```java -import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.* -``` +The assertion conditions are passed as the first argument of those steps and must match the context argument type, which is `java.lang.Object` initially and may be made more specific further in the pipeline by certain steps, among which is also the `mustBe` one. The conditions are objects implementing the `ArgumentFilter<T, R extends T>` interface. Under normal circumstances, the user is not supposed to implement custom conditions. Instead, there is a set of predefined conditions in the `CastBuilder.Predef` class, which should contain all conditions occuring in the original GnuR code, although some may be missing, while others may be added. The `ArgumentFilter<T, R extends T>` contains the logical operators `and`, `or` and `not`, which allows constructing more complex assertions from the elementary building blocks, as shown in the following listing. The expression asserts that the argument value must be a non-null string or logical value. ```java -casts.arg("msg1").mustBe(notNull().and(stringValue().or(logicalValue()))); +casts.arg("msg1").mustBe(nullValue().not().and(stringValue().or(logicalValue()))); ``` The `mustBe` step changes the context argument type of the argument in the following steps accordingly to the used filter expression. For example, in the following pipeline the `mustBe` step asserts that the argument must be a vector. This assertion narrows the context argument type in the following steps to `RAbstractVector`. Therefore, the conditions requiring a vector as the input value, such as `size`, may be used in the subsequent `shouldBe` step. @@ -112,18 +127,30 @@ casts.arg("x").mustBe(abstractVectorValue()).shouldBe(size(2)); #### Mapping Steps -The mapping steps allow converting the argument from one type to another using the so-called mappers passed as the argument of those steps. There are two mapping steps: `map` and `mapIf`. The former always converts the argument using the mapper object passed as the first argument, while the latter converts the argument only if the condition passed as the first argument is `true`. The mapper object implements the `ArgumentMapper<S, R>` interface, nevertheless, just as in the case of the filter conditions, the user is not expected to implement custom ones, since the `CastBuilder.Predef` class should contain all necessary mappers. +The mapping steps allow converting the argument from one type to another using the so-called mappers passed as the argument of those steps. There are three mapping steps: `map`, `mapIf` and `returnIf`. The first one always converts the argument using the mapper object passed as the first argument, while the second converts the argument only if the condition passed as the first argument is `true`. Both converted and non-converted arguments then proceed to the next step in the pipeline. The third one works as the second one, except that the converted value exits the pipeline without further processing. +Both `mapIf` and `returnIf` allow specifying the false mapping branch too. Interestingly, the `returnIf` can be specified with no mapping branch at all, which results in exiting the pipeline with the input argument if it matches the filter condition (it is equivalent to the true branch being the identity mapper). +The following pipeling returns NULL immediately without propagating it further in the pipeline: + +```java +casts.arg("x").returnIf(nullValue()).asIntegerVector().findFirst(); +``` + +The mapper object implements the `ArgumentMapper<S, R>` interface, nevertheless, just as in the case of the filter conditions, the user is not expected to implement custom ones, since the `CastBuilder.Predef` class should contain all necessary mappers. The mapping steps may be used in all phases except the coerced phase, for the time being. The signatures of the mapping steps are: ```java -map(ArgumentMapper<T, S> mapFn) -mapIf(ArgumentFilter<? super T, ? extends S> argFilter, ArgumentMapper<S, R> mapFn) +map(Mapper<T, S> mapFn) +mapIf(Filter<? super T, ? extends S> argFilter, Mapper<S, R> trueBranchMapper) +mapIf(Filter<? super T, ? extends S> argFilter, Mapper<S, R> trueBranchMapper, Mapper<S, R> falseBranchMapper) +returnIf(Filter<? super T, ? extends S> argFilter) +returnIf(Filter<? super T, ? extends S> argFilter, Mapper<S, R> trueBranchMapper) +returnIf(Filter<? super T, ? extends S> argFilter, Mapper<S, R> trueBranchMapper, Mapper<S, R> falseBranchMapper) ``` -A usage of the unconditional is illustrated in the following listing, where a logical value is mapped to a boolean value using the `toBoolean()` mapper. Since the unconditional mapping changes the context argument type according to the output type of the used mapper, it is possible to append the `shouldBe` modal step with the `trueValue()` filter requiring a boolean value on its input. +A usage of the unconditional map step is illustrated in the following listing, where a logical value is mapped to a boolean value using the `toBoolean()` mapper. Since the unconditional mapping changes the context argument type according to the output type of the used mapper, it is possible to append the `shouldBe` modal step with the `trueValue()` filter requiring a boolean value on its input. ```java casts.arg("na.rm"). @@ -133,7 +160,7 @@ A usage of the unconditional is illustrated in the following listing, where a lo shouldBe(trueValue()); ``` -In the following pipeline **only** null values are converted to the empty string in the initial phase. In contrast to the unconditional mapping, the conditional mapping does not change the context type. +In the following pipeline **only** null values are converted to the empty string in the initial phase. In contrast to the unconditional mapping, the conditional mapping reduces the context type to `Object`. ```java casts.arg("quote").mapIf(nullValue(), constant("")); @@ -143,11 +170,13 @@ casts.arg("quote").mapIf(nullValue(), constant("")); The vector coercion steps coerce the input argument value to the specified vector type. These steps can be used in the initial phase only. There are the following steps available: -* `asIntegerVector()`: coerces the input value to `RAbstractIntVector` -* `asDoubleVector()`: coerces the input value to `RAbstractDoubleVector` -* `asLogicalVector()`: coerces the input value to `RAbstractLogicalVector` -* `asStringVector()`: coerces the input value to `RAbstractStringVector` -* `asVector()`: coerces the input value to `RAbstractVector` +* `asIntegerVector()`: coerces the input value to `RAbstractIntVector` (see `CastIntegerNode`) +* `asDoubleVector()`: coerces the input value to `RAbstractDoubleVector` (see `CastDoubleNode`) +* `asLogicalVector()`: coerces the input value to `RAbstractLogicalVector` (see `CastLogicalNode`) +* `asStringVector()`: coerces the input value to `RAbstractStringVector` (see `CastStringNode`) +* `asComplexVector()`: coerces the input value to `RAbstractComplexVector` (see `CastComplexNode`) +* `asRawVector()`: coerces the input value to `RAbstractRawVector` (see `CastRawNode`) +* `asVector()`: coerces the input value to `RAbstractVector` (see `CastToVectorNode`) All these steps terminate the initial phase and start the coerced phase. @@ -162,9 +191,10 @@ findFirst() findFirst(RError.Message message, Object... messageArgs) <E>findFirst(E defaultValue) <E>findFirst(E defaultValue, RError.Message message, Object... messageArgs) +findFirstOrNull() ``` -The first variant raises the default error of the pipeline as long as the vector is empty, the second variant throws the error specified in its arguments, the third one returns the default value instead of raising an error and the fourth one returns the default value and prints the warning specified in its arguments. +The first variant raises the default error of the pipeline as long as the vector is empty, the second variant throws the error specified in its arguments, the third one returns the default value instead of raising an error and the fourth one returns the default value and prints the warning specified in its arguments. The fifth one returns NULL if the vector argument is empty. #### notNA @@ -177,20 +207,20 @@ notNA(T naReplacement) notNA(T naReplacement, RError.Message message, Object... messageArgs) ``` -Analogously to the findFirst step, the no-arg version throws the default error if the argument value is NA, while the second version throws the specified error. The next two versions return the `naReplacement` value, instead of raising an exception, while the last version prints the specified warning. +Analogously to the findFirst step, the no-arg version throws the default error if the argument value is an NA, while the second version throws the specified error. The next two versions return the `naReplacement` value, instead of raising an exception, while the last version prints the specified warning. The notNA step does not change the context argument type. ### Handling of RNull and RMissing values -By default, `RNull` and `RMissing` argument values are sent to the pipeline. While most of the pipeline cast nodes ignore those values and let them pass through, there are some nodes that may perform some transformation of those values. For example, the `FindFirstNode` node replaces both `RNull` and `RMissing` by the replacement values specified in the corresponding `findFirst(repl)` pipeline step. Also the `CastToVectorNode` coercion node replaces those values by an empty list provided that the `isPreserveNonVector` flag is set. +By default, `RNull` and `RMissing` argument values __are sent to the pipeline__. While most of the pipeline cast nodes ignore those values and let them pass through, there are some nodes that may perform some transformation of those values. For example, the `FindFirstNode` node replaces both `RNull` and `RMissing` by the replacement values specified in the corresponding `findFirst(repl)` pipeline step. Also the `CastToVectorNode` coercion node replaces those values by an empty list provided that the `isPreserveNonVector` flag is set (i.e. `asVector(true)`) . The following list summarizes the behavior of a couple of pipeline steps with regard to the special values: -* `as<TYPE>()` coercions: RNull/RMissing passing through +* `as<TYPE>()` coercions: RNull/RMissing pass through, except `asVector(true)`, which converts null to the empty list * `findFirst`: RNull/RMissing treated as an empty vector, i.e. if the default value is specified it is used as a replacement for the special value, otherwise an error is raised. -* `notNA`: RNull/RMissing passing through -* `mustBe`, `shouldBe`: the filter condition determines whether RNull/RMissing is let through, e.g. `mustBe(stringValue())` blocks NULL since it is obviously not a string value. On the other hand, `shouldBe(stringValue())` lets the NULL through. +* `notNA`: RNull/RMissing pass through +* `mustBe`, `shouldBe`: the filter condition determines whether RNull/RMissing is let through, e.g. `mustBe(stringValue())` blocks NULL since it is obviously not a string value. On the other hand, `shouldBe(stringValue())` lets the NULL through. On the other hand, `mustBe(nullValue().or(stringValue))` lets NULL pass. * `mapIf`: the condition behaves accordingly to the used filter (in the 1st parameter). For example, step `mapIf(stringValue(), constant(0), constant(1))` maps NULL to 1. The following cast pipeline examples aim to elucidate the behavior concerning the special values. @@ -213,16 +243,13 @@ Both NULL and MISSING pass through the pipeline: cb.arg("x").mustBe(stringValue().not()); ``` -NULL does not pass through the pipeline. MISSING passes through. - -```java - cb.arg("x").mustNotBeNull().mustBe(stringValue().not()); -``` - -The same as above: +NULL does not pass through the pipeline. MISSING passes through. All the following pipelines are equivalent: ```java + cb.arg("x").mustBe(nullValue().not()).mustBe(stringValue().not()); cb.arg("x").mustBe(nullValue().not().and(stringValue().not())); + cb.arg("x").mustBe((nullValue().or(stringValue())).not()); + cb.arg("x").mustNotBeNull().mustBe(stringValue().not()); ``` Neither NULL nor MISSING pass through the pipeline: @@ -231,20 +258,6 @@ Neither NULL nor MISSING pass through the pipeline: cb.arg("x").asStringVector().mustBe(singleElement()); ``` -#### Overriding the default behavior - -A cast pipeline can be configured not to send `RNull` and/or `RMissing` to the cast nodes forming the cast pipeline. Then those values either bypass the pipeline, being eventually transformed to some constant, or an error is raised. One can use the following steps in the pre-initial phase to override the default behavior: - -* `allowNull` - RNull bypasses the pipeline -* `mustNotBeNull(errorMsg)` - the error with errorMsg is raised when the input argument is `RNull` -* `mapNull(mapper)` - `RNull` is transformed using the mapper. The `RNull` replacement bypasses the pipeline. - -Analogous methods exist for `RMissing`. - -#### Optimizing the default behavior - -The above-mentioned overriding configurations may also be applied behind-the-scenes as optimization of the pipelines with certain structure. For instance, `allowNull()` may be activated if the pipeline contains no `RNull/RMissing` handling cast node, such as `FindFirstNode` or `CastToVectorNode`. The `mustNotBeNull` configuration may optimize pipelines containing cast nodes raising an error for `RNull/RMissing`, such as the nodes produced by steps `findFirst()`, `findFirst(error)` or `mustBe(singleElement())`. Or the `mapNull(x)` configuration may be applied provided that the pipeline's last step is `findFirst(x)`. - ### Cast Pipeline Optimizations The declarative character of cast pipelines allows for a number of optimizations done during the pipeline construction (some of them are mentioned in the previous section). @@ -257,6 +270,8 @@ Provided that the pipeline contains the `findFirst(x)` step specifying the repla String, double and integer argument values are routed directly after the `FindFirstNode`. On the other hand, logical values bypass the whole pipeline provided that the pipeline ends by the pattern `asLogicalVector()...findFirst().map(toBoolean())`. +For more details see `BypassNode`. + ### Sample Builtins Using Cast Pipelines * [Scan.java](../../com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java) @@ -264,13 +279,13 @@ String, double and integer argument values are routed directly after the `FindFi ### Using `rbdiag` tool -The `mx rbdiag` tool implements the functionality of the static and dynamic stages of the chimney sweeping. This command prints a brief report for each cast pipeline defined in a given builtin. +The `mx rbdiag` tool diagnoses a given builtin or all builtins. This command prints a brief report for each cast pipeline defined in a given builtin including a summary of the diagnosis. -#### Static stage +#### Result type analysis -In the case of the static diagnostics the tool provides information about all possible result types of each argument and how those result types are bound to the corresponding arguments of the builtin's specializations. Importantly, the report can reveal unbound argument types that can potentially cause the missing specialization error. +This analysis provides information about all possible result types of each argument and how those result types are bound to the corresponding arguments of the builtin's specializations. Importantly, the report can reveal unbound argument types that can potentially cause the missing specialization error. Further, it can reveal the so-called __dead__ specializations, i.e. specializations that are never invoked. -It can be illustrated on the `rowsum_matrix` builtin. The `mx` command is then as follows: +This analyis can be illustrated on the `rowsum_matrix` builtin. The `mx` command is then as follows: ```bash mx rbdiag rowsum_matrix @@ -280,13 +295,13 @@ It prints a rather lengthy report, of which the following snippet shows the diag ```bash Pipeline for 'x' (arg[0]): + Warning: Unbound types: + [Integer, Double] Result types union: [Integer, RAbstractIntVector, RAbstractDoubleVector, Double] Bound result types: potential (RAbstractIntVector->RVector) in Object rowsum(*RVector*,RVector,RVector,boolean,RStringVector) potential (RAbstractDoubleVector->RVector) in Object rowsum(*RVector*,RVector,RVector,boolean,RStringVector) - Unbound types: - [Integer, Double] ``` The `Result types union` section lists all result types possibly produced by the pipeline. The `Bound result types` section outlines all bound type; i.e. the argument types for which there is a corresponding specialization in the builtin. In this case, only two argument types - `RAbstractIntVector` and `RAbstractDoubleVector` - are bound to the `rowsum(RVector,RVector,RVector,boolean,RStringVector)` specialization. (The asterisks surrounding the first argument in the specialization indicate the argument to which the type in question is bound). The `potential` flag indicates here that there is an underlying implicit conversion between the type produced by the cast pipeline and the argument type in the specialization. @@ -302,15 +317,13 @@ Nevertheless, there are two arguments left unbound - `Integer` and `Double`. Thi potential (Double->RAbstractVector) in Object rowsum(*RAbstractVector*,RAbstractVector,RAbstractVector,boolean,RAbstractStringVector) full (RAbstractDoubleVector->RAbstractVector) in Object rowsum(*RAbstractVector*,RAbstractVector,RAbstractVector,boolean,RAbstractStringVector) potential (Integer->RAbstractVector) in Object rowsum(*RAbstractVector*,RAbstractVector,RAbstractVector,boolean,RAbstractStringVector) - Unbound types: - [] ``` The produced result types are naturally the same ones, but now all of them are bound. In conclusion, using the abstract types resulted in the binding of the previously unbound types. Note: The `full` flag indicates that the pipeline's type is a subtype of the specialization's argument type. The `partial` flag, which is not seen here, indicates that the pipeline's type is a supertype of the specialization's argument type. The `potential` flag indicates an implicit conversion or a potentially non-empty intersection between the two types, typically if they are both interfaces. -#### Dynamic stage +#### Chimney-sweeping In the dynamic stage, the tool uses the samples inferred from the cast pipelines of a given builtin along with some 'springboard' list of valid arguments to construct a number of argument combinations. These argument lists are then used to invoke both the FastR builtin and its GnuR counterpart. The tool uses the test output file (ExpectedTestOutput.test) to obtain the 'springboard' argument lists for a given builtin. These valid argument lists are reproduced by substituting combinations of generated samples. The varied arguments are then used against both GnuR and FastR. If the two outputs differ, the tool reports the command and the corresponding outputs. -- GitLab