dissecting the Scope API

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Sat Dec 22 00:18:01 UTC 2018


Hi,
as mentioned in a recent email, I think the Scope API is conflating 
several aspects together, which makes it difficult to discuss it and/or 
evolve it into a more stable API. This email is an attempt at teasing 
apart the various aspects of the Scope API; while it's not meant to be a 
full proposal, I think there are many ideas in here worthy of more 
consideration. Much of the credits for the ideas in here go to Steve 
Dohrmann from Intel  and Jorn Vernee who recently started a discussion 
on this [1]; Henry Jen also provided some insights into the dual nature 
played by the Scope API.

So, before jumping into some pseudocode, let's review some basic key points:

* I believe Scope is condensing two aspects: it has an allocation 
interface (Scope::allocateXYZ), but it also serves as a life-cycle 
management. While in the end we might (or not) end up to conflate the 
two, let's try to keep them separated for now

* Let's try to get there in layers (as we did for the SystemABI); there 
are clearly low level allocators - e.g. things that just give you a slab 
of memory - called memory regions - and high-level allocators, e.g. 
things that can allocate user defined data (structs, arrays, etc.)

* We'd like the approach to scale, if possible, to different kind of 
memories (heap, offheap, NVM, ...)

* Let's double down on the Pointer abstraction to do the memory 
dereference; e.g. let's not add methods to a memory abstraction a la 
Unsafe (get/putLong, ...) instead, let's have a way to get a Pointer out 
of a memory region

So, let's start... the first thing we need is something that creates 
memory regions:

interface MemoryStore {
    MemoryRegion allocate(long size);
    MemoryRegion allocate(Access access, long size);
    void free(MemoryRegion region);

    interface Access {
       READ, WRITE, READ_WRITE;
    }

    static MemoryStore offheapStore() { ... }
    static MemoryStore heapStore() { ... }
    ... //others?
}

So far so good, we can create regions, with given access etc. Note that 
here I'm trying to hide the addressing model used by Unsafe (Object + 
long offset); after all, the addressing model depends on the kind of 
memory you are operating on, so I don't think it's good to expose it via 
the API (if we can avoid doing so).

What is a memory region? Here:


interface MemoryRegion {
    MemoryStore store();
    Pointer<?> basePointer();
    //maybe ByteBuffer accessor too?
    boolean checkAlive();
    boolean checkAccess(Memory.Access access);
    long size();
}

As Jorn (and separately, Steve) suggested, this could be the place where 
we add the logic for liveness check - more specifically, a memory region 
is considered alive unless freed on its corresponding store. 
Interestingly, a memory region has a way to retrieve a pointer to its 
base location. We can assume it will be a void pointer and that the 
client will have to cast accordingly; this is, after all, a low level 
API. This is also a very general API - it's not really specific to 
Panama - other than the fact that a memory region can be projected to a 
Pointer - but we can imagine other useful projections too (e.g. 
Bytebuffers).

What about high-level allocation? This is where Panama-related concepts 
start to kick in:


abstract class Allocator {
    Allocator(MemoryStore store);

    public <Z> Pointer<Z> allocate(LayoutType<Z> type);
    public <Z extends Struct<Z>> allocateStruct(Class<Z> strClass);
    ... //other allocation factories
}

So, an allocator takes a store, and then implements the various 
allocation functions; it is easy to see how such allocation functions 
can be implemented: they will typically obtain a region (using the 
store) of a given size (given by some LayoutType), then obtain a pointer 
from the region and cast it to the desired type.

What about scope-ness? Let's start from here:

abstract class ScopedAllocator extends Allocator implements AutoCloseable {
     ScopedAllocator(MemoryStore store);
    public void close() //this makes sure that all regions created 
within this 'scope' are automatically freed
}

So, this is a special allocator that also supports the AutoCloseable 
interface, and can therefore be used with a try-with-resources, in the 
usual way. In other words, clients will often do things like:

try (Scope sc = new ScopedAllocator(MemoryStore.offheapStore())) {
     sc.allocateStruct(...)
}

You can think of this as an allocator that keeps track of the regions it 
creates, and frees them as soon as you call close().

I think this looks more or less equivalent to what we have now, but we 
have captured and broken down the primitive concepts more clearly now. 
There's a *store* that is responsible for allocating regions (all 
relatively Panama-agnostic), and high level allocators, built on top of 
stores, which provide the user facing allocation facilities. Scoped 
allocators keep track of memory region usages, to allow for automatic 
collection upon closing.

One remarkable thing is that Scope/Resource have disappeared from this 
API - the features available in the current Scope interface have been 
split between memory regions and allocators. I think there could be ways 
to add back explicit scopes into this flavor of the API - but I think 
that, before we jump to that, it's better to stop and ask - what are the 
use cases that would not be covered by this proposal?

Maurizio

[1] - 
http://mail.openjdk.java.net/pipermail/panama-dev/2018-December/003572.html




More information about the panama-dev mailing list