[foreign-memaccess] RFR: Add unsigned adapter handles

Maurizio Cimadamore mcimadamore at openjdk.java.net
Mon May 18 10:35:42 UTC 2020

On Sat, 16 May 2020 08:27:25 GMT, Chris Hegarty <chegar at openjdk.org> wrote:

> Hi,
> As part of feedback on the Foreign Memory API (when experimenting with its usage internally in the JDK), a small number
> of potential usability enhancements could be made to the API. This is the third such.
> This change proposes to add a new method:
>   MemoryHandles::asUnsigned
> When dealing with _unsigned_ native data types it is often convenient to model them as
> wider _signed_ Java primitives (with appropriate magnitude checks). For example, it is convenient to model an _unsigned
> short_ as a Java _int_ to avoid dealing with negative values, which would be the case if modeled as a Java short. We do
> this all the time in the JDK implementation, and it even bleeds into the APIs sometimes.  To model this, I found myself
> reaching for `MemoryHandles::filterValue` to do the narrowing and widening, so that my layout class could expose the
> set of memory handles with appropriate carrier types that the higher-level java code was expecting. This worked fine,
> but Maurizio pointed out that this is something that the API should probably expose at a slightly higher level. Writing
> these widening and narrowing adapters using _filterValue_ is not a lot of code, but easy to make mistakes and a place
> for bugs to hide.  A single static adapter factory, `MemoryHandles::asUnsigned(VarHandle target, Class<?>
> adaptedType)`, (thanks Maurizio for the suggestion) would be sufficient to provide such functionality. It adapts a
> target var handle by narrowing incoming values and widening outgoing values, to and from the given type, respectively.
> When calling _set_ on the resulting var handle, the incoming value (of type adaptedType) is converted by a narrowing
> primitive conversion and then passed to the target var handle. Conversely, when calling _get_ on the resulting var
> handle, the returned value obtained from the target var handle is converted by an unsigned widening conversion before
> being returned to the caller.  We don't necessarily need to, or can, support all combinations, but there seems to be a
> sweet spot where Java longs and ints can be used to model _unsigned char_, _unsigned short_, and _unsigned int_, [1]
> which covers the majority of the use-cases. This also seems to align nicely with the primitive wrapper unsigned
> widening methods, e.g. `Byte::toUnsignedInt`, `Byte::toUnsignedLong`, etc.  I started this discussion as a PR so that
> hopefully the code changes can help convey the idea and inform readers.  Comments welcome.   [1]
> https://cr.openjdk.java.net/~chegar/foreign/asUnsigned.pdf

I think the decisions in your document are sound. One point which is perhaps not stated clearly (but which we have
discussed) was also the fact that `char` is a perfectly decent carrier when users want to work with 16 bits in an
unsigned fashion, and that motivates some of the choices we made downstream (e.g. to just support `int` and `long`
as *adapted* types).

After looking at the code some more, I agree with Jorn that the templating is perhaps unnecessary. We can go from
byte/short/int to wider type using the unsigned helpers on the wrapper types. And we can go reverse by using
`MethodHandle::explicitCastArguments` which does a cast conversion (which in case of primitives e.g. from `long` to
`int` will do a narrowing, which is exactly what you want).

So I think there's a way for the code to set up some kind of table, and then just pick the adapter MHs from there. I
think this will simplify the code quite a bit and remove the `j.l.i` dependency.


PR: https://git.openjdk.java.net/panama-foreign/pull/173

More information about the panama-dev mailing list