198 lines
4.7 KiB
Markdown
198 lines
4.7 KiB
Markdown
Mini Spec — Borrow/Mutate Views on a GC Heap (Prometeu)
|
|
|
|
This document defines an optional “Rust-flavored” access discipline for heap objects without using RC/HIP. It is compatible with a GC-based heap, first-class functions (closures), and deterministic coroutines.
|
|
|
|
Goals
|
|
|
|
Preserve the feel of borrow (shared read) and mutate (exclusive write).
|
|
|
|
Prevent common aliasing bugs in user code.
|
|
|
|
Keep the runtime simple and GC-friendly.
|
|
|
|
Avoid restrictions that would block closures/coroutines.
|
|
|
|
Non-goals
|
|
|
|
Not a full Rust borrow checker.
|
|
|
|
Not a memory management mechanism (GC owns lifetimes).
|
|
|
|
Not a general concurrency/locking system.
|
|
|
|
1. Concepts
|
|
1.1 Heap handle
|
|
|
|
A heap object is referenced by a handle. Handles can be stored, copied, captured, and returned freely (GC-managed).
|
|
|
|
1.2 Views
|
|
|
|
A view is a temporary capability derived from a handle:
|
|
|
|
BorrowView<T>: shared read-only view
|
|
|
|
MutView<T>: exclusive mutable view
|
|
|
|
Views are stack-only and lexically scoped.
|
|
|
|
2. Core Rules (Language Semantics)
|
|
2.1 Creating views
|
|
|
|
Views can be created only by dedicated constructs:
|
|
|
|
borrow(h) { ... } produces a BorrowView for the duration of the block
|
|
|
|
mutate(h) { ... } produces a MutView for the duration of the block
|
|
|
|
2.2 View locality
|
|
|
|
Views must never escape their lexical scope:
|
|
|
|
A view cannot be:
|
|
|
|
stored into heap objects
|
|
|
|
captured by closures
|
|
|
|
returned from functions
|
|
|
|
yielded across coroutine suspension points
|
|
|
|
stored into globals
|
|
|
|
(Think: “views are ephemeral; handles are persistent.”)
|
|
|
|
2.3 Access permissions
|
|
|
|
Within a view scope:
|
|
|
|
BorrowView permits reads only
|
|
|
|
MutView permits reads and writes
|
|
|
|
Outside a view scope:
|
|
|
|
direct field access is not permitted; you must open a view scope again.
|
|
|
|
2.4 Exclusivity
|
|
|
|
At any program point, for a given handle h:
|
|
|
|
Multiple BorrowView(h) may coexist.
|
|
|
|
Exactly one MutView(h) may exist, and it excludes all borrows.
|
|
|
|
This is enforced at compile time (required) and optionally at runtime in debug mode (see §5).
|
|
|
|
2.5 Re-entrancy
|
|
|
|
Within a mutate(h) scope:
|
|
|
|
nested borrow(h) is allowed (it is trivially compatible)
|
|
|
|
nested mutate(h) is disallowed
|
|
|
|
Within a borrow(h) scope:
|
|
|
|
nested mutate(h) is disallowed
|
|
|
|
3. Interaction with Closures and Coroutines
|
|
3.1 Closures
|
|
|
|
Closures may capture handles freely.
|
|
|
|
Closures must not capture views.
|
|
|
|
If code inside borrow/mutate creates a closure, it may capture the handle, but not the view.
|
|
|
|
3.2 Coroutines
|
|
|
|
Coroutines may store handles in their stacks/locals.
|
|
|
|
Views must not live across suspension:
|
|
|
|
A coroutine cannot yield while a view is active.
|
|
|
|
A coroutine cannot sleep while a view is active.
|
|
|
|
This rule guarantees that view lifetimes remain local and deterministic.
|
|
|
|
4. Compiler Requirements (Static Enforcement)
|
|
|
|
The compiler enforces this feature by tracking view lifetimes as lexical regions.
|
|
|
|
Minimum checks:
|
|
|
|
A view cannot be assigned to a variable with heap lifetime.
|
|
|
|
A view cannot appear in a closure capture set.
|
|
|
|
A view cannot be returned.
|
|
|
|
A view cannot be live at FRAME_SYNC, YIELD, SLEEP, or any safe-point boundary defined by the language.
|
|
|
|
Exclusivity rules per handle region (borrow vs mutate overlaps) must hold.
|
|
|
|
If a rule is violated: compile error (fatal).
|
|
|
|
5. Optional Runtime Checking (Debug Mode)
|
|
|
|
Runtime checking is optional and intended for debugging or untrusted bytecode scenarios.
|
|
|
|
5.1 Object header borrow-state
|
|
|
|
Each heap object may maintain:
|
|
|
|
readers: u16
|
|
|
|
writer: bool
|
|
|
|
Runtime actions:
|
|
|
|
entering borrow increments readers if no writer
|
|
|
|
leaving borrow decrements readers
|
|
|
|
entering mutate requires readers==0 and writer==false, then sets writer=true
|
|
|
|
leaving mutate sets writer=false
|
|
|
|
Violations trap with a specific diagnostic.
|
|
|
|
5.2 Build profiles
|
|
|
|
Release builds may omit this entirely (zero overhead).
|
|
|
|
Debug builds may enable it.
|
|
|
|
6. Lowering to Bytecode (Suggested)
|
|
|
|
This feature does not require dedicated opcodes in the minimal design.
|
|
|
|
Recommended lowering strategy:
|
|
|
|
borrow/mutate are compile-time constructs.
|
|
|
|
Field reads/writes lower to existing heap access opcodes (FIELD_GET/FIELD_SET or equivalent), but only permitted while a view is considered “active” by the compiler.
|
|
|
|
In debug-runtime-check mode, the compiler may insert explicit enter/exit markers (or syscalls) to enable runtime enforcement.
|
|
|
|
7. Rationale
|
|
|
|
GC removes the need for RC/HIP, but aliasing mistakes still exist. Views provide a clean and didactic discipline:
|
|
|
|
Handles: long-lived, capturable, GC-managed.
|
|
|
|
Views: short-lived, local, safe access windows.
|
|
|
|
This keeps the VM simple while enabling expressive language features (closures, coroutines) without lifetime headaches.
|
|
|
|
8. Summary
|
|
|
|
Borrow/Mutate are access disciplines, not memory management.
|
|
|
|
Views are stack-only and non-escapable.
|
|
|
|
Closures and coroutines freely use handles, but never views.
|
|
|
|
Enforcement is compile-time, with optional runtime checks in debug mode. |