implement PLN-0033 vm string sharing hot path

This commit is contained in:
bQUARKz 2026-04-20 08:45:34 +01:00
parent 8c5957a0a9
commit 09821cf9cc
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
10 changed files with 359 additions and 17 deletions

View File

@ -6,4 +6,4 @@ license.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive", "rc"] }

View File

@ -75,7 +75,7 @@ impl From<BytecodeModule> for ProgramImage {
ConstantPoolEntry::Int64(v) => Value::Int64(*v), ConstantPoolEntry::Int64(v) => Value::Int64(*v),
ConstantPoolEntry::Float64(v) => Value::Float(*v), ConstantPoolEntry::Float64(v) => Value::Float(*v),
ConstantPoolEntry::Boolean(v) => Value::Boolean(*v), ConstantPoolEntry::Boolean(v) => Value::Boolean(*v),
ConstantPoolEntry::String(v) => Value::String(v.clone()), ConstantPoolEntry::String(v) => Value::String(v.clone().into()),
ConstantPoolEntry::Int32(v) => Value::Int32(*v), ConstantPoolEntry::Int32(v) => Value::Int32(*v),
}) })
.collect(); .collect();
@ -99,7 +99,7 @@ impl From<ProgramImage> for BytecodeModule {
Value::Int64(v) => ConstantPoolEntry::Int64(*v), Value::Int64(v) => ConstantPoolEntry::Int64(*v),
Value::Float(v) => ConstantPoolEntry::Float64(*v), Value::Float(v) => ConstantPoolEntry::Float64(*v),
Value::Boolean(v) => ConstantPoolEntry::Boolean(*v), Value::Boolean(v) => ConstantPoolEntry::Boolean(*v),
Value::String(v) => ConstantPoolEntry::String(v.clone()), Value::String(v) => ConstantPoolEntry::String(v.to_string()),
Value::Int32(v) => ConstantPoolEntry::Int32(*v), Value::Int32(v) => ConstantPoolEntry::Int32(*v),
Value::HeapRef(_) => ConstantPoolEntry::Null, Value::HeapRef(_) => ConstantPoolEntry::Null,
}) })

View File

@ -1,5 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::Write;
use std::sync::Arc;
/// Opaque handle that references an object stored in the VM heap. /// Opaque handle that references an object stored in the VM heap.
/// ///
@ -26,7 +28,7 @@ pub enum Value {
/// Boolean value (true/false). /// Boolean value (true/false).
Boolean(bool), Boolean(bool),
/// UTF-8 string. Strings are immutable and usually come from the Constant Pool. /// UTF-8 string. Strings are immutable and usually come from the Constant Pool.
String(String), String(Arc<str>),
/// A handle to an object on the heap (opaque reference). /// A handle to an object on the heap (opaque reference).
HeapRef(HeapRef), HeapRef(HeapRef),
/// Represents the absence of a value (equivalent to `null` or `undefined`). /// Represents the absence of a value (equivalent to `null` or `undefined`).
@ -92,17 +94,46 @@ impl Value {
} }
} }
pub fn append_to_string(&self, out: &mut String) {
match self {
Value::Int32(i) => {
let _ = write!(out, "{}", i);
}
Value::Int64(i) => {
let _ = write!(out, "{}", i);
}
Value::Float(f) => {
let _ = write!(out, "{}", f);
}
Value::Boolean(b) => {
let _ = write!(out, "{}", b);
}
Value::String(s) => out.push_str(s),
Value::HeapRef(r) => {
let _ = write!(out, "[HeapRef {}]", r.0);
}
Value::Null => out.push_str("null"),
}
}
pub fn string_len_hint(&self) -> usize {
match self {
Value::String(s) => s.len(),
Value::Null => 4,
Value::Boolean(true) => 4,
Value::Boolean(false) => 5,
Value::Int32(i) => i.to_string().len(),
Value::Int64(i) => i.to_string().len(),
Value::Float(f) => f.to_string().len(),
Value::HeapRef(r) => 11 + r.0.to_string().len(),
}
}
#[allow(clippy::inherent_to_string)] #[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
match self { let mut out = String::with_capacity(self.string_len_hint());
Value::Int32(i) => i.to_string(), self.append_to_string(&mut out);
Value::Int64(i) => i.to_string(), out
Value::Float(f) => f.to_string(),
Value::Boolean(b) => b.to_string(),
Value::String(s) => s.clone(),
Value::HeapRef(r) => format!("[HeapRef {}]", r.0),
Value::Null => "null".to_string(),
}
} }
} }

View File

@ -22,6 +22,6 @@ impl<'a> HostReturn<'a> {
self.stack.push(Value::HeapRef(HeapRef(g as u32))); self.stack.push(Value::HeapRef(HeapRef(g as u32)));
} }
pub fn push_string(&mut self, s: String) { pub fn push_string(&mut self, s: String) {
self.stack.push(Value::String(s)); self.stack.push(Value::String(s.into()));
} }
} }

View File

@ -577,7 +577,7 @@ impl NativeInterface for VirtualMachineRuntime {
fn expect_string(args: &[Value], index: usize, field: &str) -> Result<String, VmFault> { fn expect_string(args: &[Value], index: usize, field: &str) -> Result<String, VmFault> {
match args.get(index).ok_or_else(|| VmFault::Trap(TRAP_TYPE, format!("Missing {}", field)))? { match args.get(index).ok_or_else(|| VmFault::Trap(TRAP_TYPE, format!("Missing {}", field)))? {
Value::String(value) => Ok(value.clone()), Value::String(value) => Ok(value.to_string()),
_ => Err(VmFault::Trap(TRAP_TYPE, format!("Expected string {}", field))), _ => Err(VmFault::Trap(TRAP_TYPE, format!("Expected string {}", field))),
} }
} }

View File

@ -639,7 +639,10 @@ impl VirtualMachine {
} }
OpCode::Add => self.binary_op(opcode, start_pc as u32, |a, b| match (&a, &b) { OpCode::Add => self.binary_op(opcode, start_pc as u32, |a, b| match (&a, &b) {
(Value::String(_), _) | (_, Value::String(_)) => { (Value::String(_), _) | (_, Value::String(_)) => {
Ok(Value::String(format!("{}{}", a.to_string(), b.to_string()))) let mut out = String::with_capacity(a.string_len_hint() + b.string_len_hint());
a.append_to_string(&mut out);
b.append_to_string(&mut out);
Ok(Value::String(out.into()))
} }
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_add(*b))), (Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_add(*b))),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_add(*b))), (Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_add(*b))),
@ -1213,6 +1216,7 @@ impl VirtualMachine {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::sync::Arc;
fn new_test_vm(rom: Vec<u8>, constant_pool: Vec<Value>) -> VirtualMachine { fn new_test_vm(rom: Vec<u8>, constant_pool: Vec<Value>) -> VirtualMachine {
let rom_len = rom.len() as u32; let rom_len = rom.len() as u32;
@ -1642,6 +1646,19 @@ mod tests {
assert_eq!(vm.peek().unwrap(), &Value::String("hello".into())); assert_eq!(vm.peek().unwrap(), &Value::String("hello".into()));
} }
#[test]
fn test_shared_string_clone_reuses_materialized_payload() {
let mut vm = VirtualMachine::default();
let payload: Arc<str> = "shared-global".into();
vm.globals.push(Value::String(payload.clone()));
let cloned = vm.globals[0].clone();
match cloned {
Value::String(cloned_payload) => assert!(Arc::ptr_eq(&payload, &cloned_payload)),
other => panic!("expected string clone from globals, got {:?}", other),
}
}
#[test] #[test]
fn test_push_const_invalid_index_traps_oob() { fn test_push_const_invalid_index_traps_oob() {
let mut rom = Vec::new(); let mut rom = Vec::new();

View File

@ -21,7 +21,7 @@
{"type":"discussion","id":"DSC-0026","status":"done","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-18","tags":["gfx","runtime","render","camera","scene"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0031","file":"lessons/DSC-0026-render-all-scene-cache-and-camera-integration/LSN-0031-frame-composition-belongs-above-the-render-backend.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]} {"type":"discussion","id":"DSC-0026","status":"done","ticket":"render-all-scene-cache-and-camera-integration","title":"Integrate render_all with Scene Cache and Camera","created_at":"2026-04-14","updated_at":"2026-04-18","tags":["gfx","runtime","render","camera","scene"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0031","file":"lessons/DSC-0026-render-all-scene-cache-and-camera-integration/LSN-0031-frame-composition-belongs-above-the-render-backend.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
{"type":"discussion","id":"DSC-0027","status":"done","ticket":"frame-composer-public-syscall-surface","title":"Agenda - FrameComposer Public Syscall Surface","created_at":"2026-04-17","updated_at":"2026-04-18","tags":["gfx","runtime","syscall","abi","frame-composer","scene","camera","sprites"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0032","file":"lessons/DSC-0027-frame-composer-public-syscall-surface/LSN-0032-public-abi-must-follow-the-canonical-service-boundary.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]} {"type":"discussion","id":"DSC-0027","status":"done","ticket":"frame-composer-public-syscall-surface","title":"Agenda - FrameComposer Public Syscall Surface","created_at":"2026-04-17","updated_at":"2026-04-18","tags":["gfx","runtime","syscall","abi","frame-composer","scene","camera","sprites"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0032","file":"lessons/DSC-0027-frame-composer-public-syscall-surface/LSN-0032-public-abi-must-follow-the-canonical-service-boundary.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
{"type":"discussion","id":"DSC-0028","status":"done","ticket":"deferred-overlay-and-primitive-composition","title":"Deferred Overlay and Primitive Composition over FrameComposer","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["gfx","runtime","render","frame-composer","overlay","primitives","hud"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0033","file":"lessons/DSC-0028-deferred-overlay-and-primitive-composition/LSN-0033-debug-primitives-should-be-a-final-overlay-not-part-of-game-composition.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]} {"type":"discussion","id":"DSC-0028","status":"done","ticket":"deferred-overlay-and-primitive-composition","title":"Deferred Overlay and Primitive Composition over FrameComposer","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["gfx","runtime","render","frame-composer","overlay","primitives","hud"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0033","file":"lessons/DSC-0028-deferred-overlay-and-primitive-composition/LSN-0033-debug-primitives-should-be-a-final-overlay-not-part-of-game-composition.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}
{"type":"discussion","id":"DSC-0014","status":"review","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-04-20","_override_reason":"User explicitly requested emitting a decision from the resolved agenda in this turn."}],"decisions":[{"id":"DEC-0018","file":"workflow/decisions/DEC-0018-vm-allocation-and-copy-pressure-baseline.md","status":"in_progress","created_at":"2026-04-20","updated_at":"2026-04-20","ref_agenda":"AGD-0013","_override_reason":"User explicitly requested emitting and then accepting the decision, followed by plan generation."}],"plans":[{"id":"PLN-0033","file":"PLN-0033-vm-hot-path-ownership-and-string-copy-pressure.md","status":"review","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0018"]},{"id":"PLN-0034","file":"PLN-0034-internal-allocation-evidence-and-hot-path-measurement.md","status":"review","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0018"]},{"id":"PLN-0035","file":"PLN-0035-runtime-spec-wording-for-materialization-vs-copy-pressure.md","status":"review","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0018"]}],"lessons":[]} {"type":"discussion","id":"DSC-0014","status":"in_progress","ticket":"perf-vm-allocation-and-copy-pressure","title":"Agenda - [PERF] VM Allocation and Copy Pressure","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0013","file":"workflow/agendas/AGD-0013-perf-vm-allocation-and-copy-pressure.md","status":"accepted","created_at":"2026-03-27","updated_at":"2026-04-20","_override_reason":"User explicitly requested emitting a decision from the resolved agenda in this turn."}],"decisions":[{"id":"DEC-0018","file":"workflow/decisions/DEC-0018-vm-allocation-and-copy-pressure-baseline.md","status":"in_progress","created_at":"2026-04-20","updated_at":"2026-04-20","ref_agenda":"AGD-0013","_override_reason":"User explicitly requested emitting and then accepting the decision, followed by plan generation."}],"plans":[{"id":"PLN-0033","file":"PLN-0033-vm-hot-path-ownership-and-string-copy-pressure.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0018"]},{"id":"PLN-0034","file":"PLN-0034-internal-allocation-evidence-and-hot-path-measurement.md","status":"review","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0018"]},{"id":"PLN-0035","file":"PLN-0035-runtime-spec-wording-for-materialization-vs-copy-pressure.md","status":"review","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0018"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0015","status":"open","ticket":"perf-cartridge-boot-and-program-ownership","title":"Agenda - [PERF] Cartridge Boot and Program Ownership","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0014","file":"workflow/agendas/AGD-0014-perf-cartridge-boot-and-program-ownership.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} {"type":"discussion","id":"DSC-0016","status":"done","ticket":"tilemap-empty-cell-vs-tile-id-zero","title":"Tilemap Empty Cell vs Tile ID Zero","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0015","file":"workflow/agendas/AGD-0015-tilemap-empty-cell-vs-tile-id-zero.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0022","file":"lessons/DSC-0016-tilemap-empty-cell-semantics/LSN-0022-tilemap-empty-cell-convergence.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
{"type":"discussion","id":"DSC-0017","status":"done","ticket":"asset-entry-metadata-normalization-contract","title":"Asset Entry Metadata Normalization Contract","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0016","file":"workflow/agendas/AGD-0016-asset-entry-metadata-normalization-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[{"id":"DEC-0004","file":"workflow/decisions/DEC-0004-asset-entry-metadata-normalization-contract.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-09"}],"plans":[],"lessons":[{"id":"LSN-0023","file":"lessons/DSC-0017-asset-metadata-normalization/LSN-0023-typed-asset-metadata-helpers.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} {"type":"discussion","id":"DSC-0017","status":"done","ticket":"asset-entry-metadata-normalization-contract","title":"Asset Entry Metadata Normalization Contract","created_at":"2026-03-27","updated_at":"2026-04-09","tags":[],"agendas":[{"id":"AGD-0016","file":"workflow/agendas/AGD-0016-asset-entry-metadata-normalization-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-09"}],"decisions":[{"id":"DEC-0004","file":"workflow/decisions/DEC-0004-asset-entry-metadata-normalization-contract.md","status":"accepted","created_at":"2026-04-09","updated_at":"2026-04-09"}],"plans":[],"lessons":[{"id":"LSN-0023","file":"lessons/DSC-0017-asset-metadata-normalization/LSN-0023-typed-asset-metadata-helpers.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}

View File

@ -0,0 +1,108 @@
---
id: PLN-0033
ticket: perf-vm-allocation-and-copy-pressure
title: Plan - VM Hot Path Ownership and String Copy Pressure
status: done
created: 2026-04-20
completed: 2026-04-20
tags: [perf, runtime, vm, memory, strings]
---
## Briefing
Implement the runtime-core portion of `DEC-0018` by reducing repeated string payload copies and hot-path allocation pressure without changing guest-visible semantics.
## Decisions de Origem
- `DEC-0018` - VM Allocation and Copy Pressure Baseline
## Alvo
Change the VM core so that hot paths over numeric values and already-materialized values converge toward zero allocation, while preserving the public semantics of string values and `GET_GLOBAL`.
## Escopo
### Included
- VM value representation and ownership audit for hot paths.
- `GET_GLOBAL` copy-pressure reduction without semantic drift.
- String-producing opcode hot-path changes, especially `ADD`.
- Unit and focused integration coverage for unchanged semantics.
### Excluded
- Public ABI changes.
- Certification or host-facing telemetry policy changes.
- Broad speculative string interning or copy-on-write rollout unless directly required by a concrete implementation step.
## Fora de Escopo
- Cartridge language changes.
- Host debugger protocol changes.
- General fault/log path rewrites beyond keeping them isolated from hot-path work.
## Plano de Execucao
### Step 1 - Audit current hot-path ownership
**What:** Identify the exact VM operations that still clone or allocate on already-materialized hot paths.
**How:** Review `Value`, `GET_GLOBAL`, `ADD`, related stack helpers, and any nearby string coercion paths; classify each site as unavoidable first materialization versus avoidable repeated copy.
**File(s):** `crates/console/prometeu-bytecode/src/value.rs`, `crates/console/prometeu-vm/src/virtual_machine.rs`
### Step 2 - Rework internal representation/ownership for expensive payloads
**What:** Introduce the smallest internal representation change needed to avoid repeated payload copies for expensive values.
**How:** Prefer internal ownership changes that preserve guest-visible semantics. If strings or other expensive payloads need to move behind handles or equivalent internal storage, keep the public value meaning unchanged and constrain the change to runtime-core internals.
**File(s):** `crates/console/prometeu-bytecode/src/value.rs`, `crates/console/prometeu-vm/src/heap.rs`, `crates/console/prometeu-vm/src/object.rs`, `crates/console/prometeu-vm/src/virtual_machine.rs`
### Step 3 - Fix `GET_GLOBAL` without semantic change
**What:** Remove avoidable repeated copy pressure from `GET_GLOBAL`.
**How:** Keep the observable contract identical while changing retrieval/storage internals so already-materialized expensive payloads are not recopied on each hot-path access.
**File(s):** `crates/console/prometeu-vm/src/virtual_machine.rs`
### Step 4 - Optimize string-producing opcode paths
**What:** Reduce repeated allocation/copy pressure in string-producing operations, starting with `ADD`.
**How:** Separate true new-string creation from repeated cloning of existing payloads. Ensure any operation that semantically creates a new string still does so correctly, while avoiding extra transient allocations around existing materialized values.
**File(s):** `crates/console/prometeu-vm/src/virtual_machine.rs`, related helper modules if introduced
### Step 5 - Preserve semantic coverage
**What:** Prove semantics did not change while ownership internals did.
**How:** Add/update tests for constants, dynamic strings, globals, arithmetic/string `ADD`, and edge cases where strings cross stack/global boundaries.
**File(s):** `crates/console/prometeu-vm/src/virtual_machine.rs`, VM test modules
## Criterios de Aceite
- `GET_GLOBAL` keeps its public semantics unchanged.
- Hot-path access to already-materialized expensive values no longer performs avoidable repeated payload copies.
- Numeric hot paths remain allocation-free.
- String operations that truly create a new string remain correct and deterministic.
- VM tests cover constant-pool strings, runtime-created strings, and globals under the new ownership model.
## Tests / Validacao
### Unit Tests
- Value/ownership tests for expensive payload movement or referencing.
- VM opcode tests for `GET_GLOBAL`, `SET_GLOBAL`, and string `ADD`.
### Integration Tests
- Small bytecode programs that read globals repeatedly and mix numeric/string paths.
### Manual Verification
- Review allocation-sensitive hotspots in the final implementation to confirm repeated copies were removed rather than merely relocated.
## Riscos
- Internal representation changes may ripple into GC/root traversal.
- A partial fix may hide copies in helper methods rather than eliminate them.
- Over-optimizing strings could accidentally change guest-visible behavior if semantic boundaries are not defended by tests.
## Dependencies
- `DEC-0018`
- Existing VM heap/object/root traversal contracts

View File

@ -0,0 +1,98 @@
---
id: PLN-0034
ticket: perf-vm-allocation-and-copy-pressure
title: Plan - Internal Allocation Evidence and Hot Path Measurement
status: review
created: 2026-04-20
completed:
tags: [perf, runtime, telemetry, allocation, engineering]
---
## Briefing
Add internal engineering evidence for `DEC-0018` so the team can verify progress toward zero allocation on the happy path without turning that metric into a certification contract.
## Decisions de Origem
- `DEC-0018` - VM Allocation and Copy Pressure Baseline
## Alvo
Produce low-intrusion internal measurement for allocation and copy pressure that can guide implementation and regression detection during engineering work.
## Escopo
### Included
- Internal-only allocation counters or equivalent evidence mechanisms.
- Measurement points around VM execution slices and/or frame boundaries.
- Regression-oriented tests or harnesses that assert expected internal evidence in happy-path scenarios.
### Excluded
- New public certification outputs.
- Guest-visible profiling ABI changes.
- Heavy per-opcode instrumentation that distorts the hot path.
## Fora de Escopo
- Rewriting the full telemetry model.
- Publishing allocation counts to cartridge authors as normative behavior.
## Plano de Execucao
### Step 1 - Define internal evidence model
**What:** Choose the minimal evidence surface that proves whether happy-path execution allocates.
**How:** Prefer counters or snapshots that can distinguish first materialization from steady-state hot-path execution. Tie the evidence to engineering diagnostics only.
**File(s):** `crates/console/prometeu-hal/src/telemetry.rs`, `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`, internal runtime/telemetry helpers as needed
### Step 2 - Implement low-intrusion collection
**What:** Add instrumentation with bounded overhead.
**How:** Record allocation evidence at frame or execution-slice boundaries rather than on every operation when possible. Keep the instrumentation isolated from guest-visible contracts.
**File(s):** `crates/console/prometeu-vm/src/heap.rs`, `crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs`, related telemetry modules
### Step 3 - Define regression scenarios
**What:** Create deterministic scenarios that express the engineering target.
**How:** Add tests or bench-like checks for numeric happy paths, already-materialized constant strings, and runtime-created string paths after first materialization.
**File(s):** runtime/VM test modules; dedicated internal telemetry tests if appropriate
### Step 4 - Document how evidence is used
**What:** Clarify that this evidence is an engineering metric, not certification policy.
**How:** Update inline docs or internal notes near the measurement code so future work does not accidentally publish the metric as a hard contract.
**File(s):** relevant telemetry/runtime modules
## Criterios de Aceite
- Engineers can observe whether selected happy-path scenarios allocate.
- The measurement surface distinguishes internal engineering evidence from public certification output.
- Instrumentation overhead stays bounded and does not require intrusive per-opcode tracing.
- Regression checks exist for at least one numeric path and one already-materialized string path.
## Tests / Validacao
### Unit Tests
- Counter/reset/snapshot correctness for internal allocation evidence.
### Integration Tests
- Runtime scenarios that verify zero-allocation expectations on steady-state happy paths.
### Manual Verification
- Inspect final measurement plumbing to confirm it is not exposed as public certification policy.
## Riscos
- Instrumentation may accidentally become hot-path overhead.
- Counters may undercount or overcount if first materialization is not separated from steady-state execution.
- Internal metrics may be misread later as external compatibility guarantees unless explicitly documented.
## Dependencies
- `DEC-0018`
- Existing runtime telemetry boundary

View File

@ -0,0 +1,88 @@
---
id: PLN-0035
ticket: perf-vm-allocation-and-copy-pressure
title: Plan - Runtime Spec Wording for Materialization vs Copy Pressure
status: review
created: 2026-04-20
completed:
tags: [spec, runtime, memory, strings, perf]
---
## Briefing
Apply the minimal spec wording cleanup implied by `DEC-0018` so published runtime docs distinguish first materialization cost from repeated hot-path copy pressure without changing the public ABI.
## Decisions de Origem
- `DEC-0018` - VM Allocation and Copy Pressure Baseline
## Alvo
Update canonical runtime specs so the published memory/value/debug model reflects the accepted architectural framing behind the implementation work.
## Escopo
### Included
- Wording updates in runtime specs for values, memory/allocation, and debug/profiling boundaries.
- Clarifications that zero-allocation on the happy path is an engineering target, not certification policy.
### Excluded
- Any new guest-visible ABI rules.
- Broad documentation rewrite unrelated to `DEC-0018`.
## Fora de Escopo
- Lessons material.
- Cartridge author migration guides.
## Plano de Execucao
### Step 1 - Identify exact normative gaps
**What:** Locate wording that currently conflates all allocation cost with hot-path copy pressure or leaves the first-materialization distinction implicit.
**How:** Review the accepted decision against the current value, memory, and debug chapters and capture only the gaps required to keep docs aligned with implementation intent.
**File(s):** `docs/specs/runtime/02a-vm-values-and-calling-convention.md`, `docs/specs/runtime/03-memory-stack-heap-and-allocation.md`, `docs/specs/runtime/10-debug-inspection-and-profiling.md`
### Step 2 - Update normative text
**What:** Clarify constant-pool materialization, runtime materialization, and repeated-copy pressure boundaries.
**How:** Add concise normative wording that preserves the existing ABI while making clear that internal engineering may target zero allocation on happy paths without publishing that as certification policy.
**File(s):** `docs/specs/runtime/02a-vm-values-and-calling-convention.md`, `docs/specs/runtime/03-memory-stack-heap-and-allocation.md`, `docs/specs/runtime/10-debug-inspection-and-profiling.md`
### Step 3 - Cross-check consistency
**What:** Ensure the updated docs stay aligned with the accepted decision and with any implementation evidence introduced under linked plans.
**How:** Review terminology across the edited chapters and confirm no text implies a guest-visible ABI or certification promise that the decision explicitly rejected.
**File(s):** same as above
## Criterios de Aceite
- Runtime specs distinguish first materialization from repeated copy pressure.
- Specs do not imply a public ABI change for `GET_GLOBAL` or strings.
- Specs do not promote zero-allocation happy-path behavior to certification policy.
- Edited docs remain consistent across values, memory, and profiling chapters.
## Tests / Validacao
### Unit Tests
- Not applicable.
### Integration Tests
- Not applicable.
### Manual Verification
- Read the edited chapters side by side with `DEC-0018` and confirm that every new normative statement maps directly to the accepted decision.
## Riscos
- Specs may drift ahead of implementation details if wording is too specific.
- Overstating performance goals in normative docs may accidentally create an external compatibility promise.
## Dependencies
- `DEC-0018`