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