From e1d684c6451ae7875b61a92603c19336ccd734d6 Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 11 Nov 2024 05:06:56 +0000 Subject: [PATCH] 8342650: Move getChars to DecimalDigits Reviewed-by: liach --- .../java/lang/AbstractStringBuilder.java | 8 +- .../share/classes/java/lang/Integer.java | 4 +- .../share/classes/java/lang/Long.java | 4 +- .../classes/java/lang/StringConcatHelper.java | 16 +- .../share/classes/java/lang/StringLatin1.java | 115 ------- .../share/classes/java/lang/StringUTF16.java | 118 ------- .../share/classes/java/lang/System.java | 8 - .../share/classes/java/math/BigDecimal.java | 127 ++----- .../jdk/internal/access/JavaLangAccess.java | 4 - .../jdk/internal/util/DecimalDigits.java | 315 +++++++++++++++++- .../patches/java.base/java/lang/Helper.java | 12 +- .../bench/java/lang/StringBuilders.java | 50 +++ 12 files changed, 405 insertions(+), 376 deletions(-) diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index b40f6274412..fd9dcf60e54 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -830,9 +830,9 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence int spaceNeeded = count + DecimalDigits.stringSize(i); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - StringLatin1.getChars(i, spaceNeeded, value); + DecimalDigits.getCharsLatin1(i, spaceNeeded, value); } else { - StringUTF16.getChars(i, count, spaceNeeded, value); + DecimalDigits.getCharsUTF16(i, spaceNeeded, value); } this.count = spaceNeeded; return this; @@ -855,9 +855,9 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence int spaceNeeded = count + DecimalDigits.stringSize(l); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - StringLatin1.getChars(l, spaceNeeded, value); + DecimalDigits.getCharsLatin1(l, spaceNeeded, value); } else { - StringUTF16.getChars(l, count, spaceNeeded, value); + DecimalDigits.getCharsUTF16(l, spaceNeeded, value); } this.count = spaceNeeded; return this; diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index e666e977c61..5f73d61e5d4 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -432,11 +432,11 @@ public final class Integer extends Number int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - StringLatin1.getChars(i, size, buf); + DecimalDigits.getCharsLatin1(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; - StringUTF16.getChars(i, size, buf); + DecimalDigits.getCharsUTF16(i, size, buf); return new String(buf, UTF16); } } diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 8c083b3ec84..7df9ddfb270 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -462,11 +462,11 @@ public final class Long extends Number int size = DecimalDigits.stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - StringLatin1.getChars(i, size, buf); + DecimalDigits.getCharsLatin1(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; - StringUTF16.getChars(i, size, buf); + DecimalDigits.getCharsUTF16(i, size, buf); return new String(buf, UTF16); } } diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index b635d0dee0f..632fe0f58b5 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -298,12 +298,12 @@ final class StringConcatHelper { static long prepend(long indexCoder, byte[] buf, int value, String prefix) { int index = (int)indexCoder; if (indexCoder < UTF16) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); return index; } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); return index | UTF16; @@ -324,12 +324,12 @@ final class StringConcatHelper { static long prepend(long indexCoder, byte[] buf, long value, String prefix) { int index = (int)indexCoder; if (indexCoder < UTF16) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); return index; } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); return index | UTF16; @@ -682,11 +682,11 @@ final class StringConcatHelper { */ static int prepend(int index, byte coder, byte[] buf, int value, String prefix) { if (coder == String.LATIN1) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); } @@ -706,11 +706,11 @@ final class StringConcatHelper { */ static int prepend(int index, byte coder, byte[] buf, long value, String prefix) { if (coder == String.LATIN1) { - index = StringLatin1.getChars(value, index, buf); + index = DecimalDigits.getCharsLatin1(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.LATIN1); } else { - index = StringUTF16.getChars(value, index, buf); + index = DecimalDigits.getCharsUTF16(value, index, buf); index -= prefix.length(); prefix.getBytes(buf, index, String.UTF16); } diff --git a/src/java.base/share/classes/java/lang/StringLatin1.java b/src/java.base/share/classes/java/lang/StringLatin1.java index c12b8afc21f..abe42c2c7c7 100644 --- a/src/java.base/share/classes/java/lang/StringLatin1.java +++ b/src/java.base/share/classes/java/lang/StringLatin1.java @@ -34,7 +34,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; -import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.IntrinsicCandidate; import static java.lang.String.LATIN1; @@ -86,120 +85,6 @@ final class StringLatin1 { return ret; } - /** - * Places characters representing the integer i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @implNote This method converts positive inputs into negative - * values, to cover the Integer.MIN_VALUE case. Converting otherwise - * (negative to positive) will expose -Integer.MIN_VALUE that overflows - * integer. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(int i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - int q; - int charPos = index; - - boolean negative = i < 0; - if (!negative) { - i = -i; - } - - // Generate two digits per iteration - while (i <= -100) { - q = i / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (q * 100) - i); - i = q; - } - - // We know there are at most two digits left at this point. - if (i < -9) { - charPos -= 2; - writeDigitPair(buf, charPos, -i); - } else { - buf[--charPos] = (byte)('0' - i); - } - - if (negative) { - buf[--charPos] = (byte)'-'; - } - return charPos; - } - - /** - * Places characters representing the long i into the - * character array buf. The characters are placed into - * the buffer backwards starting with the least significant - * digit at the specified index (exclusive), and working - * backwards from there. - * - * @implNote This method converts positive inputs into negative - * values, to cover the Long.MIN_VALUE case. Converting otherwise - * (negative to positive) will expose -Long.MIN_VALUE that overflows - * long. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, Latin1-encoded - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(long i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - writeDigitPair(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - writeDigitPair(buf, charPos, -i2); - } else { - buf[--charPos] = (byte)('0' - i2); - } - - if (negative) { - buf[--charPos] = (byte)'-'; - } - return charPos; - } - - private static void writeDigitPair(byte[] buf, int charPos, int value) { - short pair = DecimalDigits.digitPair(value); - buf[charPos] = (byte)(pair); - buf[charPos + 1] = (byte)(pair >> 8); - } - public static void getChars(byte[] value, int srcBegin, int srcEnd, char[] dst, int dstBegin) { inflate(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); } diff --git a/src/java.base/share/classes/java/lang/StringUTF16.java b/src/java.base/share/classes/java/lang/StringUTF16.java index f04b991827f..a1dcca8ffad 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -35,7 +35,6 @@ import java.util.stream.StreamSupport; import jdk.internal.misc.Unsafe; import jdk.internal.util.ArraysSupport; -import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -1513,20 +1512,6 @@ final class StringUTF16 { return codePointCount(val, beginIndex, endIndex, true /* checked */); } - public static int getChars(int i, int begin, int end, byte[] value) { - checkBoundsBeginEnd(begin, end, value); - int pos = getChars(i, end, value); - assert begin == pos; - return pos; - } - - public static int getChars(long l, int begin, int end, byte[] value) { - checkBoundsBeginEnd(begin, end, value); - int pos = getChars(l, end, value); - assert begin == pos; - return pos; - } - public static boolean contentEquals(byte[] v1, byte[] v2, int len) { checkBoundsOffCount(0, len, v2); for (int i = 0; i < len; i++) { @@ -1662,109 +1647,6 @@ final class StringUTF16 { static final int MAX_LENGTH = Integer.MAX_VALUE >> 1; - // Used by trusted callers. Assumes all necessary bounds checks have - // been done by the caller. - - /** - * This is a variant of {@link StringLatin1#getChars(int, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(int i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - int q, r; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using ints - while (i <= -100) { - q = i / 100; - r = (q * 100) - i; - i = q; - charPos -= 2; - putPair(buf, charPos, r); - } - - // We know there are at most two digits left at this point. - if (i < -9) { - charPos -= 2; - putPair(buf, charPos, -i); - } else { - putChar(buf, --charPos, '0' - i); - } - - if (negative) { - putChar(buf, --charPos, '-'); - } - return charPos; - } - - /** - * This is a variant of {@link StringLatin1#getChars(long, int, byte[])}, but for - * UTF-16 coder. - * - * @param i value to convert - * @param index next index, after the least significant digit - * @param buf target buffer, UTF16-coded. - * @return index of the most significant digit or minus sign, if present - */ - static int getChars(long i, int index, byte[] buf) { - // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. - long q; - int charPos = index; - - boolean negative = (i < 0); - if (!negative) { - i = -i; - } - - // Get 2 digits/iteration using longs until quotient fits into an int - while (i <= Integer.MIN_VALUE) { - q = i / 100; - charPos -= 2; - putPair(buf, charPos, (int)((q * 100) - i)); - i = q; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - charPos -= 2; - putPair(buf, charPos, (q2 * 100) - i2); - i2 = q2; - } - - // We know there are at most two digits left at this point. - if (i2 < -9) { - charPos -= 2; - putPair(buf, charPos, -i2); - } else { - putChar(buf, --charPos, '0' - i2); - } - - if (negative) { - putChar(buf, --charPos, '-'); - } - return charPos; - } - - private static void putPair(byte[] buf, int charPos, int v) { - int packed = (int) DecimalDigits.digitPair(v); - putChar(buf, charPos, packed & 0xFF); - putChar(buf, charPos + 1, packed >> 8); - } - // End of trusted methods. - public static void checkIndex(int off, byte[] val) { String.checkIndex(off, length(val)); } diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 5b04bca4f44..451ed8e6bfc 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -2648,14 +2648,6 @@ public final class System { return str.coder(); } - public int getCharsLatin1(long i, int index, byte[] buf) { - return StringLatin1.getChars(i, index, buf); - } - - public int getCharsUTF16(long i, int index, byte[] buf) { - return StringUTF16.getChars(i, index, buf); - } - public String join(String prefix, String suffix, String delimiter, String[] elements, int size) { return String.join(prefix, suffix, delimiter, elements, size); } diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index abd49aa69bc..b00970963b6 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -35,9 +35,15 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectStreamException; import java.io.StreamCorruptedException; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.DecimalDigits; + /** * Immutable, arbitrary-precision signed decimal numbers. A {@code * BigDecimal} consists of an arbitrary precision integer @@ -328,6 +334,8 @@ import java.util.Objects; * @since 1.1 */ public class BigDecimal extends Number implements Comparable { + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + /* * Let l = log_2(10). * Then, L < l < L + ulp(L) / 2, that is, L = roundTiesToEven(l). @@ -4164,103 +4172,6 @@ public class BigDecimal extends Number implements Comparable { return BigDecimal.valueOf(1, this.scale(), 1); } - // Private class to build a string representation for BigDecimal object. The - // StringBuilder field acts as a buffer to hold the temporary representation - // of BigDecimal. The cmpCharArray holds all the characters for the compact - // representation of BigDecimal (except for '-' sign' if it is negative) if - // its intCompact field is not INFLATED. - static class StringBuilderHelper { - final StringBuilder sb; // Placeholder for BigDecimal string - final char[] cmpCharArray; // character array to place the intCompact - - StringBuilderHelper() { - sb = new StringBuilder(32); - // All non negative longs can be made to fit into 19 character array. - cmpCharArray = new char[19]; - } - - // Accessors. - StringBuilder getStringBuilder() { - sb.setLength(0); - return sb; - } - - char[] getCompactCharArray() { - return cmpCharArray; - } - - /** - * Places characters representing the intCompact in {@code long} into - * cmpCharArray and returns the offset to the array where the - * representation starts. - * - * @param intCompact the number to put into the cmpCharArray. - * @return offset to the array where the representation starts. - * Note: intCompact must be greater or equal to zero. - */ - int putIntCompact(long intCompact) { - assert intCompact >= 0; - - long q; - int r; - // since we start from the least significant digit, charPos points to - // the last character in cmpCharArray. - int charPos = cmpCharArray.length; - - // Get 2 digits/iteration using longs until quotient fits into an int - while (intCompact > Integer.MAX_VALUE) { - q = intCompact / 100; - r = (int)(intCompact - q * 100); - intCompact = q; - cmpCharArray[--charPos] = DIGIT_ONES[r]; - cmpCharArray[--charPos] = DIGIT_TENS[r]; - } - - // Get 2 digits/iteration using ints when i2 >= 100 - int q2; - int i2 = (int)intCompact; - while (i2 >= 100) { - q2 = i2 / 100; - r = i2 - q2 * 100; - i2 = q2; - cmpCharArray[--charPos] = DIGIT_ONES[r]; - cmpCharArray[--charPos] = DIGIT_TENS[r]; - } - - cmpCharArray[--charPos] = DIGIT_ONES[i2]; - if (i2 >= 10) - cmpCharArray[--charPos] = DIGIT_TENS[i2]; - - return charPos; - } - - static final char[] DIGIT_TENS = { - '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', - '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', - '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', - '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', - '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', - '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', - '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', - '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', - '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', - '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', - }; - - static final char[] DIGIT_ONES = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - }; - } - /** * Lay out this {@code BigDecimal} into a {@code char[]} array. * The Java 1.2 equivalent to this was called {@code getValueString}. @@ -4271,6 +4182,8 @@ public class BigDecimal extends Number implements Comparable { * {@code BigDecimal} */ private String layoutChars(boolean sci) { + long intCompact = this.intCompact; + int scale = this.scale; if (scale == 0) // zero scale is trivial return (intCompact != INFLATED) ? Long.toString(intCompact): @@ -4280,18 +4193,24 @@ public class BigDecimal extends Number implements Comparable { // currency fast path int lowInt = (int)intCompact % 100; int highInt = (int)intCompact / 100; - return (Integer.toString(highInt) + '.' + - StringBuilderHelper.DIGIT_TENS[lowInt] + - StringBuilderHelper.DIGIT_ONES[lowInt]) ; + int highIntSize = DecimalDigits.stringSize(highInt); + byte[] buf = new byte[highIntSize + 3]; + DecimalDigits.putPairLatin1(buf, highIntSize + 1, lowInt); + buf[highIntSize] = '.'; + DecimalDigits.getCharsLatin1(highInt, highIntSize, buf); + try { + return JLA.newStringNoRepl(buf, StandardCharsets.ISO_8859_1); + } catch (CharacterCodingException cce) { + throw new AssertionError(cce); + } } - StringBuilderHelper sbHelper = new StringBuilderHelper(); char[] coeff; int offset; // offset is the starting index for coeff array // Get the significand as an absolute value if (intCompact != INFLATED) { - offset = sbHelper.putIntCompact(Math.abs(intCompact)); - coeff = sbHelper.getCompactCharArray(); + coeff = new char[19]; + offset = DecimalDigits.getChars(Math.abs(intCompact), coeff.length, coeff); } else { offset = 0; coeff = intVal.abs().toString().toCharArray(); @@ -4301,7 +4220,7 @@ public class BigDecimal extends Number implements Comparable { // If E-notation is needed, length will be: +1 if negative, +1 // if '.' needed, +2 for "E+", + up to 10 for adjusted exponent. // Otherwise it could have +1 if negative, plus leading "0.00000" - StringBuilder buf = sbHelper.getStringBuilder(); + StringBuilder buf = new StringBuilder(32);; if (signum() < 0) // prefix '-' if negative buf.append('-'); int coeffLen = coeff.length - offset; diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 0436cbb314f..ecfdbd28095 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -487,10 +487,6 @@ public interface JavaLangAccess { */ Object classData(Class c); - int getCharsLatin1(long i, int index, byte[] buf); - - int getCharsUTF16(long i, int index, byte[] buf); - /** * Returns the {@link NativeLibraries} object associated with the provided class loader. * This is used by {@link SymbolLookup#loaderLookup()}. diff --git a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java index 83438e59b82..75e67e3f9cc 100644 --- a/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/DecimalDigits.java @@ -25,14 +25,18 @@ package jdk.internal.util; +import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.Stable; +import static jdk.internal.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; + /** * Digits class for decimal digits. * * @since 21 */ public final class DecimalDigits { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); /** * Each element of the array represents the packaging of two ascii characters based on little endian:

@@ -76,15 +80,6 @@ public final class DecimalDigits { private DecimalDigits() { } - /** - * For values from 0 to 99 return a short encoding a pair of ASCII-encoded digit characters in little-endian - * @param i value to convert - * @return a short encoding a pair of ASCII-encoded digit characters - */ - public static short digitPair(int i) { - return DIGITS[i]; - } - /** * Returns the string representation size for a given int value. * @@ -136,4 +131,306 @@ public final class DecimalDigits { } return 19 + d; } + + /** + * Places characters representing the integer i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Integer.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Integer.MIN_VALUE that overflows + * integer. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsLatin1(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q; + int charPos = index; + + boolean negative = i < 0; + if (!negative) { + i = -i; + } + + // Generate two digits per iteration + while (i <= -100) { + q = i / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (q * 100) - i); + i = q; + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + putPairLatin1(buf, charPos, -i); + } else { + putCharLatin1(buf, --charPos, '0' - i); + } + + if (negative) { + putCharLatin1(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * Places characters representing the long i into the + * character array buf. The characters are placed into + * the buffer backwards starting with the least significant + * digit at the specified index (exclusive), and working + * backwards from there. + * + * @implNote This method converts positive inputs into negative + * values, to cover the Long.MIN_VALUE case. Converting otherwise + * (negative to positive) will expose -Long.MIN_VALUE that overflows + * long. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, Latin1-encoded + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsLatin1(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPairLatin1(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPairLatin1(buf, charPos, -i2); + } else { + putCharLatin1(buf, --charPos, '0' - i2); + } + + if (negative) { + putCharLatin1(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * This is a variant of {@link DecimalDigits#getCharsLatin1(int, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsUTF16(int i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + int q, r; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using ints + while (i <= -100) { + q = i / 100; + r = (q * 100) - i; + i = q; + charPos -= 2; + putPairUTF16(buf, charPos, r); + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + putPairUTF16(buf, charPos, -i); + } else { + putCharUTF16(buf, --charPos, '0' - i); + } + + if (negative) { + putCharUTF16(buf, --charPos, '-'); + } + return charPos; + } + + + /** + * This is a variant of {@link DecimalDigits#getCharsLatin1(long, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getCharsUTF16(long i, int index, byte[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPairUTF16(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPairUTF16(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPairUTF16(buf, charPos, -i2); + } else { + putCharUTF16(buf, --charPos, '0' - i2); + } + + if (negative) { + putCharUTF16(buf, --charPos, '-'); + } + return charPos; + } + + /** + * This is a variant of {@link DecimalDigits#getCharsUTF16(long, int, byte[])}, but for + * UTF-16 coder. + * + * @param i value to convert + * @param index next index, after the least significant digit + * @param buf target buffer, UTF16-coded. + * @return index of the most significant digit or minus sign, if present + */ + public static int getChars(long i, int index, char[] buf) { + // Used by trusted callers. Assumes all necessary bounds checks have been done by the caller. + long q; + int charPos = index; + + boolean negative = (i < 0); + if (!negative) { + i = -i; + } + + // Get 2 digits/iteration using longs until quotient fits into an int + while (i <= Integer.MIN_VALUE) { + q = i / 100; + charPos -= 2; + putPair(buf, charPos, (int)((q * 100) - i)); + i = q; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int)i; + while (i2 <= -100) { + q2 = i2 / 100; + charPos -= 2; + putPair(buf, charPos, (q2 * 100) - i2); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + putPair(buf, charPos, -i2); + } else { + buf[--charPos] = (char) ('0' - i2); + } + + if (negative) { + buf[--charPos] = '-'; + } + return charPos; + } + + /** + * Insert the 2-chars integer into the buf as 2 decimal digit ASCII chars, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPair(char[] buf, int charPos, int v) { + int packed = DIGITS[v]; + buf[charPos ] = (char) (packed & 0xFF); + buf[charPos + 1] = (char) (packed >> 8); + } + + /** + * Insert the 2-bytes integer into the buf as 2 decimal digit ASCII bytes, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPairLatin1(byte[] buf, int charPos, int v) { + int packed = DIGITS[v]; + putCharLatin1(buf, charPos, packed & 0xFF); + putCharLatin1(buf, charPos + 1, packed >> 8); + } + + /** + * Insert the 2-chars integer into the buf as 2 decimal digit UTF16 bytes, + * only least significant 16 bits of {@code v} are used. + * @param buf byte buffer to copy into + * @param charPos insert point + * @param v to convert + */ + public static void putPairUTF16(byte[] buf, int charPos, int v) { + int packed = DIGITS[v]; + putCharUTF16(buf, charPos, packed & 0xFF); + putCharUTF16(buf, charPos + 1, packed >> 8); + } + + private static void putCharLatin1(byte[] buf, int charPos, int c) { + UNSAFE.putByte(buf, ARRAY_BYTE_BASE_OFFSET + charPos, (byte) c); + } + + private static void putCharUTF16(byte[] buf, int charPos, int c) { + UNSAFE.putChar(buf, ARRAY_BYTE_BASE_OFFSET + (charPos << 1), (char) c); + } } diff --git a/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java b/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java index 5ecc01aa2bc..a60354ec2fc 100644 --- a/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java +++ b/test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java @@ -23,6 +23,8 @@ package java.lang; +import jdk.internal.util.DecimalDigits; + /** * A helper class to get access to package-private members */ @@ -117,11 +119,17 @@ public class Helper { } public static int getChars(int i, int begin, int end, byte[] value) { - return StringUTF16.getChars(i, begin, end, value); + StringUTF16.checkBoundsBeginEnd(begin, end, value); + int pos = DecimalDigits.getCharsUTF16(i, end, value); + assert begin == pos; + return pos; } public static int getChars(long l, int begin, int end, byte[] value) { - return StringUTF16.getChars(l, begin, end, value); + StringUTF16.checkBoundsBeginEnd(begin, end, value); + int pos = DecimalDigits.getCharsUTF16(l, end, value); + assert begin == pos; + return pos; } public static boolean contentEquals(byte[] v1, byte[] v2, int len) { diff --git a/test/micro/org/openjdk/bench/java/lang/StringBuilders.java b/test/micro/org/openjdk/bench/java/lang/StringBuilders.java index ed5c0d30db8..e41bd361ff5 100644 --- a/test/micro/org/openjdk/bench/java/lang/StringBuilders.java +++ b/test/micro/org/openjdk/bench/java/lang/StringBuilders.java @@ -54,6 +54,8 @@ public class StringBuilders { private StringBuilder sbLatin2; private StringBuilder sbUtf16; private StringBuilder sbUtf17; + private int[] intsArray; + private long[] longArray; @Setup public void setup() { @@ -69,6 +71,13 @@ public class StringBuilders { sbLatin2 = new StringBuilder("Latin1 string"); sbUtf16 = new StringBuilder("UTF-\uFF11\uFF16 string"); sbUtf17 = new StringBuilder("UTF-\uFF11\uFF16 string"); + int size = 16; + intsArray = new int[size]; + longArray = new long[size]; + for (int i = 0; i < longArray.length; i++) { + intsArray[i] = ((100 * i + i) << 24) + 4543 + i * 4; + longArray[i] = ((100L * i + i) << 32) + 4543 + i * 4L; + } } @Benchmark @@ -224,6 +233,47 @@ public class StringBuilders { return result.toString(); } + @Benchmark + public int appendWithIntLatin1() { + StringBuilder buf = sbLatin1; + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithIntUtf16() { + StringBuilder buf = sbUtf16; + buf.setLength(0); + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithLongLatin1() { + StringBuilder buf = sbLatin1; + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } + + @Benchmark + public int appendWithLongUtf16() { + StringBuilder buf = sbUtf16; + buf.setLength(0); + buf.setLength(0); + for (long l : longArray) { + buf.append(l); + } + return buf.length(); + } @Benchmark public int appendWithBool8Latin1() {