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
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -28,6 +28,7 @@
import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.UnsignedAdapters;
import sun.invoke.util.Wrapper;

import java.lang.invoke.MethodHandle;
Expand Down Expand Up @@ -316,6 +317,53 @@ 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) {
return UnsignedAdapters.asUnsigned(target, adaptedType);
}

/**
* Adapts a target var handle by pre-processing incoming and outgoing values using a pair of unary filter functions.
* <p>
Expand Down
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2020, 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.foreign;

import jdk.incubator.foreign.MemoryHandles;
import sun.invoke.util.Wrapper;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.Objects;

public class UnsignedAdapters {

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 {
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);
}
}

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 intToUnsignedByte(target);
} else if (adaptedType == int.class && carrier == short.class) {
return intToUnsignedShort(target);
} else if (adaptedType == long.class && carrier == byte.class) {
return longToUnsignedByte(target);
} else if (adaptedType == long.class && carrier == short.class) {
return longToUnsignedShort(target);
} else if (adaptedType == long.class && carrier == int.class) {
return longToUnsignedInt(target);
} else {
throw new InternalError("should not reach here");
}
}

// int to byte
private static VarHandle intToUnsignedByte(VarHandle target) {
if (target.varType() != byte.class)
throw new InternalError("expected byte carrier, but got: " + target.varType());
return MemoryHandles.filterValue(target, INT_TO_BYTE, BYTE_TO_UNSIGNED_INT);
}

// int to short
private static VarHandle intToUnsignedShort(VarHandle target) {
if (target.varType() != short.class)
throw new InternalError("expected byte carrier, but got: " + target.varType());
return MemoryHandles.filterValue(target, INT_TO_SHORT, SHORT_TO_UNSIGNED_INT);
}

// long to byte
private static VarHandle longToUnsignedByte(VarHandle target) {
if (target.varType() != byte.class)
throw new InternalError("expected byte carrier, but got: " + target.varType());
return MemoryHandles.filterValue(target, LONG_TO_BYTE, BYTE_TO_UNSIGNED_LONG);
}

// long to short
private static VarHandle longToUnsignedShort(VarHandle target) {
if (target.varType() != short.class)
throw new InternalError("expected byte carrier, but got: " + target.varType());
return MemoryHandles.filterValue(target, LONG_TO_SHORT, SHORT_TO_UNSIGNED_LONG);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super sure of the added value of these - the check is essentially a no op, since you already have a switch where, based on the var handle carrier, you decide which method to call. So perhaps we can just inline the right MH filter inside the switch above?


//long to int
private static VarHandle longToUnsignedInt(VarHandle target) {
if (target.varType() != int.class)
throw new InternalError("expected byte carrier, but got: " + target.varType());
return MemoryHandles.filterValue(target, LONG_TO_INT, INT_TO_UNSIGNED_LONG);
}

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

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

private static void checkTargetWiderThanCarrier(Class<?> carrier, Class<?> target) {
if (Wrapper.forPrimitiveType(target).bitWidth() <= Wrapper.forPrimitiveType(carrier).bitWidth()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that MemoryHandles has already a routine to retrieve carrier size - maybe there's some reuse possible there

throw newIllegalArgumentException(
target.getSimpleName() + " is not wider than: ", carrier.getSimpleName());
}
}

static RuntimeException newIllegalArgumentException(String message, Object obj) {
return new IllegalArgumentException(message(message, obj));
}
private static String message(String message, Object obj) {
if (obj != null) message = message + ": " + obj;
return message;
}
}