Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 109 additions & 71 deletions src/java.base/share/classes/jdk/internal/util/ByteArray.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, 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
Expand All @@ -25,13 +25,12 @@

package jdk.internal.util;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import jdk.internal.misc.Unsafe;
import jdk.internal.vm.annotation.ForceInline;

/**
* Utility methods for packing/unpacking primitive values in/out of byte arrays
* using {@linkplain ByteOrder#BIG_ENDIAN big endian order} (aka. "network order").
* using {@linkplain java.nio.ByteOrder#BIG_ENDIAN big endian order} (aka. "network order").
* <p>
* All methods in this class will throw an {@linkplain NullPointerException} if {@code null} is
* passed in as a method parameter for a byte array.
Expand All @@ -41,12 +40,21 @@ 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);
/**
* The {@code Unsafe} can be functionality replaced by
* {@linkplain java.lang.invoke.MethodHandles#byteArrayViewVarHandle byteArrayViewVarHandle},
* but it's not feasible in practices, because {@code ByteArray} and {@code ByteArrayLittleEndian}
* can be used in fundamental classes, {@code VarHandle} exercise many other
* code at VM startup, this could lead a recursive calls when fundamental
* classes is used in {@code VarHandle}.
*/
static final Unsafe UNSAFE = Unsafe.getUnsafe();

@ForceInline
static long arrayOffset(byte[] array, int typeBytes, int offset) {
return (long) Preconditions.checkIndex(offset, array.length - typeBytes + 1, Preconditions.AIOOBE_FORMATTER)
+ Unsafe.ARRAY_BYTE_BASE_OFFSET;
}

/*
* Methods for unpacking primitive values from byte arrays starting at
Expand All @@ -62,6 +70,7 @@ private ByteArray() {
* the range [0, array.length - 1]
* @see #setBoolean(byte[], int, boolean)
*/
@ForceInline
public static boolean getBoolean(byte[] array, int offset) {
return array[offset] != 0;
}
Expand All @@ -78,8 +87,12 @@ public static boolean getBoolean(byte[] array, int offset) {
* the range [0, array.length - 2]
* @see #setChar(byte[], int, char)
*/
@ForceInline
public static char getChar(byte[] array, int offset) {
return (char) CHAR.get(array, offset);
return UNSAFE.getCharUnaligned(
array,
arrayOffset(array, Character.BYTES, offset),
true);
}

/**
Expand All @@ -95,8 +108,12 @@ public static char getChar(byte[] array, int offset) {
* the range [0, array.length - 2]
* @see #setShort(byte[], int, short)
*/
@ForceInline
public static short getShort(byte[] array, int offset) {
return (short) SHORT.get(array, offset);
return UNSAFE.getShortUnaligned(
array,
arrayOffset(array, Short.BYTES, offset),
true);
}

/**
Expand All @@ -112,8 +129,9 @@ public static short getShort(byte[] array, int offset) {
* the range [0, array.length - 2]
* @see #setUnsignedShort(byte[], int, int)
*/
@ForceInline
public static int getUnsignedShort(byte[] array, int offset) {
return Short.toUnsignedInt((short) SHORT.get(array, offset));
return Short.toUnsignedInt(getShort(array, offset));
}

/**
Expand All @@ -128,48 +146,47 @@ public static int getUnsignedShort(byte[] array, int offset) {
* the range [0, array.length - 4]
* @see #setInt(byte[], int, int)
*/
@ForceInline
public static int getInt(byte[] array, int offset) {
return (int) INT.get(array, offset);
return UNSAFE.getIntUnaligned(
array,
arrayOffset(array, Integer.BYTES, offset),
true);
}

/**
* {@return a {@code float} from the provided {@code array} at the given {@code offset}
* {@return an {@code unsigned int} from the provided {@code array} at the given {@code offset}
* using big endian order}.
* <p>
* Variants of {@linkplain Float#NaN } values are canonized to a single NaN value.
* <p>
* 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 long} representing an unsigned int from the array
* @throws IndexOutOfBoundsException if the provided {@code offset} is outside
* the range [0, array.length - 4]
* @see #setFloat(byte[], int, float)
* @see #setUnsignedInt(byte[], int, long)
*/
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));
@ForceInline
public static long getUnsignedInt(byte[] array, int offset) {
return Integer.toUnsignedLong(getInt(array, offset));
}

/**
* {@return a {@code float} from the provided {@code array} at the given {@code offset}
* using big endian order}.
* <p>
* Variants of {@linkplain Float#NaN } values are silently read according
* to their bit patterns.
* <p>
* 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)
* @see #setFloat(byte[], int, float)
*/
public static float getFloatRaw(byte[] array, int offset) {
// Just gets the bits as they are
return (float) FLOAT.get(array, offset);
@ForceInline
public static float getFloat(byte[] array, int offset) {
return Float.intBitsToFloat(getInt(array, offset));
}

/**
Expand All @@ -184,16 +201,18 @@ public static float getFloatRaw(byte[] array, int offset) {
* the range [0, array.length - 8]
* @see #setLong(byte[], int, long)
*/
@ForceInline
public static long getLong(byte[] array, int offset) {
return (long) LONG.get(array, offset);
return UNSAFE.getLongUnaligned(
array,
arrayOffset(array, Long.BYTES, offset),
true);
}

/**
* {@return a {@code double} from the provided {@code array} at the given {@code offset}
* using big endian order}.
* <p>
* Variants of {@linkplain Double#NaN } values are canonized to a single NaN value.
* <p>
* There are no access alignment requirements.
*
* @param array to get a value from.
Expand All @@ -202,30 +221,9 @@ public static long getLong(byte[] array, int offset) {
* the range [0, array.length - 8]
* @see #setDouble(byte[], int, double)
*/
@ForceInline
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}.
* <p>
* Variants of {@linkplain Double#NaN } values are silently read according to
* their bit patterns.
* <p>
* 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);
return Double.longBitsToDouble(getLong(array, offset));
}

/*
Expand All @@ -244,6 +242,7 @@ public static double getDoubleRaw(byte[] array, int offset) {
* the range [0, array.length]
* @see #getBoolean(byte[], int)
*/
@ForceInline
public static void setBoolean(byte[] array, int offset, boolean value) {
array[offset] = (byte) (value ? 1 : 0);
}
Expand All @@ -261,8 +260,13 @@ public static void setBoolean(byte[] array, int offset, boolean value) {
* the range [0, array.length - 2]
* @see #getChar(byte[], int)
*/
@ForceInline
public static void setChar(byte[] array, int offset, char value) {
CHAR.set(array, offset, value);
UNSAFE.putCharUnaligned(
array,
arrayOffset(array, Character.BYTES, offset),
value,
true);
}

/**
Expand All @@ -278,8 +282,13 @@ public static void setChar(byte[] array, int offset, char value) {
* the range [0, array.length - 2]
* @see #getShort(byte[], int)
*/
@ForceInline
public static void setShort(byte[] array, int offset, short value) {
SHORT.set(array, offset, value);
UNSAFE.putShortUnaligned(
array,
arrayOffset(array, Short.BYTES, offset),
value,
true);
}

/**
Expand All @@ -295,8 +304,9 @@ public static void setShort(byte[] array, int offset, short value) {
* the range [0, array.length - 2]
* @see #getUnsignedShort(byte[], int)
*/
@ForceInline
public static void setUnsignedShort(byte[] array, int offset, int value) {
SHORT.set(array, offset, (short) (char) value);
setShort(array, offset, (short) (char) value);
}

/**
Expand All @@ -312,8 +322,31 @@ public static void setUnsignedShort(byte[] array, int offset, int value) {
* the range [0, array.length - 4]
* @see #getInt(byte[], int)
*/
@ForceInline
public static void setInt(byte[] array, int offset, int value) {
INT.set(array, offset, value);
UNSAFE.putIntUnaligned(
array,
arrayOffset(array, Integer.BYTES, offset),
value,
true);
}

/**
* Sets (writes) the provided {@code value} using big endian order into
* the provided {@code array} beginning at the given {@code offset}.
* <p>
* 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 #getUnsignedInt(byte[], int)
*/
@ForceInline
public static void setUnsignedInt(byte[] array, int offset, long value) {
setInt(array, offset, (int) value);
}

/**
Expand All @@ -331,10 +364,11 @@ public static void setInt(byte[] array, int offset, int value) {
* the range [0, array.length - 2]
* @see #getFloat(byte[], int)
*/
@ForceInline
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));
setInt(array, offset, Float.floatToIntBits(value));
}

/**
Expand All @@ -351,11 +385,12 @@ public static void setFloat(byte[] array, int offset, float value) {
* @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)
* @see #getFloat(byte[], int)
*/
@ForceInline
public static void setFloatRaw(byte[] array, int offset, float value) {
// Just sets the bits as they are
FLOAT.set(array, offset, value);
setInt(array, offset, Float.floatToRawIntBits(value));
}

/**
Expand All @@ -371,8 +406,13 @@ public static void setFloatRaw(byte[] array, int offset, float value) {
* the range [0, array.length - 4]
* @see #getLong(byte[], int)
*/
@ForceInline
public static void setLong(byte[] array, int offset, long value) {
LONG.set(array, offset, value);
UNSAFE.putLongUnaligned(
array,
arrayOffset(array, Long.BYTES, offset),
value,
true);
}

/**
Expand All @@ -390,10 +430,11 @@ public static void setLong(byte[] array, int offset, long value) {
* the range [0, array.length - 2]
* @see #getDouble(byte[], int)
*/
@ForceInline
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));
setLong(array, offset, Double.doubleToLongBits(value));
}

/**
Expand All @@ -410,15 +451,12 @@ public static void setDouble(byte[] array, int offset, double value) {
* @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)
* @see #getDouble(byte[], int)
*/
@ForceInline
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);
setLong(array, offset, Double.doubleToRawLongBits(value));
}

}
Loading