zero-length segments

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Fri Jan 21 10:50:33 UTC 2022


Hi Michael,
I think I'm sympathetic with your argument. Not only most of the other 
factories, as you noticed, do allow for zero-length segments, but 
there's also the MemorySegment::mapFile method, which specifically 
returns an instance of a special subclass if the mapped size if zero.

I also did some more tests with the ByteBuffer API, which allows 
allocation (as you mention) with size = 0, but also allows slicing with 
slice size = 0 and limit of 0.

(Java arrays are another case where creating a zero-element array is 
indeed possible).

All this evidence point to the fact that, yes, saying no to zero-byte 
allocation on memory segment (of any kind) is at the very least 
problematic in terms of interop with existing APIs, as it will cause 
surprising behavior.

For this reason, I believe the best course of action is to enhance the 
API in the way you suggest, and accept zero-sized segments.

Thanks for the feedback!

Thinking of possible workarounds in the short term - but maybe you are 
already doing it - you could have something like this:

```
private static final MemorySegment EMPTY = 
MemorySegment.ofByteBuffer(ByteBuffer.allocateDirect(0));

MemorySegment wrapAddress(MemoryAddress address, long size, 
ResourceScope scope) {
     return size == 0 ? EMPTY : MemorySegment.ofAddress(address, size, 
scope);
}
```

In terms of performance, there are a couple of points to note:

* adding a singleton anonymous class for zero-sized segment might cause 
profile pollution when using the same callsite with empty and non-empty 
segments
* having a branch (like in the above code) so that a singleton is 
returned if size == 0 effectively disables escape analysis most of the 
times (when size is not known by C2)

And, other pseudo-random considerations:

* Looking at the impl of ByteBuffer.allocateDirect, it seems like it 
always allocates at least one byte:

```
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
```

You can see how this is suboptimal (and probably not what a programmer 
would expect).

* In the case of MemorySegment, returning a singleton is not really 
possible, because (as for mapped segments) the user is also providing a 
scope parameter, and it expects that the returned segment will have same 
scope as the provided parameter.

* All things considered, given it's late for 18, I'd prefer to address 
this in 19 - but I do want to address it.

Thanks
Maurizio

On 20/01/2022 22:50, Michael Zucchi wrote:
>
> Morning all,
>
> After a long break i've started experimenting with the foreign abi to 
> replace jni.  One tool i'm working on is a vulkan binding generator 
> that works directly from the xml specification and generates a 'nice' 
> api (particularly focusing on constructors for all the hundreds of 
> configuration structures needed for vulkan, plus the dynamic function 
> tables), and another tool generates high-level and potentially object 
> oriented api's from c header files, for this one I use a gcc plugin to 
> extract the structures and functions and i'm trying opencl and ffmpeg 
> as test cases (and to update some projects i maintain, zcl and 
> jjmpeg).  I'm not using jextract because I want to create the high 
> level api directly and provide much more control on the created 
> classes, and also because i've got the time and nothing better to do 
> with it.
>
> In general it looks pretty good after so much work, but i've come 
> across one oddity which adds complexity to the java code for no 
> obvious reason.  Is there any specific reason you cannot create zero 
> length memory segments *in some cases*?
>
> e.g. something like this comes up often in C:
>
> struct blob {
>    size_t data_size;
>    uint8_t *data;
> }
>
> From java at some point you want to get a MemorySegment to access 
> blob.data.  It might be through a high level api such as:
>
>   class blob {
>     MemorySegment getData() {
>         return MemorySegment.ofAddress(...);
>     }
>  }
>
> If the memory was allocated in the native code (quite common) this 
> obvious java-side implementation just isn't possible with the current 
> MemorySegment implementation as it will fail in an unexpected (and 
> imho unreasonable) way if size is 0.  You'd either need to wrap 
> MemorySegment in some other structure which hides this detail with 
> it's own special case code (seems redundant), return a null 
> (apparently evil these days, and kinda messy anyway), or expose the 
> detail by ensuring the callee checks size>0 before calling getData() 
> (yikes).
>
> One notes that it is inconsistent with the rest of the api:
>
> works: 
> segmentAllocator.allocate(MemoryLayout.structLayout().withBitAlignment(8))
> works:  segmentAllocator.allocateArray(JAVA_BYTE, 0);
> works: segmentAllocator.allocate(0);
> works:  segment.asSlice(offset, 0);
> works: MemorySegment.ofArray(new byte[0]);
> works: MemorySegment.ofByteBuffer(ByteBuffer.allocateDirect(0));
> (you get the idea ...)
>
> doesn't: 
> MemorySegment.allocateNative(MemoryLayout.structLayout().withBitAlignment(8), 
> scope);
> doesn't: MemorySegment.allocateNative(0, 1, scope);
> doesn't: MemorySegment.ofAddress(addr, 0, scope);
>
> With the last one being the only way to wrap sized-allocations from 
> native memory(?)  there seems to be no workaround possible.
>
> Both java and c specifically define zero-length allocations as valid 
> everywhere else because it simplifies a lot of code, and even the 
> foreign-abi does for every other case, so why not here too?
>
> Cheers,
>  Z


More information about the panama-dev mailing list