From 6487df44c26d0f1ff4bbe2fd232f09389e74e1ac Mon Sep 17 00:00:00 2001 From: Lukas Stadler <lukas.stadler@oracle.com> Date: Mon, 2 Oct 2017 19:18:39 +0200 Subject: [PATCH] improve performance of scan / type.convert builtins, custom implementation for "rgb" and "col2rgb" --- .../fastrGrid/FastRGridExternalLookup.java | 8 + .../r/library/fastrGrid/GridColorUtils.java | 31 +- .../r/library/fastrGrid/color/Col2RGB.java | 126 ++++++++ .../r/library/fastrGrid/color/RGB.java | 273 ++++++++++++++++++ .../truffle/r/library/utils/TypeConvert.java | 44 +-- .../truffle/r/nodes/builtin/base/Scan.java | 242 +++++++--------- .../oracle/truffle/r/runtime/RRuntime.java | 79 +++++ mx.fastr/copyrights/overrides | 2 + 8 files changed, 643 insertions(+), 162 deletions(-) create mode 100644 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/Col2RGB.java create mode 100644 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java index 55292b2447..414f810eac 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/FastRGridExternalLookup.java @@ -27,6 +27,8 @@ import com.oracle.truffle.r.library.fastrGrid.DisplayList.LInitDisplayList; import com.oracle.truffle.r.library.fastrGrid.DisplayList.LSetDisplayListOn; import com.oracle.truffle.r.library.fastrGrid.PaletteExternals.CPalette; import com.oracle.truffle.r.library.fastrGrid.PaletteExternals.CPalette2; +import com.oracle.truffle.r.library.fastrGrid.color.Col2RGB; +import com.oracle.truffle.r.library.fastrGrid.color.RGB; import com.oracle.truffle.r.library.fastrGrid.grDevices.DevCairo; import com.oracle.truffle.r.library.fastrGrid.grDevices.DevCurr; import com.oracle.truffle.r.library.fastrGrid.grDevices.DevHoldFlush; @@ -185,6 +187,12 @@ public final class FastRGridExternalLookup { case "L_newpagerecording": return new IgnoredGridExternal(RNull.instance); + // Color conversions + case "col2rgb": + return Col2RGB.create(); + case "rgb": + return RGB.create(); + default: if (name.startsWith("L_")) { throw RInternalError.shouldNotReachHere("Unimplemented grid external " + name); diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java index c751e57f43..217e5a92c0 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java @@ -13,10 +13,12 @@ package com.oracle.truffle.r.library.fastrGrid; import java.util.HashMap; import java.util.Locale; +import java.util.Map; import com.oracle.truffle.r.library.fastrGrid.GridState.GridPalette; import com.oracle.truffle.r.library.fastrGrid.device.GridColor; import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.RError.Message; import com.oracle.truffle.r.runtime.RRuntime; import com.oracle.truffle.r.runtime.Utils; @@ -86,15 +88,11 @@ public final class GridColorUtils { return GridColor.TRANSPARENT; } - Object result = findByName(value); + GridColor result = findByName(value); if (result == null) { throw RError.error(RError.NO_CALLER, Message.GENERIC, "Invalid color '" + value + "'."); } - if (result instanceof String) { - return parseHex((String) result); - } - assert result instanceof GridColor : "synonyms map should only contain Strings and GridColors"; - return (GridColor) result; + return result; } public static String gridColorToRString(GridColor color) { @@ -104,7 +102,7 @@ public final class GridColorUtils { return String.format("#%02x%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); } - private static GridColor parseHex(String value) { + public static GridColor parseHex(String value) { // hex format, e.g. #ffffff int red = Integer.parseInt(value.substring(1, 3), 16); int green = Integer.parseInt(value.substring(3, 5), 16); @@ -116,8 +114,8 @@ public final class GridColorUtils { return new GridColor(red, green, blue, alpha); } - private static Object findByName(String synonym) { - return NamesHolder.NAMES.get(normalizeColorName(synonym)); + private static GridColor findByName(String synonym) { + return (GridColor) NamesHolder.NAMES.get(normalizeColorName(synonym)); } // GnuR compares the user given color name to the dictionary ignoring spaces and case. We remove @@ -162,11 +160,7 @@ public final class GridColorUtils { } private static int paletteIdxFromString(String colorId) { - try { - return Integer.parseInt(colorId, 10); - } catch (NumberFormatException ex) { - return RRuntime.INT_NA; - } + return RRuntime.parseIntWithNA(colorId); } private static final class NamesHolder { @@ -831,6 +825,15 @@ public final class GridColorUtils { NAMES.put("yellow3", "#CDCD00"); NAMES.put("yellow4", "#8B8B00"); NAMES.put("yellowgreen", "#9ACD32"); + for (Map.Entry<String, Object> entry : NAMES.entrySet()) { + if (entry.getValue() instanceof GridColor) { + // nothing to do + } else if (entry.getValue() instanceof String) { + entry.setValue(parseHex((String) entry.getValue())); + } else { + throw RInternalError.shouldNotReachHere(); + } + } } } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/Col2RGB.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/Col2RGB.java new file mode 100644 index 0000000000..3afaee6ca7 --- /dev/null +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/Col2RGB.java @@ -0,0 +1,126 @@ +/* + * This material is distributed under the GNU General Public License + * Version 2. You may review the terms of this license at + * http://www.gnu.org/licenses/gpl-2.0.html + * + * Copyright (c) 1997-2014, The R Core Team + * Copyright (c) 2003, The R Foundation + * Copyright (c) 2017, Oracle and/or its affiliates + * + * All rights reserved. + */ +package com.oracle.truffle.r.library.fastrGrid.color; + +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.asIntegerVector; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.asStringVector; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.doubleValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.integerValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.logicalValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.toBoolean; + +import java.util.Arrays; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.r.library.fastrGrid.GridColorUtils; +import com.oracle.truffle.r.library.fastrGrid.GridContext; +import com.oracle.truffle.r.library.fastrGrid.GridState.GridPalette; +import com.oracle.truffle.r.library.fastrGrid.device.GridColor; +import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetNamesAttributeNode; +import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; +import com.oracle.truffle.r.nodes.function.opt.ShareObjectNode; +import com.oracle.truffle.r.runtime.RError.Message; +import com.oracle.truffle.r.runtime.RRuntime; +import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RList; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.data.RStringVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractVector; + +public abstract class Col2RGB extends RExternalBuiltinNode.Arg2 { + + private static final RStringVector NAMES = ShareObjectNode.sharePermanent(RDataFactory.createStringVector(new String[]{"red", "green", "blue"}, true)); + private static final RStringVector NAMES_ALPHA = ShareObjectNode.sharePermanent(RDataFactory.createStringVector(new String[]{"red", "green", "blue", "alpha"}, true)); + private static final GridColor TRANSPARENT_WHITE = new GridColor(255, 255, 255, 0); + + static { + Casts casts = new Casts(Col2RGB.class); + casts.arg(0).mapIf(integerValue().or(doubleValue()), asIntegerVector(), asStringVector()); + casts.arg(1).mustBe(logicalValue()).asLogicalVector().findFirst().map(toBoolean()); + } + + public static Col2RGB create() { + return Col2RGBNodeGen.create(); + } + + @Specialization + @TruffleBoundary + Object execute(RAbstractVector col, boolean alpha, + @Cached("create()") GetNamesAttributeNode getNames) { + int length = col.getLength(); + int columns = alpha ? 4 : 3; + int[] result = new int[length * columns]; + + GridPalette palette = GridContext.getContext().getGridState().getPalette(); + int pos = 0; + if (col instanceof RAbstractIntVector) { + RAbstractIntVector vector = (RAbstractIntVector) col; + + for (int i = 0; i < length; i++) { + int value = vector.getDataAt(i); + GridColor color; + if (RRuntime.isNA(value) || value == 0) { + color = TRANSPARENT_WHITE; + } else if (value < 0) { + throw error(Message.GENERIC, "numerical color values must be >= 0, found " + value); + } else { + color = palette.colors[(value - 1) % palette.colors.length]; + } + pos = addColor(alpha, result, pos, color); + } + } else if (col instanceof RAbstractStringVector) { + RAbstractStringVector vector = (RAbstractStringVector) col; + + for (int i = 0; i < length; i++) { + String value = vector.getDataAt(i); + GridColor color; + if (value.length() > 0 && value.charAt(0) >= '0' && value.charAt(0) <= '9') { + int index = RRuntime.parseIntWithNA(value); + if (RRuntime.isNA(index)) { + throw error(Message.GENERIC, "invalid color specification \"" + value + "\""); + } else if (index == 0) { + color = TRANSPARENT_WHITE; + } else { + color = palette.colors[(index - 1) % palette.colors.length]; + } + } else if ("transparent".equals(value)) { + color = TRANSPARENT_WHITE; + } else { + color = GridColorUtils.gridColorFromString(value); + } + pos = addColor(alpha, result, pos, color); + } + } else { + warning(Message.GENERIC, "supplied color is neither numeric nor character"); + Arrays.fill(result, RRuntime.INT_NA); + } + + RStringVector names = getNames.getNames(col); + RList dimNames = RDataFactory.createList(new Object[]{alpha ? NAMES_ALPHA : NAMES, names == null ? RNull.instance : names}); + return RDataFactory.createIntVector(result, false, new int[]{columns, length}, null, dimNames); + } + + private static int addColor(boolean alpha, int[] result, int start, GridColor color) { + int pos = start; + result[pos++] = color.getRed(); + result[pos++] = color.getGreen(); + result[pos++] = color.getBlue(); + if (alpha) { + result[pos++] = color.getAlpha(); + } + return pos; + } +} diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java new file mode 100644 index 0000000000..e599a553cd --- /dev/null +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java @@ -0,0 +1,273 @@ +/* + * This material is distributed under the GNU General Public License + * Version 2. You may review the terms of this license at + * http://www.gnu.org/licenses/gpl-2.0.html + * + * Copyright (c) 1997-2014, The R Core Team + * Copyright (c) 2003, The R Foundation + * Copyright (c) 2017, Oracle and/or its affiliates + * + * All rights reserved. + */ +package com.oracle.truffle.r.library.fastrGrid.color; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.dsl.TypeSystemReference; +import com.oracle.truffle.r.library.fastrGrid.color.RGBNodeGen.DoubleRGBNodeGen; +import com.oracle.truffle.r.library.fastrGrid.color.RGBNodeGen.IntegerRGBNodeGen; +import com.oracle.truffle.r.library.fastrGrid.device.GridColor; +import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; +import com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef; +import com.oracle.truffle.r.nodes.unary.CastDoubleNode; +import com.oracle.truffle.r.nodes.unary.CastIntegerNode; +import com.oracle.truffle.r.runtime.RError.Message; +import com.oracle.truffle.r.runtime.RRuntime; +import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.data.RStringVector; +import com.oracle.truffle.r.runtime.data.RTypes; +import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; +import com.oracle.truffle.r.runtime.nodes.RBaseNode; + +public abstract class RGB extends RExternalBuiltinNode.Arg6 { + + static { + Casts casts = new Casts(RGB.class); + casts.arg(4).asDoubleVector().findFirst(); + casts.arg(5).mapNull(Predef.emptyStringVector()).asStringVector(); + } + + public static RGB create() { + return RGBNodeGen.create(); + } + + protected static boolean isIntRange(double mcv) { + return mcv == 255; + } + + @Specialization(guards = "isIntRange(mcv)") + @TruffleBoundary + Object doInteger(Object r, Object g, Object b, Object a, @SuppressWarnings("unused") double mcv, RAbstractStringVector names, + @Cached("create()") CastIntegerNode castR, + @Cached("create()") CastIntegerNode castG, + @Cached("create()") CastIntegerNode castB, + @Cached("create()") CastIntegerNode castA, + @Cached("createInteger()") IntegerRGB inner) { + return inner.execute(castR.doCast(r), castG.doCast(g), castB.doCast(b), a == RNull.instance ? RNull.instance : castA.doCast(a), names); + } + + @Specialization(guards = "!isIntRange(mcv)") + @TruffleBoundary + Object doDouble(Object r, Object g, Object b, Object a, double mcv, RAbstractStringVector names, + @Cached("create()") CastDoubleNode castR, + @Cached("create()") CastDoubleNode castG, + @Cached("create()") CastDoubleNode castB, + @Cached("create()") CastDoubleNode castA, + @Cached("createDouble()") DoubleRGB inner) { + if (!Double.isFinite(mcv)) { + throw error(Message.GENERIC, "invalid value of 'maxColorValue'"); + } + return inner.execute(castR.doCast(r), castG.doCast(g), castB.doCast(b), a == RNull.instance ? RNull.instance : castA.doCast(a), mcv, names); + } + + protected static IntegerRGB createInteger() { + return IntegerRGBNodeGen.create(); + } + + protected static DoubleRGB createDouble() { + return DoubleRGBNodeGen.create(); + } + + @TypeSystemReference(RTypes.class) + abstract static class RGBBase extends RBaseNode { + + protected static int addColor(boolean alpha, int[] result, int start, GridColor color) { + int pos = start; + result[pos++] = color.getRed(); + result[pos++] = color.getGreen(); + result[pos++] = color.getBlue(); + if (alpha) { + result[pos++] = color.getAlpha(); + } + return pos; + } + + private static final char[] HexDigits = "0123456789ABCDEF".toCharArray(); + + protected static String rgb2rgb(int r, int g, int b) { + char[] colBuf = new char[7]; + colBuf[0] = '#'; + colBuf[1] = HexDigits[(r >> 4) & 15]; + colBuf[2] = HexDigits[r & 15]; + colBuf[3] = HexDigits[(g >> 4) & 15]; + colBuf[4] = HexDigits[g & 15]; + colBuf[5] = HexDigits[(b >> 4) & 15]; + colBuf[6] = HexDigits[b & 15]; + return new String(colBuf); + } + + protected static String rgba2rgb(int r, int g, int b, int a) { + char[] colBuf = new char[9]; + colBuf[0] = '#'; + colBuf[1] = HexDigits[(r >> 4) & 15]; + colBuf[2] = HexDigits[r & 15]; + colBuf[3] = HexDigits[(g >> 4) & 15]; + colBuf[4] = HexDigits[g & 15]; + colBuf[5] = HexDigits[(b >> 4) & 15]; + colBuf[6] = HexDigits[b & 15]; + colBuf[7] = HexDigits[(a >> 4) & 15]; + colBuf[8] = HexDigits[a & 15]; + return new String(colBuf); + } + + protected int scaleColor(double x) { + if (RRuntime.isNA(x)) { + error(Message.GENERIC, "color intensity NA, not in [0,1]"); + } + if (!Double.isFinite(x) || x < 0.0 || x > 1.0) { + error(Message.GENERIC, "color intensity " + x + ", not in [0,1]"); + } + return (int) (255 * x + 0.5); + } + + protected int checkColor(int x) { + if (RRuntime.isNA(x)) { + error(Message.GENERIC, "color intensity NA, not in 0:255"); + } + if (x < 0 || x > 255) { + error(Message.GENERIC, "color intensity " + x + ", not in 0:255"); + } + return x; + } + + protected int scaleAlpha(double x) { + if (RRuntime.isNA(x)) { + error(Message.GENERIC, "alpha level NA, not in [0,1]"); + } + if (!Double.isFinite(x) || x < 0.0 || x > 1.0) { + error(Message.GENERIC, "alpha level " + x + ", not in [0,1]"); + } + return (int) (255 * x + 0.5); + } + + protected int checkAlpha(int x) { + if (RRuntime.isNA(x)) { + error(Message.GENERIC, "alpha level NA, not in 0:255"); + } + if (x < 0 || x > 255) { + error(Message.GENERIC, "alpha level " + x + ", not in 0:255"); + } + return x; + } + } + + abstract static class IntegerRGB extends RGBBase { + + public abstract RStringVector execute(Object r, Object g, Object b, Object a, RAbstractStringVector names); + + @Specialization + @TruffleBoundary + protected RStringVector doAlpha(RAbstractIntVector r, RAbstractIntVector g, RAbstractIntVector b, RAbstractIntVector a, RAbstractStringVector names) { + int lengthR = r.getLength(); + int lengthG = g.getLength(); + int lengthB = b.getLength(); + int lengthA = a.getLength(); + if (lengthR == 0 || lengthG == 0 || lengthB == 0 || lengthA == 0) { + return RDataFactory.createEmptyStringVector(); + } + int length = Math.max(Math.max(lengthR, lengthG), Math.max(lengthB, lengthA)); + if (names.getLength() != 0 && names.getLength() != length) { + throw error(Message.GENERIC, "invalid 'names' vector"); + } + String[] result = new String[length]; + for (int i = 0; i < length; i++) { + int rValue = r.getDataAt(i % lengthR); + int gValue = g.getDataAt(i % lengthG); + int bValue = b.getDataAt(i % lengthB); + int aValue = a.getDataAt(i % lengthA); + result[i] = rgba2rgb(checkColor(rValue), checkColor(gValue), checkColor(bValue), checkAlpha(aValue)); + } + return RDataFactory.createStringVector(result, true, names.getLength() == 0 ? null : names.materialize()); + } + + @Specialization + @TruffleBoundary + protected RStringVector doNonAlpha(RAbstractIntVector r, RAbstractIntVector g, RAbstractIntVector b, @SuppressWarnings("unused") RNull a, RAbstractStringVector names) { + int lengthR = r.getLength(); + int lengthG = g.getLength(); + int lengthB = b.getLength(); + if (lengthR == 0 || lengthG == 0 || lengthB == 0) { + return RDataFactory.createEmptyStringVector(); + } + int length = Math.max(Math.max(lengthR, lengthG), lengthB); + if (names.getLength() != 0 && names.getLength() != length) { + throw error(Message.GENERIC, "invalid 'names' vector"); + } + String[] result = new String[length]; + for (int i = 0; i < length; i++) { + int rValue = r.getDataAt(i % lengthR); + int gValue = g.getDataAt(i % lengthG); + int bValue = b.getDataAt(i % lengthB); + result[i] = rgb2rgb(checkColor(rValue), checkColor(gValue), checkColor(bValue)); + } + return RDataFactory.createStringVector(result, true, names.getLength() == 0 ? null : names.materialize()); + } + } + + abstract static class DoubleRGB extends RGBBase { + + public abstract RStringVector execute(Object r, Object g, Object b, Object a, double mcv, RAbstractStringVector names); + + @Specialization + @TruffleBoundary + protected RStringVector doAlpha(RAbstractDoubleVector r, RAbstractDoubleVector g, RAbstractDoubleVector b, RAbstractDoubleVector a, double mcv, RAbstractStringVector names) { + int lengthR = r.getLength(); + int lengthG = g.getLength(); + int lengthB = b.getLength(); + int lengthA = a.getLength(); + if (lengthR == 0 || lengthG == 0 || lengthB == 0 || lengthA == 0) { + return RDataFactory.createEmptyStringVector(); + } + int length = Math.max(Math.max(lengthR, lengthG), Math.max(lengthB, lengthA)); + if (names.getLength() != 0 && names.getLength() != length) { + throw error(Message.GENERIC, "invalid 'names' vector"); + } + String[] result = new String[length]; + for (int i = 0; i < length; i++) { + double rValue = r.getDataAt(i % lengthR); + double gValue = g.getDataAt(i % lengthG); + double bValue = b.getDataAt(i % lengthB); + double aValue = a.getDataAt(i % lengthA); + result[i] = rgba2rgb(scaleColor(rValue / mcv), scaleColor(gValue / mcv), scaleColor(bValue / mcv), scaleAlpha(aValue / mcv)); + } + return RDataFactory.createStringVector(result, true, names.getLength() == 0 ? null : names.materialize()); + } + + @Specialization + @TruffleBoundary + protected RStringVector doNonAlpha(RAbstractDoubleVector r, RAbstractDoubleVector g, RAbstractDoubleVector b, @SuppressWarnings("unused") RNull a, double mcv, RAbstractStringVector names) { + int lengthR = r.getLength(); + int lengthG = g.getLength(); + int lengthB = b.getLength(); + if (lengthR == 0 || lengthG == 0 || lengthB == 0) { + return RDataFactory.createEmptyStringVector(); + } + int length = Math.max(Math.max(lengthR, lengthG), lengthB); + if (names.getLength() != 0 && names.getLength() != length) { + throw error(Message.GENERIC, "invalid 'names' vector"); + } + String[] result = new String[length]; + for (int i = 0; i < length; i++) { + double rValue = r.getDataAt(i % lengthR); + double gValue = g.getDataAt(i % lengthG); + double bValue = b.getDataAt(i % lengthB); + result[i] = rgb2rgb(scaleColor(rValue / mcv), scaleColor(gValue / mcv), scaleColor(bValue / mcv)); + } + return RDataFactory.createStringVector(result, true, names.getLength() == 0 ? null : names.materialize()); + } + } +} diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/TypeConvert.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/TypeConvert.java index 00ef74c006..d4e59d4ed4 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/TypeConvert.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/utils/TypeConvert.java @@ -35,8 +35,10 @@ import static com.oracle.truffle.r.runtime.RError.Message.INVALID_ARG; import static com.oracle.truffle.r.runtime.RRuntime.LOGICAL_FALSE; import java.util.Arrays; -import java.util.TreeSet; +import java.util.Map; +import java.util.TreeMap; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.r.nodes.attributes.SetFixedAttributeNode; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; @@ -93,9 +95,16 @@ public abstract class TypeConvert extends RExternalBuiltinNode.Arg5 { data[firstPos] = firstVal; for (int i = firstPos + 1; i < data.length; i++) { String s = x.getDataAt(i); - boolean isNA = isNA(s, naStrings); - data[i] = isNA ? RRuntime.INT_NA : RRuntime.string2intNoCheck(s, true); - complete = complete && !isNA; + if (isNA(s, naStrings)) { + data[i] = RRuntime.INT_NA; + complete = false; + } else { + int result = RRuntime.parseInt(s); + if (result == RRuntime.INT_NA) { + throw new NumberFormatException(); + } + data[i] = result; + } } return RDataFactory.createIntVector(data, complete); } @@ -133,13 +142,14 @@ public abstract class TypeConvert extends RExternalBuiltinNode.Arg5 { } @Specialization + @TruffleBoundary protected Object typeConvert(RAbstractStringVector x, RAbstractStringVector naStrings, boolean asIs, @SuppressWarnings("unused") Object dec, @SuppressWarnings("unused") Object numeral) { if (x.getLength() == 0) { return RDataFactory.createEmptyLogicalVector(); } int i = 0; - while (i < x.getLength() && isNA(x.getDataAt(i), naStrings)) { + while (i < x.getLength() && (x.getDataAt(i).isEmpty() || isNA(x.getDataAt(i), naStrings))) { i++; } @@ -153,7 +163,7 @@ public abstract class TypeConvert extends RExternalBuiltinNode.Arg5 { String s = x.getDataAt(i); if (RRuntime.hasHexPrefix(s)) { // this is a mess - // double takes precedense even if s is a hexadecimal integer + // double takes precedence even if s is a hexadecimal integer try { double doubleVal = RRuntime.string2doubleNoCheck(s, true); return readDoubleVector(x, i, doubleVal, naStrings); @@ -191,35 +201,33 @@ public abstract class TypeConvert extends RExternalBuiltinNode.Arg5 { if (asIs) { return x; } else { - // create a factor - TreeSet<String> levels = new TreeSet<>(); + // collect levels for a factor result + TreeMap<String, Integer> levels = new TreeMap<>(); for (int j = 0; j < x.getLength(); j++) { s = x.getDataAt(j); if (!isNA(s, naStrings)) { - levels.add(s); + levels.put(s, 0); } } - String[] levelsArray = new String[levels.size()]; - levels.toArray(levelsArray); + // assign levels IDs + int pos = 1; + for (Map.Entry<String, Integer> entry : levels.entrySet()) { + entry.setValue(pos++); + } int[] data = new int[x.getLength()]; boolean complete = true; for (int j = 0; j < data.length; j++) { s = x.getDataAt(j); if (!isNA(s, naStrings)) { - for (int k = 0; k < levelsArray.length; k++) { - if (levelsArray[k].equals(s)) { - data[j] = k + 1; - break; - } - } + data[j] = levels.get(s); } else { data[j] = RRuntime.INT_NA; complete = false; } } RIntVector res = RDataFactory.createIntVector(data, complete); - setLevelsAttrNode.execute(res, RDataFactory.createStringVector(levelsArray, RDataFactory.COMPLETE_VECTOR)); + setLevelsAttrNode.execute(res, RDataFactory.createStringVector(levels.keySet().toArray(new String[0]), RDataFactory.COMPLETE_VECTOR)); return RVector.setVectorClassAttr(res, RDataFactory.createStringVector("factor")); } } diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java index 5640789929..5a93494aad 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Scan.java @@ -28,8 +28,7 @@ import static com.oracle.truffle.r.runtime.builtins.RBehavior.IO; import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.INTERNAL; import java.io.IOException; -import java.util.LinkedList; -import java.util.regex.Pattern; +import java.util.ArrayList; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; @@ -80,10 +79,9 @@ public abstract class Scan extends RBuiltinNode.Arg19 { private static class LocalData { RAbstractStringVector naStrings = null; boolean quiet = false; - String sepchar = null; - String sepregex = null; + char sepchar = 0; // 0 means any whitespace char decchar = '.'; - String quoteset = null; + char[] quoteset = new char[0]; int comchar = NO_COMCHAR; // connection-related (currently not supported) // int ttyflag = 0; @@ -152,14 +150,13 @@ public abstract class Scan extends RBuiltinNode.Arg19 { LocalData data = new LocalData(); // TODO: some sort of character translation happens here? - data.sepchar = sep.isEmpty() ? null : sep.substring(0, 1); - data.sepregex = sep.isEmpty() ? null : Pattern.quote(sep.substring(0, 1)); + data.sepchar = sep.isEmpty() ? 0 : sep.charAt(0); // TODO: some sort of character translation happens here? data.decchar = dec.charAt(0); // TODO: some sort of character translation happens here? - data.quoteset = quotes; + data.quoteset = quotes.toCharArray(); data.naStrings = naStringsVec; @@ -202,89 +199,75 @@ public abstract class Scan extends RBuiltinNode.Arg19 { } } - private static int getFirstQuoteInd(String str, char quotechar, Character sepChar) { - int quoteInd = str.indexOf(quotechar); - if (quoteInd >= 0) { - if (quoteInd == 0 || (str.charAt(quoteInd - 1) == ' ' || str.charAt(quoteInd - 1) == '\t') || sepChar != null && str.charAt(quoteInd - 1) == sepChar) { - // it's a quote character if it starts the string or is preceded by a blank space - return quoteInd; - } + private static int skipWhitespace(String s, int start) { + int pos = start; + while (pos < s.length() && (s.charAt(pos) == ' ' || s.charAt(pos) == '\t')) { + pos++; } - return -1; + return pos; } - private static String[] getQuotedItems(LocalData data, String s) { - LinkedList<String> items = new LinkedList<>(); - - String str = s; - StringBuilder sb = null; - - while (true) { - int sepInd; - if (data.sepchar == null) { - int blInd = str.indexOf(' '); - int tabInd = str.indexOf('\t'); - if (blInd == -1) { - sepInd = tabInd; - } else if (tabInd == -1) { - sepInd = blInd; - } else { - sepInd = Math.min(blInd, tabInd); - } - } else { - assert data.sepchar.length() == 1; - sepInd = str.indexOf(data.sepchar.charAt(0)); + private static boolean isInSet(char ch, char[] quoteset) { + for (int i = 0; i < quoteset.length; i++) { + if (ch == quoteset[i]) { + return true; } + } + return false; + } - Character sepChar = data.sepchar != null ? data.sepchar.charAt(0) : null; - int quoteInd = getFirstQuoteInd(str, data.quoteset.charAt(0), sepChar); - char quoteChar = data.quoteset.charAt(0); - for (int i = 1; i < data.quoteset.length(); i++) { - int ind = getFirstQuoteInd(str, data.quoteset.charAt(i), sepChar); - if (ind >= 0 && (quoteInd == -1 || (quoteInd >= 0 && ind < quoteInd))) { - // update quoteInd if either the new index is smaller or the previous one (for - // another separator) was not found - quoteInd = ind; - quoteChar = data.quoteset.charAt(i); + private static String[] getQuotedItems(LocalData data, String s) { + ArrayList<String> items = new ArrayList<>(); + + char sepchar = data.sepchar; + char[] quoteset = data.quoteset; + int length = s.length(); + int pos = 0; + if (sepchar == 0) { + pos = skipWhitespace(s, pos); + } + if (pos == length) { + return new String[0]; + } + StringBuilder str = new StringBuilder(); + do { + char ch = s.charAt(pos); + if (sepchar == 0 && (ch == ' ' || ch == '\t')) { + pos = skipWhitespace(s, pos); + if (pos == length) { + break; } - } - - if (sb == null) { - // first iteration - if (quoteInd == -1) { - // no quotes at all - return data.sepregex == null ? s.split("\\s+") : s.split(data.sepregex); - } else { - sb = new StringBuilder(); + items.add(str.toString()); + str.setLength(0); + } else if (sepchar != 0 && ch == sepchar) { + pos++; + items.add(str.toString()); + str.setLength(0); + } else if (str.length() == 0 && isInSet(ch, quoteset)) { + char quoteStart = ch; + pos++; + while (true) { + if (pos == length) { + throw RError.error(RError.SHOW_CALLER, Message.INCOMPLETE_FINAL_LINE, s); + } + ch = s.charAt(pos++); + if (ch == quoteStart) { + if (pos < length && s.charAt(pos) == quoteStart) { + str.append(quoteStart); + pos++; + } else { + break; + } + } else { + str.append(ch); + } } - } - - if (sepInd == -1 && quoteInd == -1) { - // no more separators and no more quotes - add the last item and return - sb.append(str); - items.add(sb.toString()); - break; - } - - if (quoteInd >= 0 && (sepInd == -1 || (sepInd >= 0 && quoteInd < sepInd))) { - // quote character was found before the separator character - everything from the - // beginning of str up to the end of quote becomes part of this item - sb.append(str.substring(0, quoteInd)); - int nextQuoteInd = str.indexOf(quoteChar, quoteInd + 1); - sb.append(str.substring(quoteInd + 1, nextQuoteInd)); - str = str.substring(nextQuoteInd + 1, str.length()); } else { - assert sepInd >= 0; - // everything from the beginning of str becomes part of this time and item - // processing is completed (also eat up separators) - String[] tuple = data.sepregex == null ? str.split("\\s+", 2) : str.split(data.sepregex, 2); - assert tuple.length == 2; - sb.append(tuple[0]); - str = tuple[1]; - items.add(sb.toString()); - sb = new StringBuilder(); + str.append(ch); + pos++; } - } + } while (pos < s.length()); + items.add(str.toString()); return items.toArray(new String[items.size()]); } @@ -295,15 +278,11 @@ public abstract class Scan extends RBuiltinNode.Arg19 { if (str == null || str.length == 0) { return null; } else { - String s = str[0].trim(); - if (blSkip && s.length() == 0) { + String[] items = getQuotedItems(data, str[0]); + if (blSkip && items.length == 0) { continue; } else { - if (data.quoteset.length() == 0) { - return data.sepregex == null ? s.split("\\s+") : s.split(data.sepregex); - } else { - return getQuotedItems(data, s); - } + return items.length == 0 ? new String[]{""} : items; } } } @@ -317,8 +296,7 @@ public abstract class Scan extends RBuiltinNode.Arg19 { } private RVector<?> scanFrame(RList what, int maxRecords, int maxLines, boolean flush, boolean fill, @SuppressWarnings("unused") boolean stripWhite, boolean blSkip, boolean multiLine, - LocalData data) - throws IOException { + LocalData data) throws IOException { int nc = what.getLength(); if (nc == 0) { @@ -505,45 +483,49 @@ public abstract class Scan extends RBuiltinNode.Arg19 { } private static Object extractItem(RAbstractVector what, String buffer, LocalData data) { - switch (what.getRType()) { - case Logical: - if (isNaString(buffer, 0, data)) { - return RRuntime.LOGICAL_NA; - } else { - return RRuntime.string2logicalNoCheck(buffer); - } - case Integer: - if (isNaString(buffer, 0, data)) { - return RRuntime.INT_NA; - } else { - return RRuntime.string2intNoCheck(buffer); - } - case Double: - if (isNaString(buffer, 0, data)) { - return RRuntime.DOUBLE_NA; - } else { - return RRuntime.string2doubleNoCheck(buffer); - } - case Complex: - if (isNaString(buffer, 0, data)) { - return RComplex.createNA(); - } else { - return RRuntime.string2complexNoCheck(buffer); - } - case Character: - if (isNaString(buffer, 1, data)) { - return RRuntime.STRING_NA; - } else { - return buffer; - } - case Raw: - if (isNaString(buffer, 0, data)) { - return RDataFactory.createRaw((byte) 0); - } else { - return RRuntime.string2raw(buffer); - } - default: - throw RInternalError.shouldNotReachHere(); + try { + switch (what.getRType()) { + case Logical: + if (isNaString(buffer, 0, data)) { + return RRuntime.LOGICAL_NA; + } else { + return RRuntime.string2logicalNoCheck(buffer); + } + case Integer: + if (isNaString(buffer, 0, data)) { + return RRuntime.INT_NA; + } else { + return RRuntime.parseInt(buffer); + } + case Double: + if (isNaString(buffer, 0, data)) { + return RRuntime.DOUBLE_NA; + } else { + return RRuntime.string2doubleNoCheck(buffer); + } + case Complex: + if (isNaString(buffer, 0, data)) { + return RComplex.createNA(); + } else { + return RRuntime.string2complexNoCheck(buffer); + } + case Character: + if (isNaString(buffer, 1, data)) { + return RRuntime.STRING_NA; + } else { + return buffer; + } + case Raw: + if (isNaString(buffer, 0, data)) { + return RDataFactory.createRaw((byte) 0); + } else { + return RRuntime.string2raw(buffer); + } + default: + throw RInternalError.shouldNotReachHere(); + } + } catch (NumberFormatException e) { + throw RError.error(RError.SHOW_CALLER, Message.SCAN_UNEXPECTED, what.getRType().getName(), buffer); } } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java index 4bece013f1..bfcbd3b5ed 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RRuntime.java @@ -350,6 +350,84 @@ public class RRuntime { // conversions from string + @TruffleBoundary + public static int parseInt(String s) { + int length = s.length(); + long value = 0; + if (s.charAt(0) == '-') { + if (length == 1) { + throw new NumberFormatException(); + } + int pos = 1; + while (pos < length) { + char ch = s.charAt(pos++); + if (ch < '0' || ch > '9') { + throw new NumberFormatException(); + } + value = value * 10 + (ch - '0'); + if (value > (Integer.MAX_VALUE + 1L)) { + return INT_NA; + } + } + return (int) -value; + } else { + if (length == 0) { + throw new NumberFormatException(); + } + int pos = 0; + while (pos < length) { + char ch = s.charAt(pos++); + if (ch < '0' || ch > '9') { + throw new NumberFormatException(); + } + value = value * 10 + (ch - '0'); + if (value > Integer.MAX_VALUE) { + return INT_NA; + } + } + return (int) value; + } + } + + @TruffleBoundary + public static int parseIntWithNA(String s) { + int length = s.length(); + long value = 0; + if (s.charAt(0) == '-') { + if (length == 1) { + return INT_NA; + } + int pos = 1; + while (pos < length) { + char ch = s.charAt(pos++); + if (ch < '0' || ch > '9') { + return INT_NA; + } + value = value * 10 + (ch - '0'); + if (value > (Integer.MAX_VALUE + 1L)) { + return INT_NA; + } + } + return (int) -value; + } else { + if (length == 0) { + return INT_NA; + } + int pos = 0; + while (pos < length) { + char ch = s.charAt(pos++); + if (ch < '0' || ch > '9') { + return INT_NA; + } + value = value * 10 + (ch - '0'); + if (value > Integer.MAX_VALUE) { + return INT_NA; + } + } + return (int) value; + } + } + @TruffleBoundary public static int string2intNoCheck(String s, boolean exceptionOnFail) { // FIXME use R rules @@ -367,6 +445,7 @@ public class RRuntime { throw new NumberFormatException(); } return result; + } @TruffleBoundary diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides index 2fdbdd1da8..48739f73c6 100644 --- a/mx.fastr/copyrights/overrides +++ b/mx.fastr/copyrights/overrides @@ -34,6 +34,8 @@ com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPort com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortContext.java,gnu_r_murrel_core.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortLocation.java,gnu_r_murrel_core.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortTransform.java,gnu_r_murrel_core.copyright +com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/Col2RGB.java,gnu_r.copyright +com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/color/RGB.java,gnu_r.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/graphics/RGraphics.java,gnu_r_graphics.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/methods/MethodsListDispatch.java,gnu_r.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/methods/Slot.java,gnu_r.copyright -- GitLab