clean up
This commit is contained in:
parent
f75c61004c
commit
63631578e5
198
files/Borrow Mutate - Compiler GC.md
Normal file
198
files/Borrow Mutate - Compiler GC.md
Normal file
@ -0,0 +1,198 @@
|
||||
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.
|
||||
@ -1,52 +0,0 @@
|
||||
# Phase 03 – Rigid Frontend API & PBS Leak Containment (Junie PR Templates)
|
||||
|
||||
> Goal: **finish Phase 03 with JVM-like discipline** by making the **Backend (BE) the source of truth** and forcing the PBS Frontend (FE) to implement a **strict, minimal, canonical** contract (`frontend-api`).
|
||||
>
|
||||
> Strategy: **surgical PRs** that (1) stop PBS types from leaking, (2) replace stringy protocols with canonical models, and (3) make imports/exports/overloads deterministic across deps.
|
||||
|
||||
# Notes / Operating Rules (for Junie)
|
||||
|
||||
1. **BE is the source of truth**: `frontend-api` defines canonical models; FE conforms.
|
||||
2. **No string protocols** across layers. Strings may exist only as *display/debug*.
|
||||
3. **No FE implementation imports from other FE implementations**.
|
||||
4. **No BE imports PBS modules** (hard boundary).
|
||||
5. **Overload resolution is signature-based** (arity alone is not valid).
|
||||
|
||||
---
|
||||
|
||||
## PR-03.07 — Phase 03 cleanup: remove legacy compatibility branches and document boundary
|
||||
|
||||
### Title
|
||||
|
||||
Remove legacy string protocol branches and document FE/BE boundary rules
|
||||
|
||||
### Briefing / Context
|
||||
|
||||
After canonical models are in place, we must delete compatibility code paths (`alias/module`, `svc:` prefixes, prefix matching, etc.) to prevent regressions.
|
||||
|
||||
### Target
|
||||
|
||||
* No legacy synthetic module path support.
|
||||
* No string prefix matching for overloads.
|
||||
* Documentation: “BE owns the contract; FE implements it.”
|
||||
|
||||
### Scope
|
||||
|
||||
* Delete dead code.
|
||||
* Add `docs/phase-03-frontend-api.md` (or in-crate docs) summarizing invariants.
|
||||
* Add CI/lints to prevent BE from importing PBS modules.
|
||||
|
||||
### Checklist
|
||||
|
||||
* [ ] Remove legacy branches.
|
||||
* [ ] Add boundary docs.
|
||||
* [ ] Add lint/CI guard.
|
||||
|
||||
### Tests
|
||||
|
||||
* Full workspace tests.
|
||||
* Golden tests.
|
||||
|
||||
### Risk
|
||||
|
||||
Low/Medium. Mostly deletion + docs, but could expose hidden dependencies.
|
||||
@ -1,179 +0,0 @@
|
||||
# Prometeu Industrial-Grade Refactor Plan (JVM-like)
|
||||
|
||||
**Language policy:** All implementation notes, code comments, commit messages, PR descriptions, and review discussion **must be in English**.
|
||||
|
||||
**Reset policy:** This is a **hard reset**. We do **not** keep compatibility with the legacy bytecode/linker/verifier behaviors. No heuristics, no “temporary support”, no string hacks.
|
||||
|
||||
**North Star:** A JVM-like philosophy:
|
||||
|
||||
* Control-flow is **method-local** and **canonical**.
|
||||
* The linker resolves **symbols** and **tables**, not intra-function branches.
|
||||
* A **single canonical layout/decoder/spec** is used across compiler/linker/verifier/VM.
|
||||
* Any invalid program fails with clear diagnostics, not panics.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — JVM-like Symbol Identity: Signature-based Overload & Constant-Pool Mindset
|
||||
|
||||
### PR-09 (3 pts) — Overload resolution rules (explicit, deterministic)
|
||||
|
||||
**Briefing**
|
||||
|
||||
Once overload exists, resolution rules must be explicit.
|
||||
|
||||
**Target**
|
||||
|
||||
Implement a deterministic overload resolver based on exact type match (no implicit hacks).
|
||||
|
||||
**Scope**
|
||||
|
||||
* Exact-match resolution only (initially).
|
||||
* Clear diagnostic when ambiguous or missing.
|
||||
|
||||
**Requirements Checklist**
|
||||
|
||||
* [ ] No best-effort fallback.
|
||||
|
||||
**Completion Tests**
|
||||
|
||||
* [ ] Ambiguous call produces a clear diagnostic.
|
||||
* [ ] Missing overload produces a clear diagnostic.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Eliminate Stringly-Typed Protocols & Debug Hacks
|
||||
|
||||
### PR-10 (5 pts) — Replace `origin: Option<String>` and all string protocols with structured enums
|
||||
|
||||
**Briefing**
|
||||
|
||||
String prefixes like `svc:` and `@dep:` are fragile and non-industrial.
|
||||
|
||||
**Target**
|
||||
|
||||
All origins and external references become typed data.
|
||||
|
||||
**Scope**
|
||||
|
||||
* Replace string origins with enums.
|
||||
* Update lowering/collector/output accordingly.
|
||||
|
||||
**Requirements Checklist**
|
||||
|
||||
* [ ] No `.starts_with('@')`, `split(':')` protocols.
|
||||
|
||||
**Completion Tests**
|
||||
|
||||
* [ ] Grep-based test/lint step fails if forbidden patterns exist.
|
||||
|
||||
---
|
||||
|
||||
### PR-11 (5 pts) — DebugInfo V1: structured function metadata (no `name@offset+len`)
|
||||
|
||||
**Briefing**
|
||||
|
||||
Encoding debug metadata in strings is unacceptable.
|
||||
|
||||
**Target**
|
||||
|
||||
Introduce a structured debug info format that stores offset/len as fields.
|
||||
|
||||
**Scope**
|
||||
|
||||
* Add `DebugFunctionInfo { func_idx, name, code_offset, code_len }`.
|
||||
* Remove all parsing of `@offset+len`.
|
||||
* Update orchestrator/linker/emit to use structured debug info.
|
||||
|
||||
**Requirements Checklist**
|
||||
|
||||
* [ ] No code emits or parses `@offset+len`.
|
||||
|
||||
**Completion Tests**
|
||||
|
||||
* [ ] A test that fails if any debug name contains `@` pattern.
|
||||
* [ ] Debug info roundtrip test.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Hardening: Diagnostics, Error Handling, and Regression Shields
|
||||
|
||||
### PR-12 (3 pts) — Replace panics in critical build pipeline with typed errors + diagnostics
|
||||
|
||||
**Briefing**
|
||||
|
||||
`unwrap/expect` in compiler/linker transforms user errors into crashes.
|
||||
|
||||
**Target**
|
||||
|
||||
Introduce typed errors and surface diagnostics.
|
||||
|
||||
**Scope**
|
||||
|
||||
* Replace unwraps in:
|
||||
|
||||
* symbol resolution
|
||||
* import/export linking
|
||||
* entrypoint selection
|
||||
* Ensure clean error return with context.
|
||||
|
||||
**Requirements Checklist**
|
||||
|
||||
* [ ] No panic paths for invalid user programs.
|
||||
|
||||
**Completion Tests**
|
||||
|
||||
* [ ] Invalid program produces diagnostics, not panic.
|
||||
|
||||
---
|
||||
|
||||
### PR-13 (3 pts) — Add regression test suite: link-order invariance + opcode-change immunity
|
||||
|
||||
**Briefing**
|
||||
|
||||
We need a system immune to opcode churn.
|
||||
|
||||
**Target**
|
||||
|
||||
Add tests that fail if:
|
||||
|
||||
* linker steps bytes manually
|
||||
* decoder/spec drift exists
|
||||
* link order changes semantics
|
||||
|
||||
**Scope**
|
||||
|
||||
* Link-order invariance tests.
|
||||
* Spec coverage tests.
|
||||
* Optional: lightweight “forbidden patterns” tests.
|
||||
|
||||
**Requirements Checklist**
|
||||
|
||||
* [ ] Changing an opcode immediate size requires updating only the spec and tests.
|
||||
|
||||
**Completion Tests**
|
||||
|
||||
* [ ] All new regression tests pass.
|
||||
|
||||
---
|
||||
|
||||
## Summary of Estimated Cost (Points)
|
||||
|
||||
* Phase 1: PR-01 (3) + PR-02 (5) + PR-03 (3) = **11**
|
||||
* Phase 2: PR-04 (5) + PR-05 (3) + PR-06 (3) = **11**
|
||||
* Phase 3: PR-07 (5) + PR-08 (5) + PR-09 (3) = **13**
|
||||
* Phase 4: PR-10 (5) + PR-11 (5) = **10**
|
||||
* Phase 5: PR-12 (3) + PR-13 (3) = **6**
|
||||
|
||||
**Total: 51 points**
|
||||
|
||||
> Note: If any PR starts to exceed 5 points in practice, it must be split into smaller PRs.
|
||||
|
||||
---
|
||||
|
||||
## Non-Negotiables
|
||||
|
||||
* No compatibility with legacy encodings.
|
||||
* No heuristics.
|
||||
* No string hacks.
|
||||
* One canonical decoder/spec/layout.
|
||||
* Everything in English (including review comments).
|
||||
@ -1,341 +0,0 @@
|
||||
# Prometeu Base Script (PBS)
|
||||
|
||||
> **A didactic scripting language for game development with explicit cost, explicit memory, and predictable runtime.**
|
||||
|
||||
---
|
||||
|
||||
## 0. What PBS Is (and What It Is Not)
|
||||
|
||||
PBS (Prometeu Base Script) is a **small, explicit scripting language designed for game engines and real‑time runtimes**.
|
||||
|
||||
Its core goals are:
|
||||
|
||||
* **Didactic clarity** — the language teaches how memory, data, and APIs really work.
|
||||
* **Game‑friendly execution** — predictable runtime, no hidden allocation, no tracing GC.
|
||||
* **Explicit cost model** — you always know *when* you allocate, *where* data lives, and *who* can mutate it.
|
||||
|
||||
PBS is **not**:
|
||||
|
||||
* a general‑purpose application language
|
||||
* a productivity scripting language like Python or Lua
|
||||
* a language that hides runtime cost
|
||||
|
||||
PBS is intentionally opinionated. It is built for developers who want **control, predictability, and understanding**, especially in **game and engine contexts**.
|
||||
|
||||
---
|
||||
|
||||
## 1. The Core Philosophy
|
||||
|
||||
PBS is built around one rule:
|
||||
|
||||
> **If you do not allocate, you are safe. If you allocate, you are responsible.**
|
||||
|
||||
From this rule, everything else follows.
|
||||
|
||||
### 1.1 Two Worlds
|
||||
|
||||
PBS has **two explicit memory worlds**:
|
||||
|
||||
| World | Purpose | Properties |
|
||||
| ----- | -------------- | ---------------------------------------------- |
|
||||
| SAFE | Stack / values | No aliasing, no leaks, no shared mutation |
|
||||
| HIP | Storage / heap | Explicit aliasing, shared mutation, refcounted |
|
||||
|
||||
You never cross between these worlds implicitly.
|
||||
|
||||
---
|
||||
|
||||
## 2. First Contact: SAFE‑Only PBS
|
||||
|
||||
A PBS program that never allocates lives entirely in the SAFE world.
|
||||
|
||||
SAFE code:
|
||||
|
||||
* uses value semantics
|
||||
* copies data on assignment (conceptually)
|
||||
* cannot leak memory
|
||||
* cannot accidentally share mutable state
|
||||
|
||||
This makes SAFE PBS ideal for:
|
||||
|
||||
* gameplay logic
|
||||
* math and simulation
|
||||
* AI logic
|
||||
* scripting without fear
|
||||
|
||||
Example:
|
||||
|
||||
```pbs
|
||||
let a = Vector(1, 2);
|
||||
let b = a; // conceptual copy
|
||||
|
||||
b.scale(2);
|
||||
// a is unchanged
|
||||
```
|
||||
|
||||
No pointers. No references. No surprises.
|
||||
|
||||
---
|
||||
|
||||
## 3. Values, Not Objects
|
||||
|
||||
PBS does not have objects with identity by default.
|
||||
|
||||
### 3.1 Value Types
|
||||
|
||||
All basic types are **values**:
|
||||
|
||||
* numbers
|
||||
* bool
|
||||
* string (immutable)
|
||||
* tuples
|
||||
* user‑defined `struct`
|
||||
|
||||
A value:
|
||||
|
||||
* has no identity
|
||||
* has no lifetime beyond its scope
|
||||
* is safe to copy
|
||||
|
||||
This matches how most gameplay data *should* behave.
|
||||
|
||||
---
|
||||
|
||||
## 4. Structs (Data First)
|
||||
|
||||
Structs are **pure data models**.
|
||||
|
||||
```pbs
|
||||
declare struct Vector(x: float, y: float)
|
||||
{
|
||||
pub fn len(self: this): float { ... }
|
||||
pub fn scale(self: mut this, s: float): void { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* fields are private
|
||||
* mutation is explicit (`mut this`)
|
||||
* no inheritance
|
||||
* no identity
|
||||
|
||||
Structs are designed to be:
|
||||
|
||||
* cache‑friendly
|
||||
* predictable
|
||||
* easy to reason about
|
||||
|
||||
---
|
||||
|
||||
## 5. Mutability Is a Property of Bindings
|
||||
|
||||
In PBS, **types are never mutable**. Bindings are.
|
||||
|
||||
```pbs
|
||||
let v = Vector.ZERO;
|
||||
v.scale(); // ERROR
|
||||
|
||||
let w = mut Vector.ZERO;
|
||||
w.scale(); // OK
|
||||
```
|
||||
|
||||
This single rule eliminates entire classes of bugs.
|
||||
|
||||
---
|
||||
|
||||
## 6. When You Need Power: Allocation (HIP World)
|
||||
|
||||
Allocation is **explicit**.
|
||||
|
||||
```pbs
|
||||
let enemies = alloc list<Enemy>();
|
||||
```
|
||||
|
||||
Once you allocate:
|
||||
|
||||
* data lives in Storage (heap)
|
||||
* access happens through **gates** (handles)
|
||||
* aliasing is real and visible
|
||||
|
||||
This is intentional and explicit.
|
||||
|
||||
---
|
||||
|
||||
## 7. Gates (Handles, Not Pointers)
|
||||
|
||||
A gate is a small value that refers to heap storage.
|
||||
|
||||
Properties:
|
||||
|
||||
* cheap to copy
|
||||
* may alias shared data
|
||||
* managed by reference counting
|
||||
|
||||
### 7.1 Strong vs Weak Gates
|
||||
|
||||
* **Strong gate** — keeps data alive
|
||||
* **Weak gate** — observes without ownership
|
||||
|
||||
```pbs
|
||||
let a = alloc Node;
|
||||
let w: weak<Node> = a as weak;
|
||||
let maybeA = w as strong;
|
||||
```
|
||||
|
||||
This model mirrors real engine constraints without hiding them.
|
||||
|
||||
---
|
||||
|
||||
## 8. Controlled Access to Storage
|
||||
|
||||
Heap data is never accessed directly.
|
||||
|
||||
You must choose *how*:
|
||||
|
||||
* `peek` — copy to SAFE
|
||||
* `borrow` — temporary read‑only access
|
||||
* `mutate` — temporary mutable access
|
||||
|
||||
```pbs
|
||||
mutate enemies as e
|
||||
{
|
||||
e.push(newEnemy);
|
||||
}
|
||||
```
|
||||
|
||||
This keeps mutation visible and scoped.
|
||||
|
||||
---
|
||||
|
||||
## 9. Errors Are Values
|
||||
|
||||
PBS has no exceptions.
|
||||
|
||||
### 9.1 `optional<T>`
|
||||
|
||||
Used when absence is normal.
|
||||
|
||||
```pbs
|
||||
let x = maybeValue else 0;
|
||||
```
|
||||
|
||||
### 9.2 `result<T, E>`
|
||||
|
||||
Used when failure matters.
|
||||
|
||||
```pbs
|
||||
let v = loadTexture(path)?;
|
||||
```
|
||||
|
||||
Error propagation is explicit and typed.
|
||||
|
||||
---
|
||||
|
||||
## 10. Control Flow Without Surprises
|
||||
|
||||
PBS favors explicit flow:
|
||||
|
||||
* `if` — control only
|
||||
* `when` — expression
|
||||
* `for` — bounded loops only
|
||||
|
||||
```pbs
|
||||
for i in [0b..count] {
|
||||
update(i);
|
||||
}
|
||||
```
|
||||
|
||||
No unbounded iteration by accident.
|
||||
|
||||
---
|
||||
|
||||
## 11. Services: Explicit API Boundaries
|
||||
|
||||
A `service` is how behavior crosses module boundaries.
|
||||
|
||||
```pbs
|
||||
pub service Audio
|
||||
{
|
||||
fn play(sound: Sound): void;
|
||||
}
|
||||
```
|
||||
|
||||
Services are:
|
||||
|
||||
* explicit
|
||||
* statically checked
|
||||
* singleton‑like
|
||||
|
||||
They map naturally to engine subsystems.
|
||||
|
||||
---
|
||||
|
||||
## 12. Contracts: Static Guarantees
|
||||
|
||||
Contracts define **what exists**, not how.
|
||||
|
||||
```pbs
|
||||
pub declare contract Gfx host
|
||||
{
|
||||
fn drawText(x: int, y: int, msg: string): void;
|
||||
}
|
||||
```
|
||||
|
||||
Contracts:
|
||||
|
||||
* have no runtime cost
|
||||
* are validated at compile time
|
||||
* define engine ↔ script boundaries
|
||||
|
||||
---
|
||||
|
||||
## 13. Modules (Simple and Predictable)
|
||||
|
||||
* one directory = one module
|
||||
* only `pub` symbols cross modules
|
||||
* no side effects on import
|
||||
|
||||
This keeps build and runtime deterministic.
|
||||
|
||||
---
|
||||
|
||||
## 14. Why PBS Works for Games
|
||||
|
||||
PBS is designed around real engine needs:
|
||||
|
||||
* frame‑based execution
|
||||
* explicit allocation
|
||||
* deterministic cleanup
|
||||
* predictable performance
|
||||
|
||||
It teaches developers **why engines are written the way they are**, instead of hiding reality.
|
||||
|
||||
---
|
||||
|
||||
## 15. Who PBS Is For
|
||||
|
||||
PBS is for:
|
||||
|
||||
* game developers who want control
|
||||
* engine developers
|
||||
* students learning systems concepts
|
||||
* educators teaching memory and runtime models
|
||||
|
||||
PBS is *not* for:
|
||||
|
||||
* rapid prototyping without constraints
|
||||
* general app scripting
|
||||
* hiding complexity
|
||||
|
||||
---
|
||||
|
||||
## 16. Design Promise
|
||||
|
||||
PBS makes one promise:
|
||||
|
||||
> **Nothing happens behind your back.**
|
||||
|
||||
If you understand PBS, you understand your runtime.
|
||||
|
||||
That is the product.
|
||||
340
files/TODO.md
340
files/TODO.md
@ -1,340 +0,0 @@
|
||||
# Prometeu VM/Compiler/Bytecode — Atomic PR Plan (Junie-ready)
|
||||
|
||||
> **Entry point contract (confirmed)**
|
||||
>
|
||||
> * The PBS entry point is **`/src/main/modules/main.pbs::frame(): void`**.
|
||||
> * The compiler must inject **`FRAME_SYNC` immediately before `RET`** at the end of this function.
|
||||
> * `FRAME_SYNC` is a **signal only** (no GC opcodes). The VM uses it as a safe point.
|
||||
> * Missing entry point is a **fatal compile error**.
|
||||
|
||||
> **Goal**: Deliver a sequence of small, incremental PRs that bring the implementation closer to the published PBS/VM specs.
|
||||
>
|
||||
> **Rules for Junie (strict)**
|
||||
>
|
||||
> * **Do not make product decisions.** Only implement what is specified in the PR.
|
||||
> * **If anything is unclear, stop and ask.** Do not improvise.
|
||||
> * **All new/modified code comments must be in English.**
|
||||
> * Each PR must be **atomic** and **mergeable**.
|
||||
> * Each PR must include: **Briefing**, **Target**, **Non-goals**, **Implementation notes**, **Tests**, **Acceptance criteria**.
|
||||
|
||||
---
|
||||
|
||||
## PR-02 — VM: Introduce GateId vs heap index and a minimal GatePool (no RC yet)
|
||||
|
||||
### Briefing
|
||||
|
||||
Current runtime treats `Value::Gate(x)` as a **direct heap base index**. The spec requires a **Gate Pool** where `GateId` resolves to `{alive, base, slots, type_id, rc...}` and heap access happens only through gate validation + resolution.
|
||||
|
||||
This PR introduces the **data model** without changing ownership/RC yet, enabling later RC work.
|
||||
|
||||
### Target
|
||||
|
||||
* Add `GateId` type (e.g., `u32`) and a `GateEntry` struct with fields:
|
||||
|
||||
* `alive: bool`
|
||||
* `base: u32`
|
||||
* `slots: u32`
|
||||
* `type_id: u32` (store it, even if VM doesn’t use it yet)
|
||||
* Add `GatePool` container to the VM state.
|
||||
* Update `ALLOC(type_id, slots)` to:
|
||||
|
||||
* bump-alloc `slots` in heap
|
||||
* insert new `GateEntry { alive: true, base, slots, type_id }`
|
||||
* push `Value::Gate(GateId)` (GateId is index into gate_pool)
|
||||
* Update `GATE_LOAD/GATE_STORE` to:
|
||||
|
||||
* validate GateId (in-range + alive)
|
||||
* bounds-check offset against `slots`
|
||||
* translate to heap index: `base + offset`
|
||||
|
||||
### Non-goals
|
||||
|
||||
* No reference counting yet.
|
||||
* No reclaim/free yet.
|
||||
* No enforcement of borrow/mutate rules yet.
|
||||
|
||||
### Implementation notes
|
||||
|
||||
* Keep heap as `Vec<Value>` (or existing representation). In this PR, do not change heap layout.
|
||||
* Add a helper: `resolve_gate(gate_id) -> Result<&GateEntry, Trap>` and `resolve_gate_mut(...)`.
|
||||
* Define two new trap codes (or map onto existing ones if already defined):
|
||||
|
||||
* `TRAP_INVALID_GATE` (gate_id out of range)
|
||||
* `TRAP_DEAD_GATE` (entry exists but `alive == false`)
|
||||
* Make sure the VM never reads/writes heap directly for gate operations without resolution.
|
||||
|
||||
### Tests
|
||||
|
||||
1. VM unit tests:
|
||||
|
||||
* Allocate 2 gates; ensure they get distinct GateIds.
|
||||
* Store to gate offset 0, load back, assert equal.
|
||||
* Store to offset == slots (OOB) triggers OOB trap.
|
||||
* Use an invalid GateId triggers INVALID_GATE trap.
|
||||
2. If trap codes are new, test for the exact trap code.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
* `ALLOC` returns GateId (not heap base).
|
||||
* `GATE_LOAD/STORE` uses gate_pool resolution.
|
||||
* Invalid/dead gate attempts trap deterministically.
|
||||
|
||||
---
|
||||
|
||||
## PR-03 — VM: Add strong reference counting (RC) with deterministic retain/release semantics
|
||||
|
||||
### Briefing
|
||||
|
||||
The spec requires strong RC tracking for gates and deterministic behavior for invalid/dead gates. Today `GATE_RETAIN`/`GATE_RELEASE` are no-ops (or effectively pop-only).
|
||||
|
||||
This PR implements **strong_rc** and updates runtime to adjust RC in well-defined places.
|
||||
|
||||
### Target
|
||||
|
||||
* Extend `GateEntry` with:
|
||||
|
||||
* `strong_rc: u32`
|
||||
* Define semantics:
|
||||
|
||||
* New allocation starts with `strong_rc = 1` (gate value returned on stack owns 1 reference)
|
||||
* `GATE_RETAIN`: increment strong_rc
|
||||
* `GATE_RELEASE`: decrement strong_rc; if reaches 0 then mark gate `alive=false` and schedule reclaim
|
||||
|
||||
### Non-goals
|
||||
|
||||
* No compacting heap.
|
||||
* No weak refs.
|
||||
* Reclaim can be minimal (safe-point only) and may not actually reuse memory yet.
|
||||
|
||||
### Implementation notes
|
||||
|
||||
* Implement a `reclaim_queue: Vec<GateId>` in VM state.
|
||||
* On `strong_rc` reaching 0:
|
||||
|
||||
* set `alive = false`
|
||||
* push gate_id into `reclaim_queue`
|
||||
* **Safe point**: drain reclaim queue on `FRAME_SYNC`.
|
||||
|
||||
* Compiler guarantees `FRAME_SYNC` at the end of the PBS entry point `main.pbs::frame(): void` (PR-00).
|
||||
* In this PR, reclaim may simply:
|
||||
|
||||
* overwrite the heap range `[base, base+slots)` with `Value::Nil` (or a safe default)
|
||||
* keep gate_id non-reusable
|
||||
|
||||
### Tests
|
||||
|
||||
1. RC lifecycle test:
|
||||
|
||||
* alloc gate (rc=1)
|
||||
* retain (rc=2)
|
||||
* release (rc=1)
|
||||
* release (rc=0) => gate becomes dead
|
||||
* subsequent load/store traps DEAD_GATE
|
||||
2. Reclaim effect test (if you overwrite heap slots):
|
||||
|
||||
* store a value, release to 0, run safe-point
|
||||
* confirm heap region is cleared (only if heap is inspectable in tests)
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
* `GATE_RETAIN/RELEASE` changes RC.
|
||||
* Gate transitions to `dead` at rc==0.
|
||||
* Dead gate access traps.
|
||||
* Reclaim happens at the chosen safe point.
|
||||
|
||||
---
|
||||
|
||||
## PR-04 — VM: Automatic RC adjustments on stack/local/global/heap moves (no more “manual RC correctness”)
|
||||
|
||||
### Briefing
|
||||
|
||||
Relying on explicit `GATE_RETAIN/RELEASE` everywhere is error-prone. The spec indicates RC must be adjusted on assignments/pops/stores. This PR makes RC correctness **a VM invariant**: when a gate value is copied into a slot, RC increments; when replaced/dropped, RC decrements.
|
||||
|
||||
### Target
|
||||
|
||||
* Implement centralized helpers:
|
||||
|
||||
* `inc_rc_if_gate(Value)`
|
||||
* `dec_rc_if_gate(Value)`
|
||||
* Apply them in all places where values are moved or overwritten:
|
||||
|
||||
* Stack `PUSH`/`POP` (when dropping values)
|
||||
* Local set/get if they clone values
|
||||
* Global set/get
|
||||
* `GATE_STORE` (heap cell overwrite)
|
||||
* Any instruction that overwrites an existing slot (e.g., `STORE_LOCAL`, `STORE_GLOBAL`, etc.)
|
||||
|
||||
### Non-goals
|
||||
|
||||
* No changes to compiler output.
|
||||
* No borrow/mutate enforcement.
|
||||
|
||||
### Implementation notes
|
||||
|
||||
* When writing into a slot:
|
||||
|
||||
1. `dec_rc_if_gate(old_value)`
|
||||
2. write new value
|
||||
3. `inc_rc_if_gate(new_value)` **only if the semantics is “copy into slot”**
|
||||
* When moving (not copying) is possible, avoid double inc/dec.
|
||||
* If the VM uses `clone()` widely, be explicit about when RC should increase.
|
||||
|
||||
> ⚠️ If it’s unclear whether an opcode is “move” or “copy”, **stop and ask** (do not guess).
|
||||
|
||||
### Tests
|
||||
|
||||
1. Stack drop test:
|
||||
|
||||
* alloc gate, push into local, pop stack, ensure rc doesn’t underflow.
|
||||
2. Overwrite test:
|
||||
|
||||
* local = gateA, then local = gateB
|
||||
* rc of gateA decremented
|
||||
* gateA becomes dead if no other refs
|
||||
3. Heap store overwrite test:
|
||||
|
||||
* gateX stores gateA into offset 0
|
||||
* then stores gateB into same offset
|
||||
* rc adjusts accordingly
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
* No RC leaks on overwrites.
|
||||
* No premature dead gates when references still exist.
|
||||
* Tests cover overwrite in at least 2 storage kinds (local + heap).
|
||||
|
||||
---
|
||||
|
||||
## PR-05 — VM: Implement (debug-mode) Borrow/Mutate/Peek scopes as observable state
|
||||
|
||||
### Briefing
|
||||
|
||||
Currently `GATE_BEGIN_PEEK/BORROW/MUTATE` and `GATE_END_*` are no-ops. Even if v0 is permissive, the VM should at least track scope state to enable future enforcement and better diagnostics.
|
||||
|
||||
### Target
|
||||
|
||||
* Add per-gate “scope counters” (or a small state machine) in `GateEntry`:
|
||||
|
||||
* `peek_count: u32`
|
||||
* `borrow_count: u32`
|
||||
* `mutate_count: u32` (should be 0/1 if exclusive)
|
||||
* Implement opcodes to increment/decrement and validate balanced usage:
|
||||
|
||||
* End without begin => trap (or panic if considered VM bug)
|
||||
* Negative underflow => trap
|
||||
* In **debug builds**, optionally enforce:
|
||||
|
||||
* cannot `begin_mutate` when `borrow_count>0`
|
||||
* cannot `begin_borrow` when `mutate_count>0`
|
||||
|
||||
### Non-goals
|
||||
|
||||
* No compiler changes.
|
||||
* No runtime copy-back scratch buffers yet.
|
||||
|
||||
### Implementation notes
|
||||
|
||||
* Keep enforcement behind a feature flag or debug-only cfg.
|
||||
* Always keep counters balanced; mismatches should be deterministic.
|
||||
|
||||
### Tests
|
||||
|
||||
* Begin/End balance test per scope.
|
||||
* Debug-only conflict test (if enabled).
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
* Scopes are no longer no-ops.
|
||||
* Misbalanced begin/end produces deterministic error.
|
||||
|
||||
---
|
||||
|
||||
## PR-06 — Bytecode/VM: Represent strings as ConstId (dedup + stable value size)
|
||||
|
||||
### Briefing
|
||||
|
||||
`Value::String(String)` stores dynamic payload in runtime values. The spec direction prefers string refs into constant pools for stability and dedup. This PR migrates runtime string values to `ConstId` references.
|
||||
|
||||
### Target
|
||||
|
||||
* Add `Value::StringRef(ConstId)` (or `Value::String(ConstId)`)
|
||||
* Ensure program image contains a string pool.
|
||||
* Update `PUSH_CONST` behavior for string constants:
|
||||
|
||||
* push `StringRef(id)` instead of allocating a runtime `String`
|
||||
|
||||
### Non-goals
|
||||
|
||||
* No interning beyond the existing constant pool.
|
||||
* No changes to the source language.
|
||||
|
||||
### Implementation notes
|
||||
|
||||
* Decide where the string pool lives (ProgramImage / ConstPool).
|
||||
* Update debug printing and trap formatting if needed.
|
||||
|
||||
### Tests
|
||||
|
||||
* Constant string pushed twice references same ConstId.
|
||||
* Equality/comparison behavior remains unchanged (if supported).
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
* Strings in runtime values are pool references.
|
||||
* Existing programs using constants still run.
|
||||
|
||||
---
|
||||
|
||||
## PR-07 — Compiler: Enforce import placement rules (top-of-file)
|
||||
|
||||
### Briefing
|
||||
|
||||
The PBS module model specifies that imports must be at the top-level (and typically before other declarations). This PR makes the compiler reject invalid import placement to align with the module/linking spec.
|
||||
|
||||
### Target
|
||||
|
||||
* In parser/collector phase, detect imports appearing after non-import declarations.
|
||||
* Emit a diagnostic with a clear message and span.
|
||||
|
||||
### Non-goals
|
||||
|
||||
* No changes to how linking works.
|
||||
* No auto-fix.
|
||||
|
||||
### Implementation notes
|
||||
|
||||
* Track a boolean `seen_non_import_decl` during file scan.
|
||||
* When an import is encountered and the flag is set, produce an error diagnostic.
|
||||
|
||||
### Tests
|
||||
|
||||
* One file with valid imports at top => ok.
|
||||
* One file with import after a function/type => error.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
* Compiler rejects invalid import placement with stable diagnostic.
|
||||
|
||||
---
|
||||
|
||||
# Suggested merge order
|
||||
|
||||
1. PR-00 (entry point validation + FRAME_SYNC injection)
|
||||
2. PR-01 (linker JMP relocation)
|
||||
3. PR-02 (GatePool + GateId)
|
||||
4. PR-03 (RC strong + retain/release + reclaim at FRAME_SYNC)
|
||||
5. PR-04 (automatic RC on moves/overwrites)
|
||||
6. PR-05 (scope tracking)
|
||||
7. PR-06 (StringRef ConstId)
|
||||
8. PR-07 (import placement enforcement)
|
||||
|
||||
---
|
||||
|
||||
# Open questions (must be answered before PR-04)
|
||||
|
||||
> Junie must stop and ask if any of these cannot be determined from the codebase.
|
||||
|
||||
1. Which exact opcodes correspond to storage overwrites (locals/globals) in the current VM implementation (names may differ).
|
||||
2. Which trap codes are already defined for gate errors vs whether we introduce new ones.
|
||||
|
||||
> ✅ Note: the safe point for reclaim is **FRAME_SYNC** (compiler inserts it at the end of `fn frame(): void`).
|
||||
Loading…
x
Reference in New Issue
Block a user