Skip to content
Permalink
Browse files

Add unsigned adapter handles

Reviewed-by: mcimadamore, jvernee
  • Loading branch information
ChrisHegarty authored and JornVernee committed May 19, 2020
1 parent 6450214 commit 3193db4c91d808ec9a00c13c074456678e65286d
@@ -36,6 +36,7 @@
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import java.util.List;
import java.util.Objects;

/**
* This class defines several factory methods for constructing and combining memory access var handles.
@@ -132,6 +133,17 @@ private MemoryHandles() {
private static final MethodHandle ADD_OFFSET;
private static final MethodHandle ADD_STRIDE;

private static final MethodHandle INT_TO_BYTE;
private static final MethodHandle BYTE_TO_UNSIGNED_INT;
private static final MethodHandle INT_TO_SHORT;
private static final MethodHandle SHORT_TO_UNSIGNED_INT;
private static final MethodHandle LONG_TO_BYTE;
private static final MethodHandle BYTE_TO_UNSIGNED_LONG;
private static final MethodHandle LONG_TO_SHORT;
private static final MethodHandle SHORT_TO_UNSIGNED_LONG;
private static final MethodHandle LONG_TO_INT;
private static final MethodHandle INT_TO_UNSIGNED_LONG;

static {
try {
LONG_TO_ADDRESS = MethodHandles.lookup().findStatic(MemoryHandles.class, "longToAddress",
@@ -143,6 +155,27 @@ private MemoryHandles() {

ADD_STRIDE = MethodHandles.lookup().findStatic(MemoryHandles.class, "addStride",
MethodType.methodType(MemoryAddress.class, MemoryAddress.class, long.class, long.class));

INT_TO_BYTE = MethodHandles.explicitCastArguments(MethodHandles.identity(byte.class),
MethodType.methodType(byte.class, int.class));
BYTE_TO_UNSIGNED_INT = MethodHandles.lookup().findStatic(Byte.class, "toUnsignedInt",
MethodType.methodType(int.class, byte.class));
INT_TO_SHORT = MethodHandles.explicitCastArguments(MethodHandles.identity(short.class),
MethodType.methodType(short.class, int.class));
SHORT_TO_UNSIGNED_INT = MethodHandles.lookup().findStatic(Short.class, "toUnsignedInt",
MethodType.methodType(int.class, short.class));
LONG_TO_BYTE = MethodHandles.explicitCastArguments(MethodHandles.identity(byte.class),
MethodType.methodType(byte.class, long.class));
BYTE_TO_UNSIGNED_LONG = MethodHandles.lookup().findStatic(Byte.class, "toUnsignedLong",
MethodType.methodType(long.class, byte.class));
LONG_TO_SHORT = MethodHandles.explicitCastArguments(MethodHandles.identity(short.class),
MethodType.methodType(short.class, long.class));
SHORT_TO_UNSIGNED_LONG = MethodHandles.lookup().findStatic(Short.class, "toUnsignedLong",
MethodType.methodType(long.class, short.class));
LONG_TO_INT = MethodHandles.explicitCastArguments(MethodHandles.identity(int.class),
MethodType.methodType(int.class, long.class));
INT_TO_UNSIGNED_LONG = MethodHandles.lookup().findStatic(Integer.class, "toUnsignedLong",
MethodType.methodType(long.class, int.class));
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
@@ -316,6 +349,72 @@ public static VarHandle asAddressVarHandle(VarHandle target) {
}
}

/**
* Adapts a target var handle by narrowing incoming values and widening
* outgoing values, to and from the given type, respectively.
* <p>
* The returned var handle can be used to conveniently treat unsigned
* primitive data types as if they were a wider signed primitive type. For
* example, it is often convenient to model an <i>unsigned short</i> as a
* Java {@code int} to avoid dealing with negative values, which would be
* the case if modeled as a Java {@code short}. The returned var handle
* converts to and from wider primitive types, to a more narrow possibly
* unsigned primitive type.
* <p>
* When calling e.g. {@link VarHandle#set(Object...)} on the resulting var
* handle, the incoming value (of type {@code adaptedType}) is converted by a
* <i>narrowing primitive conversion</i> and then passed to the {@code
* target} var handle. A narrowing primitive conversion may lose information
* about the overall magnitude of a numeric value. Conversely, when calling
* e.g. {@link VarHandle#get(Object...)} on the resulting var handle, the
* returned value obtained from the {@code target} var handle is converted
* by a <i>unsigned widening conversion</i> before being returned to the
* caller. In an unsigned widening conversion the high-order bits greater
* than that of the {@code target} carrier type are zero, and the low-order
* bits (equal to the width of the {@code target} carrier type) are equal to
* the bits of the value obtained from the {@code target} var handle.
* <p>
* The returned var handle will feature the variable type {@code adaptedType},
* and the same access coordinates, the same access modes (see {@link
* java.lang.invoke.VarHandle.AccessMode}, and the same atomic access
* guarantees, as those featured by the {@code target} var handle.
*
* @param target the memory access var handle to be adapted
* @param adaptedType the adapted type
* @returns the adapted var handle.
* @throws IllegalArgumentException if the carrier type of {@code target}
* is not one of {@code byte}, {@code short}, or {@code int}; if {@code
* adaptedType} is not one of {@code int}, or {@code long}; if the bitwidth
* of the {@code adaptedType} is not greater than that of the {@code target}
* carrier type
* @throws NullPointerException if either of {@code target} or {@code
* adaptedType} is null
*
* @jls 5.1.3 Narrowing Primitive Conversion
*/
public static VarHandle asUnsigned(VarHandle target, final Class<?> adaptedType) {
Objects.requireNonNull(target);
Objects.requireNonNull(adaptedType);
final Class<?> carrier = target.varType();
checkWidenable(carrier);
checkNarrowable(adaptedType);
checkTargetWiderThanCarrier(carrier, adaptedType);

if (adaptedType == int.class && carrier == byte.class) {
return filterValue(target, INT_TO_BYTE, BYTE_TO_UNSIGNED_INT);
} else if (adaptedType == int.class && carrier == short.class) {
return filterValue(target, INT_TO_SHORT, SHORT_TO_UNSIGNED_INT);
} else if (adaptedType == long.class && carrier == byte.class) {
return filterValue(target, LONG_TO_BYTE, BYTE_TO_UNSIGNED_LONG);
} else if (adaptedType == long.class && carrier == short.class) {
return filterValue(target, LONG_TO_SHORT, SHORT_TO_UNSIGNED_LONG);
} else if (adaptedType == long.class && carrier == int.class) {
return filterValue(target, LONG_TO_INT, INT_TO_UNSIGNED_LONG);
} else {
throw new InternalError("should not reach here");
}
}

/**
* Adapts a target var handle by pre-processing incoming and outgoing values using a pair of unary filter functions.
* <p>
@@ -532,6 +631,25 @@ private static long carrierSize(Class<?> carrier) {
return Utils.bitsToBytesOrThrow(bitsAlignment, IllegalStateException::new);
}

private static void checkWidenable(Class<?> carrier) {
if (!(carrier == byte.class || carrier == short.class || carrier == int.class)) {
throw new IllegalArgumentException("illegal carrier:" + carrier.getSimpleName());
}
}

private static void checkNarrowable(Class<?> type) {
if (!(type == int.class || type == long.class)) {
throw new IllegalArgumentException("illegal adapter type: " + type.getSimpleName());
}
}

private static void checkTargetWiderThanCarrier(Class<?> carrier, Class<?> target) {
if (Wrapper.forPrimitiveType(target).bitWidth() <= Wrapper.forPrimitiveType(carrier).bitWidth()) {
throw new IllegalArgumentException(
target.getSimpleName() + " is not wider than: " + carrier.getSimpleName());
}
}

private static MemoryAddress longToAddress(long value) {
return MemoryAddress.ofLong(value);
}

0 comments on commit 3193db4

Please sign in to comment.