diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java index e58ded864f0..b41b5db3783 100644 --- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java +++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2023, 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 @@ -832,7 +832,7 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence int spaceNeeded = count + Integer.stringSize(i); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - Integer.getChars(i, spaceNeeded, value); + StringLatin1.getChars(i, spaceNeeded, value); } else { StringUTF16.getChars(i, count, spaceNeeded, value); } @@ -857,7 +857,7 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence int spaceNeeded = count + Long.stringSize(l); ensureCapacityInternal(spaceNeeded); if (isLatin1()) { - Long.getChars(l, spaceNeeded, value); + StringLatin1.getChars(l, spaceNeeded, value); } else { StringUTF16.getChars(l, count, spaceNeeded, value); } diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 8c8bcb9226f..2b2377a889b 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -414,33 +414,6 @@ public final class Integer extends Number } while (charPos > 0); } - static final byte[] DigitTens = { - '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 byte[] DigitOnes = { - '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', - } ; - - /** * Returns a {@code String} object representing the * specified integer. The argument is converted to signed decimal @@ -456,7 +429,7 @@ public final class Integer extends Number int size = stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - getChars(i, size, buf); + StringLatin1.getChars(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; @@ -483,53 +456,6 @@ public final class Integer extends Number return Long.toString(toUnsignedLong(i)); } - /** - * 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) { - int q, r; - int charPos = index; - - boolean negative = i < 0; - if (!negative) { - i = -i; - } - - // Generate two digits per iteration - while (i <= -100) { - q = i / 100; - r = (q * 100) - i; - i = q; - buf[--charPos] = DigitOnes[r]; - buf[--charPos] = DigitTens[r]; - } - - // We know there are at most two digits left at this point. - buf[--charPos] = DigitOnes[-i]; - if (i < -9) { - buf[--charPos] = DigitTens[-i]; - } - - if (negative) { - buf[--charPos] = (byte)'-'; - } - return charPos; - } - /** * Returns the string representation size for a given int value. * diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 813837020fb..91583c26be1 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -459,7 +459,7 @@ public final class Long extends Number int size = stringSize(i); if (COMPACT_STRINGS) { byte[] buf = new byte[size]; - getChars(i, size, buf); + StringLatin1.getChars(i, size, buf); return new String(buf, LATIN1); } else { byte[] buf = new byte[size * 2]; @@ -486,65 +486,6 @@ public final class Long extends Number return toUnsignedString(i, 10); } - /** - * 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) { - long q; - int r; - 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; - r = (int)((q * 100) - i); - i = q; - buf[--charPos] = Integer.DigitOnes[r]; - buf[--charPos] = Integer.DigitTens[r]; - } - - // Get 2 digits/iteration using ints - int q2; - int i2 = (int)i; - while (i2 <= -100) { - q2 = i2 / 100; - r = (q2 * 100) - i2; - i2 = q2; - buf[--charPos] = Integer.DigitOnes[r]; - buf[--charPos] = Integer.DigitTens[r]; - } - - // We know there are at most two digits left at this point. - buf[--charPos] = Integer.DigitOnes[-i2]; - if (i2 < -9) { - buf[--charPos] = Integer.DigitTens[-i2]; - } - - if (negative) { - buf[--charPos] = (byte)'-'; - } - return charPos; - } - /** * Returns the string representation size for a given long value. * diff --git a/src/java.base/share/classes/java/lang/StringConcatHelper.java b/src/java.base/share/classes/java/lang/StringConcatHelper.java index 139181af096..eddfc7e6496 100644 --- a/src/java.base/share/classes/java/lang/StringConcatHelper.java +++ b/src/java.base/share/classes/java/lang/StringConcatHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, 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 @@ -250,7 +250,7 @@ final class StringConcatHelper { */ private static long prepend(long indexCoder, byte[] buf, int value) { if (indexCoder < UTF16) { - return Integer.getChars(value, (int)indexCoder, buf); + return StringLatin1.getChars(value, (int)indexCoder, buf); } else { return StringUTF16.getChars(value, (int)indexCoder, buf) | UTF16; } @@ -285,7 +285,7 @@ final class StringConcatHelper { */ private static long prepend(long indexCoder, byte[] buf, long value) { if (indexCoder < UTF16) { - return Long.getChars(value, (int)indexCoder, buf); + return StringLatin1.getChars(value, (int)indexCoder, buf); } else { return StringUTF16.getChars(value, (int)indexCoder, buf) | UTF16; } diff --git a/src/java.base/share/classes/java/lang/StringLatin1.java b/src/java.base/share/classes/java/lang/StringLatin1.java index 7c12e5711b3..f6e2acd2fe6 100644 --- a/src/java.base/share/classes/java/lang/StringLatin1.java +++ b/src/java.base/share/classes/java/lang/StringLatin1.java @@ -33,7 +33,9 @@ import java.util.function.IntConsumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.ByteArrayLittleEndian; import jdk.internal.vm.annotation.IntrinsicCandidate; +import jdk.internal.vm.annotation.Stable; import static java.lang.String.LATIN1; import static java.lang.String.UTF16; @@ -42,6 +44,40 @@ import static java.lang.String.checkOffset; final class StringLatin1 { + /** + * Each element of the array represents the packaging of two ascii characters based on little endian:

+ *

+     *      00 -> '0' | ('0' << 8) -> 0x3030
+     *      01 -> '1' | ('0' << 8) -> 0x3130
+     *      02 -> '2' | ('0' << 8) -> 0x3230
+     *
+     *     ...
+     *
+     *      10 -> '0' | ('1' << 8) -> 0x3031
+     *      11 -> '1' | ('1' << 8) -> 0x3131
+     *      12 -> '2' | ('1' << 8) -> 0x3231
+     *
+     *     ...
+     *
+     *      97 -> '7' | ('9' << 8) -> 0x3739
+     *      98 -> '8' | ('9' << 8) -> 0x3839
+     *      99 -> '9' | ('9' << 8) -> 0x3939
+     * 
+ */ + @Stable + static final short[] PACKED_DIGITS = new short[] { + 0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930, + 0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931, + 0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932, + 0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933, + 0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934, + 0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935, + 0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936, + 0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937, + 0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938, + 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939 + }; + public static char charAt(byte[] value, int index) { checkIndex(index, value.length); return (char)(value[index] & 0xff); @@ -79,6 +115,115 @@ 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, r; + int charPos = index; + + boolean negative = i < 0; + if (!negative) { + i = -i; + } + + // Generate two digits per iteration + while (i <= -100) { + q = i / 100; + r = (q * 100) - i; + i = q; + charPos -= 2; + ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[r]); + } + + // We know there are at most two digits left at this point. + if (i < -9) { + charPos -= 2; + ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[-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; + ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[(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; + ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[(q2 * 100) - i2]); + i2 = q2; + } + + // We know there are at most two digits left at this point. + if (i2 < -9) { + charPos -= 2; + ByteArrayLittleEndian.setShort(buf, charPos, PACKED_DIGITS[-i2]); + } else { + buf[--charPos] = (byte)('0' - i2); + } + + if (negative) { + buf[--charPos] = (byte)'-'; + } + return charPos; + } + 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 73d85863990..db9bdb11864 100644 --- a/src/java.base/share/classes/java/lang/StringUTF16.java +++ b/src/java.base/share/classes/java/lang/StringUTF16.java @@ -33,6 +33,7 @@ import java.util.function.IntConsumer; import java.util.stream.Stream; import java.util.stream.StreamSupport; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.ByteArrayLittleEndian; import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -1519,7 +1520,7 @@ final class StringUTF16 { // been done by the caller. /** - * This is a variant of {@link Integer#getChars(int, int, byte[])}, but for + * This is a variant of {@link StringLatin1#getChars(int, int, byte[])}, but for * UTF-16 coder. * * @param i value to convert @@ -1528,6 +1529,7 @@ final class StringUTF16 { * @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; @@ -1541,14 +1543,23 @@ final class StringUTF16 { q = i / 100; r = (q * 100) - i; i = q; - putChar(buf, --charPos, Integer.DigitOnes[r]); - putChar(buf, --charPos, Integer.DigitTens[r]); + + int packed = (int) StringLatin1.PACKED_DIGITS[r]; + int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF); + + charPos -= 2; + ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated); } // We know there are at most two digits left at this point. - putChar(buf, --charPos, Integer.DigitOnes[-i]); if (i < -9) { - putChar(buf, --charPos, Integer.DigitTens[-i]); + int packed = (int) StringLatin1.PACKED_DIGITS[-i]; + int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF); + + charPos -= 2; + ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated); + } else { + putChar(buf, --charPos, '0' - i); } if (negative) { @@ -1558,7 +1569,7 @@ final class StringUTF16 { } /** - * This is a variant of {@link Long#getChars(long, int, byte[])}, but for + * This is a variant of {@link StringLatin1#getChars(long, int, byte[])}, but for * UTF-16 coder. * * @param i value to convert @@ -1567,8 +1578,8 @@ final class StringUTF16 { * @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 r; int charPos = index; boolean negative = (i < 0); @@ -1579,10 +1590,13 @@ final class StringUTF16 { // Get 2 digits/iteration using longs until quotient fits into an int while (i <= Integer.MIN_VALUE) { q = i / 100; - r = (int)((q * 100) - i); + + int packed = (int) StringLatin1.PACKED_DIGITS[(int)((q * 100) - i)]; + int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF); + + charPos -= 2; + ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated); i = q; - putChar(buf, --charPos, Integer.DigitOnes[r]); - putChar(buf, --charPos, Integer.DigitTens[r]); } // Get 2 digits/iteration using ints @@ -1590,16 +1604,25 @@ final class StringUTF16 { int i2 = (int)i; while (i2 <= -100) { q2 = i2 / 100; - r = (q2 * 100) - i2; + + int packed = (int) StringLatin1.PACKED_DIGITS[(q2 * 100) - i2]; + int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF); + + charPos -= 2; + ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated); i2 = q2; - putChar(buf, --charPos, Integer.DigitOnes[r]); - putChar(buf, --charPos, Integer.DigitTens[r]); } // We know there are at most two digits left at this point. - putChar(buf, --charPos, Integer.DigitOnes[-i2]); if (i2 < -9) { - putChar(buf, --charPos, Integer.DigitTens[-i2]); + charPos -= 2; + + int packed = (int) StringLatin1.PACKED_DIGITS[-i2]; + int inflated = ((packed & 0xFF00) << 8) | (packed & 0xFF); + + ByteArrayLittleEndian.setInt(buf, charPos << 1, inflated); + } else { + putChar(buf, --charPos, '0' - i2); } if (negative) { diff --git a/test/jdk/java/lang/Integer/ToString.java b/test/jdk/java/lang/Integer/ToString.java index 060e0666eb9..0ada8bbf47c 100644 --- a/test/jdk/java/lang/Integer/ToString.java +++ b/test/jdk/java/lang/Integer/ToString.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, 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 @@ -23,13 +23,20 @@ /* * @test - * @bug 8136500 - * @summary Test Integer.toString method + * @bug 8136500 8310929 + * @summary Test Integer.toString method for both compact and non-compact strings + * @run junit/othervm -XX:+CompactStrings ToString + * @run junit/othervm -XX:-CompactStrings ToString */ +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + public class ToString { - public static void main(String[] args) throws Exception { + @Test + public void testBase10() { test("-2147483648", Integer.MIN_VALUE); test("2147483647", Integer.MAX_VALUE); test("0", 0); @@ -77,9 +84,6 @@ public class ToString { } private static void test(String expected, int value) { - String actual = Integer.toString(value); - if (!expected.equals(actual)) { - throw new RuntimeException("Expected " + expected + ", but got " + actual); - } + assertEquals(expected, Integer.toString(value)); } } diff --git a/test/jdk/java/lang/Long/ToString.java b/test/jdk/java/lang/Long/ToString.java index a420e3ed25e..4dd457da2fd 100644 --- a/test/jdk/java/lang/Long/ToString.java +++ b/test/jdk/java/lang/Long/ToString.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, 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 @@ -23,13 +23,20 @@ /* * @test - * @bug 8136500 - * @summary Test Long.toString method + * @bug 8136500 8310929 + * @summary Test Long.toString method for both compact and non-compact strings + * @run junit/othervm -XX:+CompactStrings ToString + * @run junit/othervm -XX:-CompactStrings ToString */ +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + public class ToString { - public static void main(String[] args) throws Exception { + @Test + public void testBase10() { test("-9223372036854775808", Long.MIN_VALUE); test("9223372036854775807", Long.MAX_VALUE); test("0", 0); @@ -77,9 +84,6 @@ public class ToString { } private static void test(String expected, long value) { - String actual = Long.toString(value); - if (!expected.equals(actual)) { - throw new RuntimeException("Expected " + expected + ", but got " + actual); - } + assertEquals(expected, Long.toString(value)); } }