List of intended memory-management policies (note: the actual policies are below, after both kinds of modifiers):
structure modifiers:
- relative - own address is added to make effective pointer. Useful for
realloc
andmemcpy
, as well as shared memory. - middle_pointer - actually points to the middle of an object, with a known way to find the start
- note particularly how this interacts with subclasses. Possibly that should be the only way to create such a pointer? It isn't too crazy to synthesize a subclass just for the CowString trick ...
- (other arithmetic tricks possible: base+scale+offset, with each of these possibly hard-coded (which requires that there be multiple copies of some ownership policies! we can't just use an enum) or possibly embedded)
- (but what about "object is stored in a file too large to mmap" and such? Or should those only satisfy "ChunkyRandomIterator" concept?)
policy modifiers:
- nomove
- is it useful to have an explicit "owned by foreign GC"? That would imply an additional "foreign GC root", but I was handling that as 'resources' currently ...
- singlethread (avoid atomic operations - this does win in benchmarks, though it's not insane to omit it if you elide useless changes)
- mostlyonethread (has an owner, others have to pay a toll to access)
- variant<policy1, policy2> e.g. "maybe shared, maybe borrowed"
-
unless_singleton - allow dereferencing the special object at the head of a linked list, but otherwise always follows the main policy.
(Note that the policy is usually "unique", although I suppose "shared" might make sense for graphs.)
-
policies:
- complex_shared (allocated as 2 objects, unless
make_shared
. Can do weird tricks with unrelated pointers if you want.)- weak
- simple_shared (uses a refcount, so forbids oddly-sliced objects)
- weak (distinct from complex_shared's weak)
- shared_exactly_twice - there are exactly two owners initially; others can only make weak pointers if anything. The two owners can only be moved, mostly like
unique
. - unique
- deep/forward (would overflow the stack destructing in order - e.g. linked lists, but also unbalanced trees; beware the case where it is a tree without uniform type!)
- I wrote some pseudocode for this
- weak (distinct from the shared kind of weak. It may be worth making multiple variants of
unique
based on how they support this. BEWARE SAFETY PROBLEMS, especially with threads - what can you do with theweak
pointer?)- temporarily_shared - causes unique's dtor to kill the process if any are still alive - some call this "constraint references"
- copy (avoiding slicing, but logically similar to
value
. This probably doesn't need a distinct type, only an optional trait - but CoW may change this)
- deep/forward (would overflow the stack destructing in order - e.g. linked lists, but also unbalanced trees; beware the case where it is a tree without uniform type!)
- circular (always applied to a single member of the same type as we are in - in multiply-linked lists, others are borrowed)
- borrowed (note overlap with unique's weak)
- parent/back (this allows easier checking, since we know someone will clean up after us)
- the parent object might have various unique-like ownership policies of the current object; deep and circular are notable. But even shared isn't completely nonsensical; consider a DAG with only a single "shortest" backpath.
- note that for non-list-like cases, the parent object often has indirect ownership, e.g. via a list of child widgets.
- remember to support the case where the parent isn't currently alive; the list-head exception might be useful
- borrowed-and-not-immediately-dying - solve the string().c_str() problem without a full Rust-style borrow checker
- parent/back (this allows easier checking, since we know someone will clean up after us)
- raw (definitely needed at least for implementing other pointers; see resources in other file)
- static
- zero-init, constant-init, reloc-init, code-init
- thread_local (requires allocation except in the main thread. May or may not be async-signal-safe, depending on TLS model)
- value (slicing)
- pool (not sure how this fits in; comparable to
complex_shared
)- if there is a pool of fixed-size objects, you can use "generation counter" to detect dead weak references that are dead.
Hey o11c! I saw this from your comment on HN. This part is particularly interesting to me:
Is this reference-counted at run-time? Or would it be compile-time, e.g. by requiring that both owning references be present and destroyed at the same time? I've been doing some thinking on the latter, and have some ideas there if you'd like. Cheers!