RFR: 7903649: Field and global variables of array type should have indexed accessors [v2]

Maurizio Cimadamore mcimadamore at openjdk.org
Wed Jan 31 18:15:14 UTC 2024


On Wed, 31 Jan 2024 18:06:34 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:

>> This PR adds support for indexed accessors for struct fields and global variables whose type is an array type. These accessors feature a number of `long` access coordinates which has the same cardinality as that of the underlying array type.
>> 
>> For instance, consider the following global variable declaration:
>> 
>> 
>> int ints[2][3][4];
>> 
>> 
>> For this, jextract now emits:
>> 
>> 
>>     private static SequenceLayout ints$LAYOUT() {
>>         class Holder {
>>             static final SequenceLayout LAYOUT = MemoryLayout.sequenceLayout(2, MemoryLayout.sequenceLayout(3, MemoryLayout.sequenceLayout(4, foo_h.C_INT)));
>>         }
>>         return Holder.LAYOUT;
>>     }
>> 
>>     private static MemorySegment ints$SEGMENT() {
>>         class Holder {
>>             static final MemorySegment SEGMENT = foo_h.findOrThrow("ints")
>>                 .reinterpret(ints$LAYOUT().byteSize());
>>         }
>>         return Holder.SEGMENT;
>>     }
>> 
>>     /**
>>      * Getter for variable:
>>      * {@snippet lang=c :
>>      * int ints[2][3][4]
>>      * }
>>      */
>>     public static MemorySegment ints() {
>>         return ints$SEGMENT();
>>     }
>> 
>>     /**
>>      * Setter for variable:
>>      * {@snippet lang=c :
>>      * int ints[2][3][4]
>>      * }
>>      */
>>     public static void ints(MemorySegment varValue) {
>>         MemorySegment.copy(varValue, 0L, ints$SEGMENT(), 0L, ints$LAYOUT().byteSize());
>>     }
>> 
>>     private static VarHandle ints$ELEM_HANDLE() {
>>         class Holder {
>>             static final VarHandle HANDLE = ints$LAYOUT().varHandle(sequenceElement(), sequenceElement(), sequenceElement());
>>         }
>>         return Holder.HANDLE;
>>     }
>> 
>>     /**
>>      * Indexed getter for variable:
>>      * {@snippet lang=c :
>>      * int ints[2][3][4]
>>      * }
>>      */
>>     public static int ints(long index0, long index1, long index2) {
>>         return (int)ints$ELEM_HANDLE().get(ints$SEGMENT(), 0L, index0, index1, index2);
>>     }
>> 
>>     /**
>>      * Indexed setter for variable:
>>      * {@snippet lang=c :
>>      * int ints[2][3][4]
>>      * }
>>      */
>>     public static void ints(long index0, long index1, long index2, int varValue) {
>>         ints$ELEM_HANDLE().set(ints$SEGMENT(), 0L, index0, index1, index2, varValue);
>>     }
>> 
>> 
>> If the array element type is a struct, different code needs to be generated. Consider this global variable declaration:
>> 
>> 
>> struct Point { int x; int y; } points[2][3][4];
>> 
>> 
>> This generates the followi...
>
> Maurizio Cimadamore has updated the pull request incrementally with five additional commits since the last revision:
> 
>  - Add support for global variabls and struct fields
>    Fix tests
>  - Tweak header file class to expose constant accessors
>  - Expose constants in structs
>  - Expose function wrappers constant holders
>  - Add public holder class for globals

I've uploaded a new iteration. The main idea is to just add some additional accessors for every function, global variable and struct field. These accessors contain make use of the `$` sign, and provide access to constants that might be useful from the generated code. E.g. `foo$descriptor` is an accessor that returns the function descriptor for `foo`. This way we don't need to introduce additional classes in the generated API. Here are some numbers on windows.h with this new approach:

Size of generated sources
vanilla: 57M
holder: 68M

Size of generated classes
vanilla: 85M
holder: 87M

Number of compiled classes:
vanilla: 20562
holder: 19671

So, the size in generated sources goes up (because of the extra accessors and javadoc). The size of generated classes stays roughly the same, and the number of generated classes actually goes down (as this new strategy is a bit smarter, in that it uses a single holder class per global variable, instead of two as before).

I'm a bit on the fence as to whether we should keep constant accessors on by default, or whether we should provide a flag to enable/disable constant accessors. Any thoughts?

For the records, here's what we generate for the usual `Point` class in the new strategy:


/**
 * {@snippet lang=c :
 * struct Point {
 *     int x;
 *     int y;
 * }
 * }
 */
public class Point {

    Point() {
        // Should not be called directly
    }

    private static final GroupLayout $LAYOUT = MemoryLayout.structLayout(
        foo_h.C_INT.withName("x"),
        foo_h.C_INT.withName("y")
    ).withName("Point");

    /**
     * The layout of this struct
     */
    public static final GroupLayout layout() {
        return $LAYOUT;
    }

    private static final OfInt x$LAYOUT = (OfInt)$LAYOUT.select(groupElement("x"));

    /**
     * Layout for field:
     * {@snippet lang=c :
     * int x
     * }
     */
    public static final OfInt x$layout() {
        return x$LAYOUT;
    }

    private static final long x$OFFSET = 0;

    /**
     * Offset for field:
     * {@snippet lang=c :
     * int x
     * }
     */
    public static final long x$offset() {
        return x$OFFSET;
    }

    /**
     * Getter for field:
     * {@snippet lang=c :
     * int x
     * }
     */
    public static int x(MemorySegment struct) {
        return struct.get(x$LAYOUT, x$OFFSET);
    }

    /**
     * Setter for field:
     * {@snippet lang=c :
     * int x
     * }
     */
    public static void x(MemorySegment struct, int fieldValue) {
        struct.set(x$LAYOUT, x$OFFSET, fieldValue);
    }

    private static final OfInt y$LAYOUT = (OfInt)$LAYOUT.select(groupElement("y"));

    /**
     * Layout for field:
     * {@snippet lang=c :
     * int y
     * }
     */
    public static final OfInt y$layout() {
        return y$LAYOUT;
    }

    private static final long y$OFFSET = 4;

    /**
     * Offset for field:
     * {@snippet lang=c :
     * int y
     * }
     */
    public static final long y$offset() {
        return y$OFFSET;
    }

    /**
     * Getter for field:
     * {@snippet lang=c :
     * int y
     * }
     */
    public static int y(MemorySegment struct) {
        return struct.get(y$LAYOUT, y$OFFSET);
    }

    /**
     * Setter for field:
     * {@snippet lang=c :
     * int y
     * }
     */
    public static void y(MemorySegment struct, int fieldValue) {
        struct.set(y$LAYOUT, y$OFFSET, fieldValue);
    }

    /**
     * Obtains a slice of {@code arrayParam} which selects the array element at {@code index}.
     * The returned segment has address {@code arrayParam.address() + index * layout().byteSize()}
     */
    public static MemorySegment asSlice(MemorySegment array, long index) {
        return array.asSlice(layout().byteSize() * index);
    }

    /**
     * The size (in bytes) of this struct
     */
    public static long sizeof() { return layout().byteSize(); }

    /**
     * Allocate a segment of size {@code layout().byteSize()} using {@code allocator}
     */
    public static MemorySegment allocate(SegmentAllocator allocator) {
        return allocator.allocate(layout());
    }

    /**
     * Allocate an array of size {@code elementCount} using {@code allocator}.
     * The returned segment has size {@code elementCount * layout().byteSize()}.
     */
    public static MemorySegment allocateArray(long elementCount, SegmentAllocator allocator) {
        return allocator.allocate(MemoryLayout.sequenceLayout(elementCount, layout()));
    }

    /**
     * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction) (if any).
     * The returned segment has size {@code layout().byteSize()}
     */
    public static MemorySegment reinterpret(MemorySegment addr, Arena arena, Consumer<MemorySegment> cleanup) {
        return reinterpret(addr, 1, arena, cleanup);
    }

    /**
     * Reinterprets {@code addr} using target {@code arena} and {@code cleanupAction) (if any).
     * The returned segment has size {@code elementCount * layout().byteSize()}
     */
    public static MemorySegment reinterpret(MemorySegment addr, long elementCount, Arena arena, Consumer<MemorySegment> cleanup) {
        return addr.reinterpret(layout().byteSize() * elementCount, arena, cleanup);
    }
}

-------------

PR Comment: https://git.openjdk.org/jextract/pull/198#issuecomment-1919644092
PR Comment: https://git.openjdk.org/jextract/pull/198#issuecomment-1919646767


More information about the jextract-dev mailing list