prometeu-runtime/files/Borrow Mutate - Compiler GC.md
2026-03-24 13:40:35 +00:00

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.