Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unsigned adapter handles #173

Closed
Closed
@@ -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);
}