javaproject-panama

Java FFM - Obtaining a var handle to an array field of a structure


I'm using FFM from first principles to access native structures, such as:

VkLayerProperties {
    char layerName[256];
    uint32_t specVersion;
    uint32_t implementationVersion;
    char description[256];
};

The following trivial code extracts the fields from a given off-heap memory address:

public void load(MemorySegment address) {
    var layout = MemoryLayout.structLayout(
        MemoryLayout.sequenceLayout(256, JAVA_BYTE).withName("layerName"),
        JAVA_INT.withName("specVersion"),
        JAVA_INT.withName("implementationVersion"),
        MemoryLayout.sequenceLayout(256, JAVA_BYTE).withName("description")
    );

    VarHandle handle = layout.varHandle(PathElement.groupElement("specVersion"));
    int specVersion = (int) handle.get(address, 0L);
    ....
}

This works fine for the primitive fields (and also for reference types). But how can the same pattern using the layout and path elements be used to create var handles to the array fields, i.e:

VarHandle layerName = layout.varHandle(...);   // <-- magic here please
byte[] layerName = (byte[]) handle.get(address, 0L);

I have tried every combination of the various element classes, tried the helpers in MethodHandles, etc. all without any luck.

Either the handle fails with a "not a value layout" error or the get fails (presumably) because the coordinates are wrong. I suspect I'm just being more than normally dumb.

However, if one looks at the equivalent code generated using jextract for this structure, lo and behold, it essentially doesn't use a var handle but just hard-codes the byte offsets and slices the memory:

public class VkLayerProperties {
    public static MemorySegment layerName$slice(MemorySegment seg) {
        return seg.asSlice(0, 256);
    }
}

Whereas all the non-array fields use handles to access the fields directly:

static final VarHandle const$1 = constants$53.const$0.varHandle(MemoryLayout.PathElement.groupElement("specVersion"));

public static int specVersion$get(MemorySegment seg) {
    return (int)constants$53.const$1.get(seg);
}

Obviously I could just use the same approach and calculate the byte offsets and sizes from the layout (or even just hard code them), but it seems like the using the path element framework was intended to be the preferred solution.

So:

  1. Is there a way to access array fields by deriving a var handle from structures memory layout?

  2. Why does the equivalent jextract circumvent the general approach and use a memory slice for these cases.

Are these two questions connected?

Note that "use jextract" is not an answer.

Thanks in advance for any suggestions or solutions.


Solution

  • Is there a way to access array fields by deriving a var handle from structures memory layout?

    No. This does not exist in the current FFM API.

    VarHandles have all kinds of atomic access modes, such as compareAndExchange, that are not possible to implement for values larger than 8 bytes, due to hardware limitations.

    Additionally, while you want a byte[] copy to be returned, another user might want a long[] (for improved alignment), or a MemorySegment (reference or copy), or to be able to provide a pre-allocated array instead. There is no one obvious choice here.

    So, working out the details is left up to the user. Instead, as jextract does, you have to create a slice of the memory you want to access, and then if you want that to be copied into a newly-allocated byte[], you can call toArray(ValueLayout.JAVA_BYTE) on the resulting slice.

    There are several ways to create a slice. As you've found out, jextract pre-computes the offset of the slice and calls asSlice with that offset. Alternatively, you can use MemoryLayout::byteOffset to derive the offset from a memory layout, then call asSlice with the resulting offset. Or, as Rob Spoor mentioned in the comments, use MemoryLayout::sliceHandle to create a method handle that will return a slice for the particular field.