% Sketch of a multi-field feature for hyper-long types % John Rose, February 2022 Multi-field feature: - a JVM-internal annotation gives a replication count - extra copies of field injected by class loader - extra copies are *guaranteed contiguous* in memory layout - at most one multi-field per class - the class must be final (no inheritance of multi-fields) ```java value class Shape128 { @MultiField(2) long data; //long data#1 ⇐ allocated contiguously } value class Shape256 { @MultiField(4) long data; //long data#{1,2,3} ⇐ allocated contiguously } value class Shape512 { @MultiField(8) long data; //long data#{1,2,3,4,5,6,7} ⇐ allocated contiguously } ``` Annotation is internal and privileged in `jdk.internal.vm.annotation`, like `@ForceInline`, `@Stable`, etc. Only one type required per shape. For SVE, one type required for each possible size, to be dynamically selected based on SVE process configuration. Given the above restrictions, the JVM can always position the storage for a multi-field at the very end of its containing object. This is not logically necessary (nor are the restrictions) but it seems helpful to align the layout of an object with a multi-field with the layout of an array. When the optimizer does alias analysis on array objects, all access to offsets at or beyond the first array element are classified as a single indexed set, while smaller offsets point to the array's header fields (length, class, mark word). Likewise, an object with a multi-field would be accessed using either short offsets in the header (or perhaps ad hoc instance fields), or else starting at the first multi-field instance and after. Possible annotation to configure register allocation: ```java @RegisterAllocation(128) value class Shape128 { @MultiField(2) long data; … } @RegisterAllocation(256) value class Shape256 { @MultiField(4) long data; … } @RegisterAllocation(512) value class Shape512 { @MultiField(8) long data; … } ``` Var handle operates on a memory-resident instance. The optimizer has to clean up the messy copy chains. ```java value class Shape512 { @MultiField(8) long data; private static final VarHandle VH_data; static { Field F_data = Shape512.class.getField("data"); long OFF_data = U.objectFieldOffset(F_data); VH_data = new VarHandleMultiLongs(Shape512.class, OFF_data, 8); // addressing mode: VH_data(this, i) ⇒ getLong(this, OFF_data + i*8) } public long data(int i) { // spills ‘this’ to heap or stack in order to use Unsafe.getLong return (long) VH_data.get(this, i); } public Shape512 dataUpdate(int i, long x) { Shape512 pbuf = U.makePrivateBuffer(this); VH_data.set(pbuf, i, x); // ⇒ putLong(pbuf, OFF_data + i*8, x) return U.finishPrivateBuffer(pbuf); } } ``` Side path: Maybe we need a new var handle access mode, _update_ (or _with_), for changing one field of a value object, returning not `void` but rather the updated value. ```java public Shape512 dataUpdate(int i, long x) { return (Shape512) VH_data.‘update’(this, i, x); } ``` Other lane types can be stored on top of the basic `long` type, or can be given their own "native" multi-field types. ```java value class Shape512 { @MultiField(8) long data; private static final VarHandle VH_data; private static final VarHandle VH_dataAsFloats, VH_dataAsBytes, …; static { Field F_data = Shape512.class.getField("data"); long OFF_data = U.objectFieldOffset(F_data); VH_data = new VarHandleMultiLongs(Shape512.class, OFF_data, 8); // addressing mode: VH_data(this, i) ⇒ getLong(this, OFF_data + i*8) VH_dataAsFloats = new VarHandleMultiFloats(Shape512.class, OFF_data, 16); VH_dataAsBytes = new VarHandleMultiBytes(Shape512.class, OFF_data, 64); … } … public float dataAsFloat(int i) { return (float) VH_dataAsFloats.get(this, i); } } // or else, less preferably: value class Shape512Longs { @MultiField(8) long data; } value class Shape512Floats { @MultiField(16) float data; } value class Shape512Bytes { @MultiField(64) byte data; } ``` The more complex choice would (in principle) allow the optimizer to more readily track individual field values. This might possibly be useful, but in fact it seems less useful than for other value types. This is because, unlike regular value types, vectors are usually processed as bitwise memory images, either in memory or in the vector register file; they are _not_ usually found in scalar registers. Direct transcoding between vector and scalar registers uses insert/extract lane instructions, which are not much faster than to L1 memory loads and stores (3 cycles for single lane access vs. 4 and 5 cycles load-use and store-reload latencies quoted for [Haswell]). This suggests that data movement from vector registers directly to memory (whether stack or heap) is going to be at least as favorable as direct data movement between the vector unit and the scalar registers. [Haswell]: Synthetic multi-vector types are straightforward to define as well, merely by increasing the replication count by a multiple: ```java @RegisterAllocation(128) value class Shape128x2 { @MultiField(2*2) long data; } @RegisterAllocation(256) value class Shape256x3 { @MultiField(4*3) long data; } @RegisterAllocation(512) value class Shape512x4 { @MultiField(8*4) long data; } // Nested representation would be more complex to implement value class Shape256x5 { @MultiField(5) Shape256 nestedVector; // requires carefully contiguous layout of the nestedVectors } ``` None of the above addresses alignment; this is a difficult problem because the GC does not provide alignment services beyond a set maximum, such as 64 or 128 bits. Aligning 512-bit vectors would require special allocation paths (and reallocation/copying paths) in the GC. This seems worth thinking about, but not pushing on any time soon. Alignment constraints could be readily encoded as annotations: ```java value class Shape512 { @AlignField(512) @MultiField(8) long data; //long data#{1,2,3,4,5,6,7} ⇐ allocated contiguously and aligned } ``` Possible future directions for multi-fields: - Allow object and (multi-word) value types for multi-fields (not just `long`). - Nest a multi-field of vectors into a matrix-like structure. - Allow the field multiplicity value to depend on an instance field (for fused `Strings`, with a per-instance customized length). - Allow the field multiplicity value to depend on a species parameter (generically-sized vectors). - Allow more than one multi-field (e.g., a hybrid mix of blocks of `long` and `Object` multi-fields). - Allow multi-fields to be inherited (non-final objects).