From 74e1a8bfa852a55fb8e6e93e19e2999f4d23f959 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 25 Jan 2023 12:54:27 +0000 Subject: [PATCH] 8300236: Use VarHandle access in Data(Input | Output)Stream classes Reviewed-by: rriggs, alanb --- src/java.base/share/classes/java/io/Bits.java | 120 ----- .../classes/java/io/DataInputStream.java | 47 +- .../classes/java/io/DataOutputStream.java | 40 +- .../classes/java/io/ObjectInputStream.java | 45 +- .../classes/java/io/ObjectOutputStream.java | 46 +- .../classes/java/io/ObjectStreamClass.java | 45 +- .../classes/java/io/RandomAccessFile.java | 21 +- .../classes/jdk/internal/util/ByteArray.java | 424 ++++++++++++++++++ .../io/Bits/java.base/java/io/BitsProxy.java | 88 ---- .../util/ByteArray}/ReadWriteValues.java | 207 +++++++-- .../PrimitiveFieldSerializationBenchmark.java | 150 +++++++ 11 files changed, 869 insertions(+), 364 deletions(-) delete mode 100644 src/java.base/share/classes/java/io/Bits.java create mode 100644 src/java.base/share/classes/jdk/internal/util/ByteArray.java delete mode 100644 test/jdk/java/io/Bits/java.base/java/io/BitsProxy.java rename test/jdk/{java/io/Bits => jdk/internal/util/ByteArray}/ReadWriteValues.java (62%) create mode 100644 test/micro/org/openjdk/bench/java/io/PrimitiveFieldSerializationBenchmark.java diff --git a/src/java.base/share/classes/java/io/Bits.java b/src/java.base/share/classes/java/io/Bits.java deleted file mode 100644 index 4a7524db3eb..00000000000 --- a/src/java.base/share/classes/java/io/Bits.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2001, 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.io; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; -import java.nio.ByteOrder; - -/** - * Utility methods for packing/unpacking primitive values in/out of byte arrays - * using big-endian byte ordering (i.e. "Network Order"). - */ -final class Bits { - private Bits() {} - - private static final VarHandle SHORT = create(short[].class); - private static final VarHandle INT = create(int[].class); - private static final VarHandle LONG = create(long[].class); - - /* - * Methods for unpacking primitive values from byte arrays starting at - * given offsets. - */ - - static boolean getBoolean(byte[] b, int off) { - return b[off] != 0; - } - - static char getChar(byte[] b, int off) { - return (char) (short) SHORT.get(b, off); - } - - static short getShort(byte[] b, int off) { - return (short) SHORT.get(b, off); - } - - static int getInt(byte[] b, int off) { - return (int) INT.get(b, off); - } - - static float getFloat(byte[] b, int off) { - // Using Float.intBitsToFloat collapses NaN values to a single - // "canonical" NaN value - return Float.intBitsToFloat((int) INT.get(b, off)); - } - - static long getLong(byte[] b, int off) { - return (long) LONG.get(b, off); - } - - static double getDouble(byte[] b, int off) { - // Using Double.longBitsToDouble collapses NaN values to a single - // "canonical" NaN value - return Double.longBitsToDouble((long) LONG.get(b, off)); - } - - /* - * Methods for packing primitive values into byte arrays starting at given - * offsets. - */ - - static void putBoolean(byte[] b, int off, boolean val) { - b[off] = (byte) (val ? 1 : 0); - } - - static void putChar(byte[] b, int off, char val) { - SHORT.set(b, off, (short) val); - } - - static void putShort(byte[] b, int off, short val) { - SHORT.set(b, off, val); - } - - static void putInt(byte[] b, int off, int val) { - INT.set(b, off, val); - } - - static void putFloat(byte[] b, int off, float val) { - // Using Float.floatToIntBits collapses NaN values to a single - // "canonical" NaN value - INT.set(b, off, Float.floatToIntBits(val)); - } - - static void putLong(byte[] b, int off, long val) { - LONG.set(b, off, val); - } - - static void putDouble(byte[] b, int off, double val) { - // Using Double.doubleToLongBits collapses NaN values to a single - // "canonical" NaN value - LONG.set(b, off, Double.doubleToLongBits(val)); - } - - private static VarHandle create(Class viewArrayClass) { - return MethodHandles.byteArrayViewVarHandle(viewArrayClass, ByteOrder.BIG_ENDIAN); - } -} diff --git a/src/java.base/share/classes/java/io/DataInputStream.java b/src/java.base/share/classes/java/io/DataInputStream.java index 971910c9a3d..2e7484fd974 100644 --- a/src/java.base/share/classes/java/io/DataInputStream.java +++ b/src/java.base/share/classes/java/io/DataInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -25,6 +25,8 @@ package java.io; +import jdk.internal.util.ByteArray; + import java.util.Objects; /** @@ -54,6 +56,8 @@ public class DataInputStream extends FilterInputStream implements DataInput { super(in); } + private final byte[] readBuffer = new byte[8]; + /** * working arrays initialized on demand by readUTF */ @@ -309,7 +313,8 @@ public class DataInputStream extends FilterInputStream implements DataInput { * @see java.io.FilterInputStream#in */ public final short readShort() throws IOException { - return (short) readUnsignedShort(); + readFully(readBuffer, 0, 2); + return ByteArray.getShort(readBuffer, 0); } /** @@ -330,12 +335,8 @@ public class DataInputStream extends FilterInputStream implements DataInput { * @see java.io.FilterInputStream#in */ public final int readUnsignedShort() throws IOException { - InputStream in = this.in; - int ch1 = in.read(); - int ch2 = in.read(); - if ((ch1 | ch2) < 0) - throw new EOFException(); - return (ch1 << 8) + (ch2 << 0); + readFully(readBuffer, 0, 2); + return ByteArray.getUnsignedShort(readBuffer, 0); } /** @@ -356,7 +357,8 @@ public class DataInputStream extends FilterInputStream implements DataInput { * @see java.io.FilterInputStream#in */ public final char readChar() throws IOException { - return (char) readUnsignedShort(); + readFully(readBuffer, 0, 2); + return ByteArray.getChar(readBuffer, 0); } /** @@ -377,18 +379,10 @@ public class DataInputStream extends FilterInputStream implements DataInput { * @see java.io.FilterInputStream#in */ public final int readInt() throws IOException { - InputStream in = this.in; - int ch1 = in.read(); - int ch2 = in.read(); - int ch3 = in.read(); - int ch4 = in.read(); - if ((ch1 | ch2 | ch3 | ch4) < 0) - throw new EOFException(); - return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + readFully(readBuffer, 0, 4); + return ByteArray.getInt(readBuffer, 0); } - private final byte[] readBuffer = new byte[8]; - /** * See the general contract of the {@code readLong} * method of {@code DataInput}. @@ -408,14 +402,7 @@ public class DataInputStream extends FilterInputStream implements DataInput { */ public final long readLong() throws IOException { readFully(readBuffer, 0, 8); - return (((long)readBuffer[0] << 56) + - ((long)(readBuffer[1] & 255) << 48) + - ((long)(readBuffer[2] & 255) << 40) + - ((long)(readBuffer[3] & 255) << 32) + - ((long)(readBuffer[4] & 255) << 24) + - ((readBuffer[5] & 255) << 16) + - ((readBuffer[6] & 255) << 8) + - ((readBuffer[7] & 255) << 0)); + return ByteArray.getLong(readBuffer, 0); } /** @@ -437,7 +424,8 @@ public class DataInputStream extends FilterInputStream implements DataInput { * @see java.lang.Float#intBitsToFloat(int) */ public final float readFloat() throws IOException { - return Float.intBitsToFloat(readInt()); + readFully(readBuffer, 0, 4); + return ByteArray.getFloat(readBuffer, 0); } /** @@ -459,7 +447,8 @@ public class DataInputStream extends FilterInputStream implements DataInput { * @see java.lang.Double#longBitsToDouble(long) */ public final double readDouble() throws IOException { - return Double.longBitsToDouble(readLong()); + readFully(readBuffer, 0, 8); + return ByteArray.getDouble(readBuffer, 0); } private char[] lineBuffer; diff --git a/src/java.base/share/classes/java/io/DataOutputStream.java b/src/java.base/share/classes/java/io/DataOutputStream.java index af215607c56..8ac9c2d3144 100644 --- a/src/java.base/share/classes/java/io/DataOutputStream.java +++ b/src/java.base/share/classes/java/io/DataOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -25,6 +25,8 @@ package java.io; +import jdk.internal.util.ByteArray; + /** * A data output stream lets an application write primitive Java data * types to an output stream in a portable way. An application can @@ -170,8 +172,7 @@ public class DataOutputStream extends FilterOutputStream implements DataOutput { * @see java.io.FilterOutputStream#out */ public final void writeShort(int v) throws IOException { - writeBuffer[0] = (byte)(v >>> 8); - writeBuffer[1] = (byte)(v >>> 0); + ByteArray.setUnsignedShort(writeBuffer, 0, v); out.write(writeBuffer, 0, 2); incCount(2); } @@ -186,8 +187,7 @@ public class DataOutputStream extends FilterOutputStream implements DataOutput { * @see java.io.FilterOutputStream#out */ public final void writeChar(int v) throws IOException { - writeBuffer[0] = (byte)(v >>> 8); - writeBuffer[1] = (byte)(v >>> 0); + ByteArray.setUnsignedShort(writeBuffer, 0, v); out.write(writeBuffer, 0, 2); incCount(2); } @@ -202,10 +202,7 @@ public class DataOutputStream extends FilterOutputStream implements DataOutput { * @see java.io.FilterOutputStream#out */ public final void writeInt(int v) throws IOException { - writeBuffer[0] = (byte)(v >>> 24); - writeBuffer[1] = (byte)(v >>> 16); - writeBuffer[2] = (byte)(v >>> 8); - writeBuffer[3] = (byte)(v >>> 0); + ByteArray.setInt(writeBuffer, 0, v); out.write(writeBuffer, 0, 4); incCount(4); } @@ -220,14 +217,7 @@ public class DataOutputStream extends FilterOutputStream implements DataOutput { * @see java.io.FilterOutputStream#out */ public final void writeLong(long v) throws IOException { - writeBuffer[0] = (byte)(v >>> 56); - writeBuffer[1] = (byte)(v >>> 48); - writeBuffer[2] = (byte)(v >>> 40); - writeBuffer[3] = (byte)(v >>> 32); - writeBuffer[4] = (byte)(v >>> 24); - writeBuffer[5] = (byte)(v >>> 16); - writeBuffer[6] = (byte)(v >>> 8); - writeBuffer[7] = (byte)(v >>> 0); + ByteArray.setLong(writeBuffer, 0, v); out.write(writeBuffer, 0, 8); incCount(8); } @@ -246,7 +236,9 @@ public class DataOutputStream extends FilterOutputStream implements DataOutput { * @see java.lang.Float#floatToIntBits(float) */ public final void writeFloat(float v) throws IOException { - writeInt(Float.floatToIntBits(v)); + ByteArray.setFloat(writeBuffer, 0, v); + out.write(writeBuffer, 0, 4); + incCount(4); } /** @@ -263,7 +255,9 @@ public class DataOutputStream extends FilterOutputStream implements DataOutput { * @see java.lang.Double#doubleToLongBits(double) */ public final void writeDouble(double v) throws IOException { - writeLong(Double.doubleToLongBits(v)); + ByteArray.setDouble(writeBuffer, 0, v); + out.write(writeBuffer, 0, 8); + incCount(8); } /** @@ -301,8 +295,7 @@ public class DataOutputStream extends FilterOutputStream implements DataOutput { int len = s.length(); for (int i = 0 ; i < len ; i++) { int v = s.charAt(i); - writeBuffer[0] = (byte)(v >>> 8); - writeBuffer[1] = (byte)(v >>> 0); + ByteArray.setUnsignedShort(writeBuffer, 0, v); out.write(writeBuffer, 0, 2); } incCount(len * 2); @@ -379,9 +372,8 @@ public class DataOutputStream extends FilterOutputStream implements DataOutput { } int count = 0; - bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF); - bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF); - + ByteArray.setUnsignedShort(bytearr, count, utflen); + count += 2; int i = 0; for (i = 0; i < strlen; i++) { // optimized for initial run of ASCII int c = str.charAt(i); diff --git a/src/java.base/share/classes/java/io/ObjectInputStream.java b/src/java.base/share/classes/java/io/ObjectInputStream.java index 7c02fba8103..ec0d524f008 100644 --- a/src/java.base/share/classes/java/io/ObjectInputStream.java +++ b/src/java.base/share/classes/java/io/ObjectInputStream.java @@ -45,6 +45,7 @@ import java.util.Objects; import jdk.internal.access.SharedSecrets; import jdk.internal.event.DeserializationEvent; import jdk.internal.misc.Unsafe; +import jdk.internal.util.ByteArray; import sun.reflect.misc.ReflectUtil; import sun.security.action.GetBooleanAction; import sun.security.action.GetIntegerAction; @@ -2631,7 +2632,7 @@ public class ObjectInputStream public boolean get(String name, boolean val) { int off = getFieldOffset(name, Boolean.TYPE); - return (off >= 0) ? Bits.getBoolean(primValues, off) : val; + return (off >= 0) ? ByteArray.getBoolean(primValues, off) : val; } public byte get(String name, byte val) { @@ -2641,32 +2642,32 @@ public class ObjectInputStream public char get(String name, char val) { int off = getFieldOffset(name, Character.TYPE); - return (off >= 0) ? Bits.getChar(primValues, off) : val; + return (off >= 0) ? ByteArray.getChar(primValues, off) : val; } public short get(String name, short val) { int off = getFieldOffset(name, Short.TYPE); - return (off >= 0) ? Bits.getShort(primValues, off) : val; + return (off >= 0) ? ByteArray.getShort(primValues, off) : val; } public int get(String name, int val) { int off = getFieldOffset(name, Integer.TYPE); - return (off >= 0) ? Bits.getInt(primValues, off) : val; + return (off >= 0) ? ByteArray.getInt(primValues, off) : val; } public float get(String name, float val) { int off = getFieldOffset(name, Float.TYPE); - return (off >= 0) ? Bits.getFloat(primValues, off) : val; + return (off >= 0) ? ByteArray.getFloat(primValues, off) : val; } public long get(String name, long val) { int off = getFieldOffset(name, Long.TYPE); - return (off >= 0) ? Bits.getLong(primValues, off) : val; + return (off >= 0) ? ByteArray.getLong(primValues, off) : val; } public double get(String name, double val) { int off = getFieldOffset(name, Double.TYPE); - return (off >= 0) ? Bits.getDouble(primValues, off) : val; + return (off >= 0) ? ByteArray.getDouble(primValues, off) : val; } public Object get(String name, Object val) throws ClassNotFoundException { @@ -3114,7 +3115,7 @@ public class ObjectInputStream return HEADER_BLOCKED; } in.readFully(hbuf, 0, 5); - int len = Bits.getInt(hbuf, 1); + int len = ByteArray.getInt(hbuf, 1); if (len < 0) { throw new StreamCorruptedException( "illegal block data header length: " + @@ -3413,7 +3414,7 @@ public class ObjectInputStream } else if (end - pos < 2) { return din.readChar(); } - char v = Bits.getChar(buf, pos); + char v = ByteArray.getChar(buf, pos); pos += 2; return v; } @@ -3425,7 +3426,7 @@ public class ObjectInputStream } else if (end - pos < 2) { return din.readShort(); } - short v = Bits.getShort(buf, pos); + short v = ByteArray.getShort(buf, pos); pos += 2; return v; } @@ -3437,7 +3438,7 @@ public class ObjectInputStream } else if (end - pos < 2) { return din.readUnsignedShort(); } - int v = Bits.getShort(buf, pos) & 0xFFFF; + int v = ByteArray.getShort(buf, pos) & 0xFFFF; pos += 2; return v; } @@ -3449,7 +3450,7 @@ public class ObjectInputStream } else if (end - pos < 4) { return din.readInt(); } - int v = Bits.getInt(buf, pos); + int v = ByteArray.getInt(buf, pos); pos += 4; return v; } @@ -3461,7 +3462,7 @@ public class ObjectInputStream } else if (end - pos < 4) { return din.readFloat(); } - float v = Bits.getFloat(buf, pos); + float v = ByteArray.getFloat(buf, pos); pos += 4; return v; } @@ -3473,7 +3474,7 @@ public class ObjectInputStream } else if (end - pos < 8) { return din.readLong(); } - long v = Bits.getLong(buf, pos); + long v = ByteArray.getLong(buf, pos); pos += 8; return v; } @@ -3485,7 +3486,7 @@ public class ObjectInputStream } else if (end - pos < 8) { return din.readDouble(); } - double v = Bits.getDouble(buf, pos); + double v = ByteArray.getDouble(buf, pos); pos += 8; return v; } @@ -3523,7 +3524,7 @@ public class ObjectInputStream } while (off < stop) { - v[off++] = Bits.getBoolean(buf, pos++); + v[off++] = ByteArray.getBoolean(buf, pos++); } } } @@ -3544,7 +3545,7 @@ public class ObjectInputStream } while (off < stop) { - v[off++] = Bits.getChar(buf, pos); + v[off++] = ByteArray.getChar(buf, pos); pos += 2; } } @@ -3566,7 +3567,7 @@ public class ObjectInputStream } while (off < stop) { - v[off++] = Bits.getShort(buf, pos); + v[off++] = ByteArray.getShort(buf, pos); pos += 2; } } @@ -3588,7 +3589,7 @@ public class ObjectInputStream } while (off < stop) { - v[off++] = Bits.getInt(buf, pos); + v[off++] = ByteArray.getInt(buf, pos); pos += 4; } } @@ -3610,7 +3611,7 @@ public class ObjectInputStream } while (off < stop) { - v[off++] = Bits.getFloat(buf, pos); + v[off++] = ByteArray.getFloat(buf, pos); pos += 4; } } @@ -3632,7 +3633,7 @@ public class ObjectInputStream } while (off < stop) { - v[off++] = Bits.getLong(buf, pos); + v[off++] = ByteArray.getLong(buf, pos); pos += 8; } } @@ -3654,7 +3655,7 @@ public class ObjectInputStream } while (off < stop) { - v[off++] = Bits.getDouble(buf, pos); + v[off++] = ByteArray.getDouble(buf, pos); pos += 8; } } diff --git a/src/java.base/share/classes/java/io/ObjectOutputStream.java b/src/java.base/share/classes/java/io/ObjectOutputStream.java index d3f3e05a4ac..3e4e03d4240 100644 --- a/src/java.base/share/classes/java/io/ObjectOutputStream.java +++ b/src/java.base/share/classes/java/io/ObjectOutputStream.java @@ -32,6 +32,8 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.StringJoiner; + +import jdk.internal.util.ByteArray; import sun.reflect.misc.ReflectUtil; /** @@ -1638,7 +1640,7 @@ public class ObjectOutputStream } public void put(String name, boolean val) { - Bits.putBoolean(primVals, getFieldOffset(name, Boolean.TYPE), val); + ByteArray.setBoolean(primVals, getFieldOffset(name, Boolean.TYPE), val); } public void put(String name, byte val) { @@ -1646,27 +1648,27 @@ public class ObjectOutputStream } public void put(String name, char val) { - Bits.putChar(primVals, getFieldOffset(name, Character.TYPE), val); + ByteArray.setChar(primVals, getFieldOffset(name, Character.TYPE), val); } public void put(String name, short val) { - Bits.putShort(primVals, getFieldOffset(name, Short.TYPE), val); + ByteArray.setShort(primVals, getFieldOffset(name, Short.TYPE), val); } public void put(String name, int val) { - Bits.putInt(primVals, getFieldOffset(name, Integer.TYPE), val); + ByteArray.setInt(primVals, getFieldOffset(name, Integer.TYPE), val); } public void put(String name, float val) { - Bits.putFloat(primVals, getFieldOffset(name, Float.TYPE), val); + ByteArray.setFloat(primVals, getFieldOffset(name, Float.TYPE), val); } public void put(String name, long val) { - Bits.putLong(primVals, getFieldOffset(name, Long.TYPE), val); + ByteArray.setLong(primVals, getFieldOffset(name, Long.TYPE), val); } public void put(String name, double val) { - Bits.putDouble(primVals, getFieldOffset(name, Double.TYPE), val); + ByteArray.setDouble(primVals, getFieldOffset(name, Double.TYPE), val); } public void put(String name, Object val) { @@ -1908,7 +1910,7 @@ public class ObjectOutputStream out.write(hbuf, 0, 2); } else { hbuf[0] = TC_BLOCKDATALONG; - Bits.putInt(hbuf, 1, len); + ByteArray.setInt(hbuf, 1, len); out.write(hbuf, 0, 5); } } @@ -1925,7 +1927,7 @@ public class ObjectOutputStream if (pos >= MAX_BLOCK_SIZE) { drain(); } - Bits.putBoolean(buf, pos++, v); + ByteArray.setBoolean(buf, pos++, v); } public void writeByte(int v) throws IOException { @@ -1937,7 +1939,7 @@ public class ObjectOutputStream public void writeChar(int v) throws IOException { if (pos + 2 <= MAX_BLOCK_SIZE) { - Bits.putChar(buf, pos, (char) v); + ByteArray.setChar(buf, pos, (char) v); pos += 2; } else { dout.writeChar(v); @@ -1946,7 +1948,7 @@ public class ObjectOutputStream public void writeShort(int v) throws IOException { if (pos + 2 <= MAX_BLOCK_SIZE) { - Bits.putShort(buf, pos, (short) v); + ByteArray.setShort(buf, pos, (short) v); pos += 2; } else { dout.writeShort(v); @@ -1955,7 +1957,7 @@ public class ObjectOutputStream public void writeInt(int v) throws IOException { if (pos + 4 <= MAX_BLOCK_SIZE) { - Bits.putInt(buf, pos, v); + ByteArray.setInt(buf, pos, v); pos += 4; } else { dout.writeInt(v); @@ -1964,7 +1966,7 @@ public class ObjectOutputStream public void writeFloat(float v) throws IOException { if (pos + 4 <= MAX_BLOCK_SIZE) { - Bits.putFloat(buf, pos, v); + ByteArray.setFloat(buf, pos, v); pos += 4; } else { dout.writeFloat(v); @@ -1973,7 +1975,7 @@ public class ObjectOutputStream public void writeLong(long v) throws IOException { if (pos + 8 <= MAX_BLOCK_SIZE) { - Bits.putLong(buf, pos, v); + ByteArray.setLong(buf, pos, v); pos += 8; } else { dout.writeLong(v); @@ -1982,7 +1984,7 @@ public class ObjectOutputStream public void writeDouble(double v) throws IOException { if (pos + 8 <= MAX_BLOCK_SIZE) { - Bits.putDouble(buf, pos, v); + ByteArray.setDouble(buf, pos, v); pos += 8; } else { dout.writeDouble(v); @@ -2042,7 +2044,7 @@ public class ObjectOutputStream } int stop = Math.min(endoff, off + (MAX_BLOCK_SIZE - pos)); while (off < stop) { - Bits.putBoolean(buf, pos++, v[off++]); + ByteArray.setBoolean(buf, pos++, v[off++]); } } } @@ -2055,7 +2057,7 @@ public class ObjectOutputStream int avail = (MAX_BLOCK_SIZE - pos) >> 1; int stop = Math.min(endoff, off + avail); while (off < stop) { - Bits.putChar(buf, pos, v[off++]); + ByteArray.setChar(buf, pos, v[off++]); pos += 2; } } else { @@ -2072,7 +2074,7 @@ public class ObjectOutputStream int avail = (MAX_BLOCK_SIZE - pos) >> 1; int stop = Math.min(endoff, off + avail); while (off < stop) { - Bits.putShort(buf, pos, v[off++]); + ByteArray.setShort(buf, pos, v[off++]); pos += 2; } } else { @@ -2089,7 +2091,7 @@ public class ObjectOutputStream int avail = (MAX_BLOCK_SIZE - pos) >> 2; int stop = Math.min(endoff, off + avail); while (off < stop) { - Bits.putInt(buf, pos, v[off++]); + ByteArray.setInt(buf, pos, v[off++]); pos += 4; } } else { @@ -2106,7 +2108,7 @@ public class ObjectOutputStream int avail = (MAX_BLOCK_SIZE - pos) >> 2; int stop = Math.min(endoff, off + avail); while (off < stop) { - Bits.putFloat(buf, pos, v[off++]); + ByteArray.setFloat(buf, pos, v[off++]); pos += 4; } } else { @@ -2123,7 +2125,7 @@ public class ObjectOutputStream int avail = (MAX_BLOCK_SIZE - pos) >> 3; int stop = Math.min(endoff, off + avail); while (off < stop) { - Bits.putLong(buf, pos, v[off++]); + ByteArray.setLong(buf, pos, v[off++]); pos += 8; } } else { @@ -2140,7 +2142,7 @@ public class ObjectOutputStream int avail = (MAX_BLOCK_SIZE - pos) >> 3; int stop = Math.min(endoff, off + avail); while (off < stop) { - Bits.putDouble(buf, pos, v[off++]); + ByteArray.setDouble(buf, pos, v[off++]); pos += 8; } } else { diff --git a/src/java.base/share/classes/java/io/ObjectStreamClass.java b/src/java.base/share/classes/java/io/ObjectStreamClass.java index c01430912eb..484e3b7b973 100644 --- a/src/java.base/share/classes/java/io/ObjectStreamClass.java +++ b/src/java.base/share/classes/java/io/ObjectStreamClass.java @@ -61,6 +61,7 @@ import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; import jdk.internal.access.SharedSecrets; import jdk.internal.access.JavaSecurityAccess; +import jdk.internal.util.ByteArray; import sun.reflect.misc.ReflectUtil; /** @@ -1986,14 +1987,14 @@ public final class ObjectStreamClass implements Serializable { long key = readKeys[i]; int off = offsets[i]; switch (typeCodes[i]) { - case 'Z' -> Bits.putBoolean(buf, off, UNSAFE.getBoolean(obj, key)); + case 'Z' -> ByteArray.setBoolean(buf, off, UNSAFE.getBoolean(obj, key)); case 'B' -> buf[off] = UNSAFE.getByte(obj, key); - case 'C' -> Bits.putChar(buf, off, UNSAFE.getChar(obj, key)); - case 'S' -> Bits.putShort(buf, off, UNSAFE.getShort(obj, key)); - case 'I' -> Bits.putInt(buf, off, UNSAFE.getInt(obj, key)); - case 'F' -> Bits.putFloat(buf, off, UNSAFE.getFloat(obj, key)); - case 'J' -> Bits.putLong(buf, off, UNSAFE.getLong(obj, key)); - case 'D' -> Bits.putDouble(buf, off, UNSAFE.getDouble(obj, key)); + case 'C' -> ByteArray.setChar(buf, off, UNSAFE.getChar(obj, key)); + case 'S' -> ByteArray.setShort(buf, off, UNSAFE.getShort(obj, key)); + case 'I' -> ByteArray.setInt(buf, off, UNSAFE.getInt(obj, key)); + case 'F' -> ByteArray.setFloat(buf, off, UNSAFE.getFloat(obj, key)); + case 'J' -> ByteArray.setLong(buf, off, UNSAFE.getLong(obj, key)); + case 'D' -> ByteArray.setDouble(buf, off, UNSAFE.getDouble(obj, key)); default -> throw new InternalError(); } } @@ -2015,14 +2016,14 @@ public final class ObjectStreamClass implements Serializable { } int off = offsets[i]; switch (typeCodes[i]) { - case 'Z' -> UNSAFE.putBoolean(obj, key, Bits.getBoolean(buf, off)); + case 'Z' -> UNSAFE.putBoolean(obj, key, ByteArray.getBoolean(buf, off)); case 'B' -> UNSAFE.putByte(obj, key, buf[off]); - case 'C' -> UNSAFE.putChar(obj, key, Bits.getChar(buf, off)); - case 'S' -> UNSAFE.putShort(obj, key, Bits.getShort(buf, off)); - case 'I' -> UNSAFE.putInt(obj, key, Bits.getInt(buf, off)); - case 'F' -> UNSAFE.putFloat(obj, key, Bits.getFloat(buf, off)); - case 'J' -> UNSAFE.putLong(obj, key, Bits.getLong(buf, off)); - case 'D' -> UNSAFE.putDouble(obj, key, Bits.getDouble(buf, off)); + case 'C' -> UNSAFE.putChar(obj, key, ByteArray.getChar(buf, off)); + case 'S' -> UNSAFE.putShort(obj, key, ByteArray.getShort(buf, off)); + case 'I' -> UNSAFE.putInt(obj, key, ByteArray.getInt(buf, off)); + case 'F' -> UNSAFE.putFloat(obj, key, ByteArray.getFloat(buf, off)); + case 'J' -> UNSAFE.putLong(obj, key, ByteArray.getLong(buf, off)); + case 'D' -> UNSAFE.putDouble(obj, key, ByteArray.getDouble(buf, off)); default -> throw new InternalError(); } } @@ -2473,16 +2474,16 @@ public final class ObjectStreamClass implements Serializable { try { PRIM_VALUE_EXTRACTORS = Map.of( byte.class, MethodHandles.arrayElementGetter(byte[].class), - short.class, lkp.findStatic(Bits.class, "getShort", MethodType.methodType(short.class, byte[].class, int.class)), - int.class, lkp.findStatic(Bits.class, "getInt", MethodType.methodType(int.class, byte[].class, int.class)), - long.class, lkp.findStatic(Bits.class, "getLong", MethodType.methodType(long.class, byte[].class, int.class)), - float.class, lkp.findStatic(Bits.class, "getFloat", MethodType.methodType(float.class, byte[].class, int.class)), - double.class, lkp.findStatic(Bits.class, "getDouble", MethodType.methodType(double.class, byte[].class, int.class)), - char.class, lkp.findStatic(Bits.class, "getChar", MethodType.methodType(char.class, byte[].class, int.class)), - boolean.class, lkp.findStatic(Bits.class, "getBoolean", MethodType.methodType(boolean.class, byte[].class, int.class)) + short.class, lkp.findStatic(ByteArray.class, "getShort", MethodType.methodType(short.class, byte[].class, int.class)), + int.class, lkp.findStatic(ByteArray.class, "getInt", MethodType.methodType(int.class, byte[].class, int.class)), + long.class, lkp.findStatic(ByteArray.class, "getLong", MethodType.methodType(long.class, byte[].class, int.class)), + float.class, lkp.findStatic(ByteArray.class, "getFloat", MethodType.methodType(float.class, byte[].class, int.class)), + double.class, lkp.findStatic(ByteArray.class, "getDouble", MethodType.methodType(double.class, byte[].class, int.class)), + char.class, lkp.findStatic(ByteArray.class, "getChar", MethodType.methodType(char.class, byte[].class, int.class)), + boolean.class, lkp.findStatic(ByteArray.class, "getBoolean", MethodType.methodType(boolean.class, byte[].class, int.class)) ); } catch (NoSuchMethodException | IllegalAccessException e) { - throw new InternalError("Can't lookup Bits.getXXX", e); + throw new InternalError("Can't lookup " + ByteArray.class.getName() + ".getXXX", e); } } } diff --git a/src/java.base/share/classes/java/io/RandomAccessFile.java b/src/java.base/share/classes/java/io/RandomAccessFile.java index 7cbc06fd3b4..71f9956b15b 100644 --- a/src/java.base/share/classes/java/io/RandomAccessFile.java +++ b/src/java.base/share/classes/java/io/RandomAccessFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 @@ -30,6 +30,7 @@ import java.nio.channels.FileChannel; import jdk.internal.access.JavaIORandomAccessFileAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Blocker; +import jdk.internal.util.ByteArray; import sun.nio.ch.FileChannelImpl; @@ -884,7 +885,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { */ public final int readInt() throws IOException { readFully(buffer, 0, Integer.BYTES); - return Bits.getInt(buffer, 0); + return ByteArray.getInt(buffer, 0); } /** @@ -917,7 +918,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { */ public final long readLong() throws IOException { readFully(buffer, 0, Long.BYTES); - return Bits.getLong(buffer, 0); + return ByteArray.getLong(buffer, 0); } /** @@ -941,7 +942,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { */ public final float readFloat() throws IOException { readFully(buffer, 0, Float.BYTES); - return Bits.getFloat(buffer, 0); + return ByteArray.getFloat(buffer, 0); } /** @@ -965,7 +966,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { */ public final double readDouble() throws IOException { readFully(buffer, 0, Double.BYTES); - return Bits.getDouble(buffer, 0); + return ByteArray.getDouble(buffer, 0); } /** @@ -1104,7 +1105,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @throws IOException if an I/O error occurs. */ public final void writeInt(int v) throws IOException { - Bits.putInt(buffer, 0, v); + ByteArray.setInt(buffer, 0, v); write(buffer, 0, Integer.BYTES); //written += 4; } @@ -1117,7 +1118,7 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @throws IOException if an I/O error occurs. */ public final void writeLong(long v) throws IOException { - Bits.putLong(buffer, 0, v); + ByteArray.setLong(buffer, 0, v); write(buffer, 0, Long.BYTES); } @@ -1133,7 +1134,8 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @see java.lang.Float#floatToIntBits(float) */ public final void writeFloat(float v) throws IOException { - writeInt(Float.floatToIntBits(v)); + ByteArray.setFloat(buffer, 0, v); + write(buffer, 0, Float.BYTES); } /** @@ -1148,7 +1150,8 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable { * @see java.lang.Double#doubleToLongBits(double) */ public final void writeDouble(double v) throws IOException { - writeLong(Double.doubleToLongBits(v)); + ByteArray.setDouble(buffer, 0, v); + write(buffer, 0, Double.BYTES); } /** diff --git a/src/java.base/share/classes/jdk/internal/util/ByteArray.java b/src/java.base/share/classes/jdk/internal/util/ByteArray.java new file mode 100644 index 00000000000..94395df2c34 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/util/ByteArray.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.util; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + +/** + * Utility methods for packing/unpacking primitive values in/out of byte arrays + * using {@linkplain ByteOrder#BIG_ENDIAN big endian order} (aka. "network order"). + *

+ * All methods in this class will throw an {@linkplain NullPointerException} if {@code null} is + * passed in as a method parameter for a byte array. + */ +public final class ByteArray { + + private ByteArray() { + } + + private static final VarHandle SHORT = create(short[].class); + private static final VarHandle CHAR = create(char[].class); + private static final VarHandle INT = create(int[].class); + private static final VarHandle FLOAT = create(float[].class); + private static final VarHandle LONG = create(long[].class); + private static final VarHandle DOUBLE = create(double[].class); + + /* + * Methods for unpacking primitive values from byte arrays starting at + * a given offset. + */ + + /** + * {@return a {@code boolean} from the provided {@code array} at the given {@code offset}}. + * + * @param array to read a value from. + * @param offset where extraction in the array should begin + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 1] + * @see #setBoolean(byte[], int, boolean) + */ + public static boolean getBoolean(byte[] array, int offset) { + return array[offset] != 0; + } + + /** + * {@return a {@code char} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #setChar(byte[], int, char) + */ + public static char getChar(byte[] array, int offset) { + return (char) CHAR.get(array, offset); + } + + /** + * {@return a {@code short} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @return a {@code short} from the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #setShort(byte[], int, short) + */ + public static short getShort(byte[] array, int offset) { + return (short) SHORT.get(array, offset); + } + + /** + * {@return an {@code unsigned short} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @return an {@code int} representing an unsigned short from the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #setUnsignedShort(byte[], int, int) + */ + public static int getUnsignedShort(byte[] array, int offset) { + return Short.toUnsignedInt((short) SHORT.get(array, offset)); + } + + /** + * {@return an {@code int} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 4] + * @see #setInt(byte[], int, int) + */ + public static int getInt(byte[] array, int offset) { + return (int) INT.get(array, offset); + } + + /** + * {@return a {@code float} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * Variants of {@linkplain Float#NaN } values are canonized to a single NaN value. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 4] + * @see #setFloat(byte[], int, float) + */ + public static float getFloat(byte[] array, int offset) { + // Using Float.intBitsToFloat collapses NaN values to a single + // "canonical" NaN value + return Float.intBitsToFloat((int) INT.get(array, offset)); + } + + /** + * {@return a {@code float} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * Variants of {@linkplain Float#NaN } values are silently read according + * to their bit patterns. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 4] + * @see #setFloatRaw(byte[], int, float) + */ + public static float getFloatRaw(byte[] array, int offset) { + // Just gets the bits as they are + return (float) FLOAT.get(array, offset); + } + + /** + * {@return a {@code long} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 8] + * @see #setLong(byte[], int, long) + */ + public static long getLong(byte[] array, int offset) { + return (long) LONG.get(array, offset); + } + + /** + * {@return a {@code double} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * Variants of {@linkplain Double#NaN } values are canonized to a single NaN value. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 8] + * @see #setDouble(byte[], int, double) + */ + public static double getDouble(byte[] array, int offset) { + // Using Double.longBitsToDouble collapses NaN values to a single + // "canonical" NaN value + return Double.longBitsToDouble((long) LONG.get(array, offset)); + } + + /** + * {@return a {@code double} from the provided {@code array} at the given {@code offset} + * using big endian order}. + *

+ * Variants of {@linkplain Double#NaN } values are silently read according to + * their bit patterns. + *

+ * There are no access alignment requirements. + * + * @param array to get a value from. + * @param offset where extraction in the array should begin + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 8] + * @see #setDoubleRaw(byte[], int, double) + */ + public static double getDoubleRaw(byte[] array, int offset) { + // Just gets the bits as they are + return (double) DOUBLE.get(array, offset); + } + + /* + * Methods for packing primitive values into byte arrays starting at a given + * offset. + */ + + /** + * Sets (writes) the provided {@code value} into + * the provided {@code array} beginning at the given {@code offset}. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length] + * @see #getBoolean(byte[], int) + */ + public static void setBoolean(byte[] array, int offset, boolean value) { + array[offset] = (byte) (value ? 1 : 0); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #getChar(byte[], int) + */ + public static void setChar(byte[] array, int offset, char value) { + CHAR.set(array, offset, value); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #getShort(byte[], int) + */ + public static void setShort(byte[] array, int offset, short value) { + SHORT.set(array, offset, value); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #getUnsignedShort(byte[], int) + */ + public static void setUnsignedShort(byte[] array, int offset, int value) { + SHORT.set(array, offset, (short) (char) value); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 4] + * @see #getInt(byte[], int) + */ + public static void setInt(byte[] array, int offset, int value) { + INT.set(array, offset, value); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * Variants of {@linkplain Float#NaN } values are canonized to a single NaN value. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #getFloat(byte[], int) + */ + public static void setFloat(byte[] array, int offset, float value) { + // Using Float.floatToIntBits collapses NaN values to a single + // "canonical" NaN value + INT.set(array, offset, Float.floatToIntBits(value)); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * Variants of {@linkplain Float#NaN } values are silently written according to + * their bit patterns. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #getFloatRaw(byte[], int) + */ + public static void setFloatRaw(byte[] array, int offset, float value) { + // Just sets the bits as they are + FLOAT.set(array, offset, value); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 4] + * @see #getLong(byte[], int) + */ + public static void setLong(byte[] array, int offset, long value) { + LONG.set(array, offset, value); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * Variants of {@linkplain Double#NaN } values are canonized to a single NaN value. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #getDouble(byte[], int) + */ + public static void setDouble(byte[] array, int offset, double value) { + // Using Double.doubleToLongBits collapses NaN values to a single + // "canonical" NaN value + LONG.set(array, offset, Double.doubleToLongBits(value)); + } + + /** + * Sets (writes) the provided {@code value} using big endian order into + * the provided {@code array} beginning at the given {@code offset}. + *

+ * Variants of {@linkplain Double#NaN } values are silently written according to + * their bit patterns. + *

+ * There are no access alignment requirements. + * + * @param array to set (write) a value into + * @param offset where setting (writing) in the array should begin + * @param value value to set in the array + * @throws IndexOutOfBoundsException if the provided {@code offset} is outside + * the range [0, array.length - 2] + * @see #getDoubleRaw(byte[], int) + */ + public static void setDoubleRaw(byte[] array, int offset, double value) { + // Just sets the bits as they are + DOUBLE.set(array, offset, value); + } + + private static VarHandle create(Class viewArrayClass) { + return MethodHandles.byteArrayViewVarHandle(viewArrayClass, ByteOrder.BIG_ENDIAN); + } + +} diff --git a/test/jdk/java/io/Bits/java.base/java/io/BitsProxy.java b/test/jdk/java/io/Bits/java.base/java/io/BitsProxy.java deleted file mode 100644 index 6151651cedd..00000000000 --- a/test/jdk/java/io/Bits/java.base/java/io/BitsProxy.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.io; - -/** - * Class to allow public access to package-private methods. - */ -public final class BitsProxy { - - public static boolean getBoolean(byte[] b, int off) { - return Bits.getBoolean(b, off); - } - - public static char getChar(byte[] b, int off) { - return Bits.getChar(b, off); - } - - public static short getShort(byte[] b, int off) { - return Bits.getShort(b, off); - } - - public static int getInt(byte[] b, int off) { - return Bits.getInt(b, off); - } - - public static float getFloat(byte[] b, int off) { - return Bits.getFloat(b, off); - } - - public static long getLong(byte[] b, int off) { - return Bits.getLong(b, off); - } - - public static double getDouble(byte[] b, int off) { - return Bits.getDouble(b, off); - } - - - public static void putBoolean(byte[] b, int off, boolean val) { - Bits.putBoolean(b, off, val); - } - - public static void putChar(byte[] b, int off, char val) { - Bits.putChar(b, off, val); - } - - public static void putShort(byte[] b, int off, short val) { - Bits.putShort(b, off, val); - } - - public static void putInt(byte[] b, int off, int val) { - Bits.putInt(b, off, val); - } - - public static void putFloat(byte[] b, int off, float val) { - Bits.putFloat(b, off, val); - } - - public static void putLong(byte[] b, int off, long val) { - Bits.putLong(b, off, val); - } - - public static void putDouble(byte[] b, int off, double val) { - Bits.putDouble(b, off, val); - } - -} diff --git a/test/jdk/java/io/Bits/ReadWriteValues.java b/test/jdk/jdk/internal/util/ByteArray/ReadWriteValues.java similarity index 62% rename from test/jdk/java/io/Bits/ReadWriteValues.java rename to test/jdk/jdk/internal/util/ByteArray/ReadWriteValues.java index 835c439e485..403f2fa08f3 100644 --- a/test/jdk/java/io/Bits/ReadWriteValues.java +++ b/test/jdk/jdk/internal/util/ByteArray/ReadWriteValues.java @@ -24,19 +24,19 @@ /* * @test * @bug 8299576 + * @modules java.base/jdk.internal.util * @summary Verify that reads and writes of primitives are correct - * @compile/module=java.base java/io/BitsProxy.java * @run junit ReadWriteValues */ -import java.io.BitsProxy; +import jdk.internal.util.ByteArray; +import org.junit.jupiter.api.*; + import java.util.concurrent.ThreadLocalRandom; import java.util.stream.DoubleStream; import java.util.stream.LongStream; import java.util.stream.Stream; -import org.junit.jupiter.api.*; - import static org.junit.jupiter.api.Assertions.*; final class ReadWriteValues { @@ -53,7 +53,7 @@ final class ReadWriteValues { longs().forEach(l -> { short expected = (short) l; RefImpl.putShort(BUFF, OFFSET, expected); - short actual = BitsProxy.getShort(BUFF, OFFSET); + short actual = ByteArray.getShort(BUFF, OFFSET); assertEquals(expected, actual); }); } @@ -62,7 +62,7 @@ final class ReadWriteValues { void testPutShort() { longs().forEach(l -> { short expected = (short) l; - BitsProxy.putShort(BUFF, OFFSET, expected); + ByteArray.setShort(BUFF, OFFSET, expected); short actual = RefImpl.getShort(BUFF, OFFSET); assertEquals(expected, actual); }); @@ -73,7 +73,7 @@ final class ReadWriteValues { longs().forEach(l -> { char expected = (char) l; RefImpl.putChar(BUFF, OFFSET, expected); - char actual = BitsProxy.getChar(BUFF, OFFSET); + char actual = ByteArray.getChar(BUFF, OFFSET); assertEquals(expected, actual); }); } @@ -82,7 +82,7 @@ final class ReadWriteValues { void testPutChar() { longs().forEach(l -> { char expected = (char) l; - BitsProxy.putChar(BUFF, OFFSET, expected); + ByteArray.setChar(BUFF, OFFSET, expected); char actual = RefImpl.getChar(BUFF, OFFSET); assertEquals(expected, actual); }); @@ -93,7 +93,7 @@ final class ReadWriteValues { longs().forEach(l -> { int expected = (int) l; RefImpl.putInt(BUFF, OFFSET, expected); - int actual = BitsProxy.getInt(BUFF, OFFSET); + int actual = ByteArray.getInt(BUFF, OFFSET); assertEquals(expected, actual); }); } @@ -102,7 +102,7 @@ final class ReadWriteValues { void testPutInt() { longs().forEach(l -> { int expected = (int) l; - BitsProxy.putInt(BUFF, OFFSET, expected); + ByteArray.setInt(BUFF, OFFSET, expected); int actual = RefImpl.getInt(BUFF, OFFSET); assertEquals(expected, actual); }); @@ -112,7 +112,7 @@ final class ReadWriteValues { void testGetLong() { longs().forEach(expected -> { RefImpl.putLong(BUFF, OFFSET, expected); - long actual = BitsProxy.getLong(BUFF, OFFSET); + long actual = ByteArray.getLong(BUFF, OFFSET); assertEquals(expected, actual); }); } @@ -120,7 +120,7 @@ final class ReadWriteValues { @Test void testPutLong() { longs().forEach(expected -> { - BitsProxy.putLong(BUFF, OFFSET, expected); + ByteArray.setLong(BUFF, OFFSET, expected); long actual = RefImpl.getLong(BUFF, OFFSET); assertEquals(expected, actual); }); @@ -130,7 +130,7 @@ final class ReadWriteValues { void testGetFloat() { floats().forEach(expected -> { RefImpl.putFloat(BUFF, OFFSET, expected); - float actual = BitsProxy.getFloat(BUFF, OFFSET); + float actual = ByteArray.getFloat(BUFF, OFFSET); assertEquals(expected, actual); }); } @@ -138,7 +138,7 @@ final class ReadWriteValues { @Test void testPutFloat() { floats().forEach(expected -> { - BitsProxy.putFloat(BUFF, OFFSET, expected); + ByteArray.setFloat(BUFF, OFFSET, expected); float actual = RefImpl.getFloat(BUFF, OFFSET); assertEquals(expected, actual); }); @@ -148,7 +148,7 @@ final class ReadWriteValues { void testGetDouble() { doubles().forEach(expected -> { RefImpl.putDouble(BUFF, OFFSET, expected); - double actual = BitsProxy.getDouble(BUFF, OFFSET); + double actual = ByteArray.getDouble(BUFF, OFFSET); assertEquals(expected, actual); }); } @@ -156,31 +156,183 @@ final class ReadWriteValues { @Test void testPutDouble() { doubles().forEach(expected -> { - BitsProxy.putDouble(BUFF, OFFSET, expected); + ByteArray.setDouble(BUFF, OFFSET, expected); double actual = RefImpl.getDouble(BUFF, OFFSET); assertEquals(expected, actual); }); } + @Test + void testPutUnsignedShort() { + longs().forEach(l -> { + int expected = Short.toUnsignedInt((short) l); + ByteArray.setUnsignedShort(BUFF, OFFSET, expected); + int actual = Short.toUnsignedInt(RefImpl.getShort(BUFF, OFFSET)); + assertEquals(expected, actual); + }); + } + + + // Unusual cases + + @Test + void testNullArray() { + assertThrowsOriginal(NullPointerException.class, () -> ByteArray.getInt(null, OFFSET)); + assertThrowsOriginal(NullPointerException.class, () -> ByteArray.setInt(null, OFFSET, 1)); + } + + @Test + void testNegArg() { + assertThrowsOriginal(IndexOutOfBoundsException.class, () -> ByteArray.getInt(BUFF, -1)); + assertThrowsOriginal(IndexOutOfBoundsException.class, () -> ByteArray.setInt(BUFF, -1, 1)); + } + + @Test + void testOutOfBounds() { + assertThrowsOriginal(IndexOutOfBoundsException.class, () -> ByteArray.getInt(BUFF, BUFF.length)); + assertThrowsOriginal(IndexOutOfBoundsException.class, () -> ByteArray.setInt(BUFF, BUFF.length, 1)); + } + + // At-zero methods + + @Test + void testGetShortAtZero() { + longs().forEach(l -> { + short expected = (short) l; + RefImpl.putShort(BUFF, 0, expected); + short actual = ByteArray.getShort(BUFF); + assertEquals(expected, actual); + }); + } + + @Test + void testPutShortAtZero() { + longs().forEach(l -> { + short expected = (short) l; + ByteArray.setShort(BUFF, expected); + short actual = RefImpl.getShort(BUFF, 0); + assertEquals(expected, actual); + }); + } + + @Test + void testGetCharAtZero() { + longs().forEach(l -> { + char expected = (char) l; + RefImpl.putChar(BUFF, 0, expected); + char actual = ByteArray.getChar(BUFF); + assertEquals(expected, actual); + }); + } + + @Test + void testPutCharAtZero() { + longs().forEach(l -> { + char expected = (char) l; + ByteArray.setChar(BUFF, expected); + char actual = RefImpl.getChar(BUFF, 0); + assertEquals(expected, actual); + }); + } + + @Test + void testGetIntAtZero() { + longs().forEach(l -> { + int expected = (int) l; + RefImpl.putInt(BUFF, 0, expected); + int actual = ByteArray.getInt(BUFF); + assertEquals(expected, actual); + }); + } + + @Test + void testPutIntAtZero() { + longs().forEach(l -> { + int expected = (int) l; + ByteArray.setInt(BUFF, expected); + int actual = RefImpl.getInt(BUFF, 0); + assertEquals(expected, actual); + }); + } + + @Test + void testGetLongAtZero() { + longs().forEach(expected -> { + RefImpl.putLong(BUFF, 0, expected); + long actual = ByteArray.getLong(BUFF); + assertEquals(expected, actual); + }); + } + + @Test + void testPutLongAtZero() { + longs().forEach(expected -> { + ByteArray.setLong(BUFF, expected); + long actual = RefImpl.getLong(BUFF, 0); + assertEquals(expected, actual); + }); + } + + @Test + void testGetFloatAtZero() { + floats().forEach(expected -> { + RefImpl.putFloat(BUFF, 0, expected); + float actual = ByteArray.getFloat(BUFF); + assertEquals(expected, actual); + }); + } + + @Test + void testPutFloatAtZero() { + floats().forEach(expected -> { + ByteArray.setFloat(BUFF, expected); + float actual = RefImpl.getFloat(BUFF, 0); + assertEquals(expected, actual); + }); + } + + @Test + void testGetDoubleAtZero() { + doubles().forEach(expected -> { + RefImpl.putDouble(BUFF, 0, expected); + double actual = ByteArray.getDouble(BUFF); + assertEquals(expected, actual); + }); + } + + @Test + void testPutDoubleAtZero() { + doubles().forEach(expected -> { + ByteArray.setDouble(BUFF, expected); + double actual = RefImpl.getDouble(BUFF, 0); + assertEquals(expected, actual); + }); + } + + @Test + void testPutUnsignedShortAtZero() { + longs().forEach(l -> { + int expected = Short.toUnsignedInt((short) l); + ByteArray.setUnsignedShort(BUFF, expected); + int actual = Short.toUnsignedInt(RefImpl.getShort(BUFF, 0)); + assertEquals(expected, actual); + }); + } + // Unusual cases @Test - void testNullArray() { - assertThrowsOriginal(NullPointerException.class, () -> BitsProxy.getInt(null, OFFSET)); - assertThrowsOriginal(NullPointerException.class, () -> BitsProxy.putInt(null, OFFSET, 1)); + void testNullArrayAtZero() { + assertThrowsOriginal(NullPointerException.class, () -> ByteArray.getInt(null)); + assertThrowsOriginal(NullPointerException.class, () -> ByteArray.setInt(null, 1)); } @Test - void testNegArg() { - assertThrowsOriginal(IndexOutOfBoundsException.class, () -> BitsProxy.getInt(BUFF, -1)); - assertThrowsOriginal(IndexOutOfBoundsException.class, () -> BitsProxy.putInt(BUFF, -1, 1)); + void testOutOfBoundsAtZero() { + assertThrowsOriginal(IndexOutOfBoundsException.class, () -> ByteArray.getInt(new byte[1])); + assertThrowsOriginal(IndexOutOfBoundsException.class, () -> ByteArray.setInt(new byte[1],1)); } - @Test - void testOutOfBounds() { - assertThrowsOriginal(IndexOutOfBoundsException.class, () -> BitsProxy.getInt(BUFF, BUFF.length)); - assertThrowsOriginal(IndexOutOfBoundsException.class, () -> BitsProxy.putInt(BUFF, BUFF.length, 1)); - } static LongStream longs() { return ThreadLocalRandom.current().longs(ITERATIONS); @@ -315,5 +467,4 @@ final class ReadWriteValues { putLong(b, off, Double.doubleToLongBits(val)); } } - } diff --git a/test/micro/org/openjdk/bench/java/io/PrimitiveFieldSerializationBenchmark.java b/test/micro/org/openjdk/bench/java/io/PrimitiveFieldSerializationBenchmark.java new file mode 100644 index 00000000000..3d9c8a5cefe --- /dev/null +++ b/test/micro/org/openjdk/bench/java/io/PrimitiveFieldSerializationBenchmark.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.io; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(2) +@Warmup(iterations = 4, time = 2, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 4, time = 2, timeUnit = TimeUnit.SECONDS) +@State(Scope.Thread) + +public class PrimitiveFieldSerializationBenchmark { + + public static void main(String[] args) throws Exception { + + Options options = new OptionsBuilder() + .include(PrimitiveFieldSerializationBenchmark.class.getSimpleName()) + .build(); + new Runner(options).run(); + } + + @State(Scope.Benchmark) + public static class Log { + + MyData myData = new MyData((byte) 1, 'a', (short) 47, 1234, 0.01f, 1234L, 0.01d); + MyRecord myRecord = new MyRecord((byte) 1, 'a', (short) 47, 1234, 0.01f, 1234L, 0.01d); + } + + private OutputStream bos; + private ObjectOutputStream os; + + @Setup + public void setupStreams(Blackhole bh) throws IOException { + bos = new BlackholeOutputStream(bh); + os = new ObjectOutputStream(bos); + } + + @TearDown + public void tearDownStreams() throws IOException { + os.close(); + bos.close(); + } + + private static final class MyData implements Serializable { + byte b; + char c; + short s; + int i; + float f; + long l; + double d; + + public MyData(byte b, char c, short s, int i, float f, long l, double d) { + this.b = b; + this.c = c; + this.s = s; + this.i = i; + this.f = f; + this.l = l; + this.d = d; + } + } + + private record MyRecord(byte b, + char c, + short s, + int i, + float f, + long l, + double d) implements Serializable { + } + + @Benchmark + public void serializeData(Log input) throws IOException { + os.writeObject(input.myData); + } + + @Benchmark + public void serializeRecord(Log input) throws IOException { + os.writeObject(input.myRecord); + } + + public static final class BlackholeOutputStream extends OutputStream { + + private final Blackhole bh; + + public BlackholeOutputStream(Blackhole bh) { + this.bh = bh; + } + + @Override + public void write(int b) { + bh.consume(b); + } + + @Override + public void write(byte[] b) { + bh.consume(b); + } + + @Override + public void write(byte[] b, int off, int len) { + bh.consume(b); + } + } + + +}