implement PLN-0034 internal allocation evidence
This commit is contained in:
parent
09821cf9cc
commit
811af09ea7
@ -21,4 +21,4 @@ pub use disassembler::disassemble;
|
|||||||
pub use layout::{compute_function_layouts, FunctionLayout};
|
pub use layout::{compute_function_layouts, FunctionLayout};
|
||||||
pub use model::{BytecodeLoader, FunctionMeta, LoadError, SyscallDecl};
|
pub use model::{BytecodeLoader, FunctionMeta, LoadError, SyscallDecl};
|
||||||
pub use program_image::ProgramImage;
|
pub use program_image::ProgramImage;
|
||||||
pub use value::{HeapRef, Value};
|
pub use value::{string_materialization_count, HeapRef, Value};
|
||||||
|
|||||||
@ -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().into()),
|
ConstantPoolEntry::String(v) => Value::string(v.clone()),
|
||||||
ConstantPoolEntry::Int32(v) => Value::Int32(*v),
|
ConstantPoolEntry::Int32(v) => Value::Int32(*v),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
static STRING_MATERIALIZATION_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
/// Opaque handle that references an object stored in the VM heap.
|
/// Opaque handle that references an object stored in the VM heap.
|
||||||
///
|
///
|
||||||
/// This is an index-based handle. It does not imply ownership and carries
|
/// This is an index-based handle. It does not imply ownership and carries
|
||||||
@ -76,6 +79,14 @@ impl PartialOrd for Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
pub fn string<S>(value: S) -> Self
|
||||||
|
where
|
||||||
|
S: Into<Arc<str>>,
|
||||||
|
{
|
||||||
|
STRING_MATERIALIZATION_COUNT.fetch_add(1, AtomicOrdering::Relaxed);
|
||||||
|
Value::String(value.into())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_float(&self) -> Option<f64> {
|
pub fn as_float(&self) -> Option<f64> {
|
||||||
match self {
|
match self {
|
||||||
Value::Int32(i) => Some(*i as f64),
|
Value::Int32(i) => Some(*i as f64),
|
||||||
@ -137,6 +148,10 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_materialization_count() -> u64 {
|
||||||
|
STRING_MATERIALIZATION_COUNT.load(AtomicOrdering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@ -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.into()));
|
self.stack.push(Value::string(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,8 @@ pub struct TelemetryFrame {
|
|||||||
// RAM (Heap)
|
// RAM (Heap)
|
||||||
pub heap_used_bytes: usize,
|
pub heap_used_bytes: usize,
|
||||||
pub heap_max_bytes: usize,
|
pub heap_max_bytes: usize,
|
||||||
|
pub vm_heap_allocations: u64,
|
||||||
|
pub vm_string_materializations: u64,
|
||||||
|
|
||||||
// Log Pressure from the last completed logical frame
|
// Log Pressure from the last completed logical frame
|
||||||
pub logs_count: u32,
|
pub logs_count: u32,
|
||||||
@ -53,6 +55,8 @@ pub struct AtomicTelemetry {
|
|||||||
// RAM (Heap)
|
// RAM (Heap)
|
||||||
pub heap_used_bytes: AtomicUsize,
|
pub heap_used_bytes: AtomicUsize,
|
||||||
pub heap_max_bytes: AtomicUsize,
|
pub heap_max_bytes: AtomicUsize,
|
||||||
|
pub vm_heap_allocations: AtomicU64,
|
||||||
|
pub vm_string_materializations: AtomicU64,
|
||||||
|
|
||||||
// Transient in-flight log counter for the current logical frame
|
// Transient in-flight log counter for the current logical frame
|
||||||
pub current_logs_count: Arc<AtomicU32>,
|
pub current_logs_count: Arc<AtomicU32>,
|
||||||
@ -83,6 +87,8 @@ impl AtomicTelemetry {
|
|||||||
scene_slots_total: self.scene_slots_total.load(Ordering::Relaxed),
|
scene_slots_total: self.scene_slots_total.load(Ordering::Relaxed),
|
||||||
heap_used_bytes: self.heap_used_bytes.load(Ordering::Relaxed),
|
heap_used_bytes: self.heap_used_bytes.load(Ordering::Relaxed),
|
||||||
heap_max_bytes: self.heap_max_bytes.load(Ordering::Relaxed),
|
heap_max_bytes: self.heap_max_bytes.load(Ordering::Relaxed),
|
||||||
|
vm_heap_allocations: self.vm_heap_allocations.load(Ordering::Relaxed),
|
||||||
|
vm_string_materializations: self.vm_string_materializations.load(Ordering::Relaxed),
|
||||||
logs_count: self.logs_count.load(Ordering::Relaxed),
|
logs_count: self.logs_count.load(Ordering::Relaxed),
|
||||||
vm_steps: self.vm_steps.load(Ordering::Relaxed),
|
vm_steps: self.vm_steps.load(Ordering::Relaxed),
|
||||||
}
|
}
|
||||||
@ -102,6 +108,8 @@ impl AtomicTelemetry {
|
|||||||
self.scene_slots_used.store(0, Ordering::Relaxed);
|
self.scene_slots_used.store(0, Ordering::Relaxed);
|
||||||
self.scene_slots_total.store(0, Ordering::Relaxed);
|
self.scene_slots_total.store(0, Ordering::Relaxed);
|
||||||
self.heap_used_bytes.store(0, Ordering::Relaxed);
|
self.heap_used_bytes.store(0, Ordering::Relaxed);
|
||||||
|
self.vm_heap_allocations.store(0, Ordering::Relaxed);
|
||||||
|
self.vm_string_materializations.store(0, Ordering::Relaxed);
|
||||||
self.vm_steps.store(0, Ordering::Relaxed);
|
self.vm_steps.store(0, Ordering::Relaxed);
|
||||||
self.logs_count.store(0, Ordering::Relaxed);
|
self.logs_count.store(0, Ordering::Relaxed);
|
||||||
self.current_logs_count.store(0, Ordering::Relaxed);
|
self.current_logs_count.store(0, Ordering::Relaxed);
|
||||||
@ -313,4 +321,17 @@ mod tests {
|
|||||||
assert_eq!(snapshot.logs_count, 3);
|
assert_eq!(snapshot.logs_count, 3);
|
||||||
assert_eq!(current.load(Ordering::Relaxed), 7);
|
assert_eq!(current.load(Ordering::Relaxed), 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn snapshot_includes_internal_allocation_evidence() {
|
||||||
|
let current = Arc::new(AtomicU32::new(0));
|
||||||
|
let tel = AtomicTelemetry::new(current);
|
||||||
|
tel.vm_heap_allocations.store(2, Ordering::Relaxed);
|
||||||
|
tel.vm_string_materializations.store(5, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let snapshot = tel.snapshot();
|
||||||
|
|
||||||
|
assert_eq!(snapshot.vm_heap_allocations, 2);
|
||||||
|
assert_eq!(snapshot.vm_string_materializations, 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ mod tick;
|
|||||||
use crate::CrashReport;
|
use crate::CrashReport;
|
||||||
use crate::fs::{FsState, VirtualFS};
|
use crate::fs::{FsState, VirtualFS};
|
||||||
use crate::services::memcard::MemcardService;
|
use crate::services::memcard::MemcardService;
|
||||||
|
use prometeu_bytecode::string_materialization_count;
|
||||||
use prometeu_hal::cartridge::AppMode;
|
use prometeu_hal::cartridge::AppMode;
|
||||||
use prometeu_hal::log::LogService;
|
use prometeu_hal::log::LogService;
|
||||||
use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier};
|
use prometeu_hal::telemetry::{AtomicTelemetry, CertificationConfig, Certifier};
|
||||||
@ -38,6 +39,8 @@ pub struct VirtualMachineRuntime {
|
|||||||
pub paused: bool,
|
pub paused: bool,
|
||||||
pub debug_step_request: bool,
|
pub debug_step_request: bool,
|
||||||
pub inspection_active: bool,
|
pub inspection_active: bool,
|
||||||
|
pub(crate) frame_start_heap_allocations: u64,
|
||||||
|
pub(crate) frame_start_string_materializations: u64,
|
||||||
pub(crate) needs_prepare_entry_call: bool,
|
pub(crate) needs_prepare_entry_call: bool,
|
||||||
pub(crate) boot_time: Instant,
|
pub(crate) boot_time: Instant,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,8 @@ impl VirtualMachineRuntime {
|
|||||||
paused: false,
|
paused: false,
|
||||||
debug_step_request: false,
|
debug_step_request: false,
|
||||||
inspection_active: false,
|
inspection_active: false,
|
||||||
|
frame_start_heap_allocations: 0,
|
||||||
|
frame_start_string_materializations: 0,
|
||||||
needs_prepare_entry_call: false,
|
needs_prepare_entry_call: false,
|
||||||
boot_time,
|
boot_time,
|
||||||
};
|
};
|
||||||
@ -106,6 +108,8 @@ impl VirtualMachineRuntime {
|
|||||||
self.paused = false;
|
self.paused = false;
|
||||||
self.debug_step_request = false;
|
self.debug_step_request = false;
|
||||||
self.inspection_active = false;
|
self.inspection_active = false;
|
||||||
|
self.frame_start_heap_allocations = 0;
|
||||||
|
self.frame_start_string_materializations = 0;
|
||||||
self.needs_prepare_entry_call = false;
|
self.needs_prepare_entry_call = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -493,6 +493,8 @@ fn reset_clears_cartridge_scoped_runtime_state() {
|
|||||||
Some(CrashReport::VmPanic { message: "stale".into(), pc: Some(55) });
|
Some(CrashReport::VmPanic { message: "stale".into(), pc: Some(55) });
|
||||||
runtime.paused = true;
|
runtime.paused = true;
|
||||||
runtime.debug_step_request = true;
|
runtime.debug_step_request = true;
|
||||||
|
runtime.frame_start_heap_allocations = 11;
|
||||||
|
runtime.frame_start_string_materializations = 22;
|
||||||
runtime.needs_prepare_entry_call = true;
|
runtime.needs_prepare_entry_call = true;
|
||||||
|
|
||||||
runtime.reset(&mut vm);
|
runtime.reset(&mut vm);
|
||||||
@ -518,10 +520,56 @@ fn reset_clears_cartridge_scoped_runtime_state() {
|
|||||||
assert!(runtime.last_crash_report.is_none());
|
assert!(runtime.last_crash_report.is_none());
|
||||||
assert!(!runtime.paused);
|
assert!(!runtime.paused);
|
||||||
assert!(!runtime.debug_step_request);
|
assert!(!runtime.debug_step_request);
|
||||||
|
assert_eq!(runtime.frame_start_heap_allocations, 0);
|
||||||
|
assert_eq!(runtime.frame_start_string_materializations, 0);
|
||||||
assert!(!runtime.needs_prepare_entry_call);
|
assert!(!runtime.needs_prepare_entry_call);
|
||||||
assert_eq!(vm.pc(), 0);
|
assert_eq!(vm.pc(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_numeric_happy_path_records_zero_internal_allocations() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code =
|
||||||
|
assemble("PUSH_I32 1\nPUSH_I32 2\nADD\nPOP_N 1\nFRAME_SYNC\nHALT").expect("assemble");
|
||||||
|
let program = serialized_single_function_module(code, vec![]);
|
||||||
|
let cartridge = cartridge_with_program(program, caps::NONE);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
|
|
||||||
|
assert!(report.is_none());
|
||||||
|
let snapshot = runtime.atomic_telemetry.snapshot();
|
||||||
|
assert_eq!(snapshot.vm_heap_allocations, 0);
|
||||||
|
assert_eq!(snapshot.vm_string_materializations, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tick_already_materialized_string_path_records_zero_internal_allocations() {
|
||||||
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
let mut vm = VirtualMachine::default();
|
||||||
|
let mut hardware = Hardware::new();
|
||||||
|
let signals = InputSignals::default();
|
||||||
|
let code = assemble("PUSH_CONST 0\nSET_GLOBAL 0\nGET_GLOBAL 0\nPOP_N 1\nFRAME_SYNC\nHALT")
|
||||||
|
.expect("assemble");
|
||||||
|
let program = serialized_single_function_module_with_consts(
|
||||||
|
code,
|
||||||
|
vec![ConstantPoolEntry::String("steady".into())],
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
let cartridge = cartridge_with_program(program, caps::NONE);
|
||||||
|
|
||||||
|
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||||
|
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||||
|
|
||||||
|
assert!(report.is_none());
|
||||||
|
let snapshot = runtime.atomic_telemetry.snapshot();
|
||||||
|
assert_eq!(snapshot.vm_heap_allocations, 0);
|
||||||
|
assert_eq!(snapshot.vm_string_materializations, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn initialize_vm_failure_clears_previous_identity_and_handles() {
|
fn initialize_vm_failure_clears_previous_identity_and_handles() {
|
||||||
let mut runtime = VirtualMachineRuntime::new(None);
|
let mut runtime = VirtualMachineRuntime::new(None);
|
||||||
|
|||||||
@ -7,6 +7,18 @@ use prometeu_vm::LogicalFrameEndingReason;
|
|||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
impl VirtualMachineRuntime {
|
impl VirtualMachineRuntime {
|
||||||
|
fn refresh_internal_allocation_telemetry(&self, vm: &VirtualMachine) {
|
||||||
|
let heap_allocations =
|
||||||
|
vm.heap().allocation_count().saturating_sub(self.frame_start_heap_allocations);
|
||||||
|
let string_materializations =
|
||||||
|
string_materialization_count().saturating_sub(self.frame_start_string_materializations);
|
||||||
|
|
||||||
|
self.atomic_telemetry.vm_heap_allocations.store(heap_allocations, Ordering::Relaxed);
|
||||||
|
self.atomic_telemetry
|
||||||
|
.vm_string_materializations
|
||||||
|
.store(string_materializations, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
fn bank_telemetry_summary(
|
fn bank_telemetry_summary(
|
||||||
hw: &dyn HardwareBridge,
|
hw: &dyn HardwareBridge,
|
||||||
) -> (BankTelemetry, BankTelemetry, BankTelemetry) {
|
) -> (BankTelemetry, BankTelemetry, BankTelemetry) {
|
||||||
@ -94,6 +106,10 @@ impl VirtualMachineRuntime {
|
|||||||
self.atomic_telemetry.cycles_used.store(0, Ordering::Relaxed);
|
self.atomic_telemetry.cycles_used.store(0, Ordering::Relaxed);
|
||||||
self.atomic_telemetry.syscalls.store(0, Ordering::Relaxed);
|
self.atomic_telemetry.syscalls.store(0, Ordering::Relaxed);
|
||||||
self.atomic_telemetry.vm_steps.store(0, Ordering::Relaxed);
|
self.atomic_telemetry.vm_steps.store(0, Ordering::Relaxed);
|
||||||
|
self.atomic_telemetry.vm_heap_allocations.store(0, Ordering::Relaxed);
|
||||||
|
self.atomic_telemetry.vm_string_materializations.store(0, Ordering::Relaxed);
|
||||||
|
self.frame_start_heap_allocations = vm.heap().allocation_count();
|
||||||
|
self.frame_start_string_materializations = string_materialization_count();
|
||||||
}
|
}
|
||||||
|
|
||||||
let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles);
|
let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles);
|
||||||
@ -177,6 +193,7 @@ impl VirtualMachineRuntime {
|
|||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.heap_used_bytes
|
.heap_used_bytes
|
||||||
.store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed);
|
.store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed);
|
||||||
|
self.refresh_internal_allocation_telemetry(vm);
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.host_cpu_time_us
|
.host_cpu_time_us
|
||||||
.store(start.elapsed().as_micros() as u64, Ordering::Relaxed);
|
.store(start.elapsed().as_micros() as u64, Ordering::Relaxed);
|
||||||
@ -253,6 +270,7 @@ impl VirtualMachineRuntime {
|
|||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
.heap_used_bytes
|
.heap_used_bytes
|
||||||
.store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed);
|
.store(vm.heap().used_bytes.load(Ordering::Relaxed), Ordering::Relaxed);
|
||||||
|
self.refresh_internal_allocation_telemetry(vm);
|
||||||
|
|
||||||
self.atomic_telemetry.frame_index.store(self.logical_frame_index, Ordering::Relaxed);
|
self.atomic_telemetry.frame_index.store(self.logical_frame_index, Ordering::Relaxed);
|
||||||
self.atomic_telemetry
|
self.atomic_telemetry
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use crate::object::{ObjectHeader, ObjectKind};
|
|||||||
use prometeu_bytecode::{HeapRef, Value};
|
use prometeu_bytecode::{HeapRef, Value};
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
||||||
|
|
||||||
/// Internal stored object: header plus opaque payload bytes.
|
/// Internal stored object: header plus opaque payload bytes.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -79,6 +79,8 @@ pub struct Heap {
|
|||||||
|
|
||||||
/// Total bytes currently used by all objects in the heap.
|
/// Total bytes currently used by all objects in the heap.
|
||||||
pub used_bytes: Arc<AtomicUsize>,
|
pub used_bytes: Arc<AtomicUsize>,
|
||||||
|
/// Monotonic count of heap allocation events for internal engineering telemetry.
|
||||||
|
pub allocation_count: Arc<AtomicU64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Heap {
|
impl Heap {
|
||||||
@ -87,11 +89,13 @@ impl Heap {
|
|||||||
objects: Vec::new(),
|
objects: Vec::new(),
|
||||||
free_list: Vec::new(),
|
free_list: Vec::new(),
|
||||||
used_bytes: Arc::new(AtomicUsize::new(0)),
|
used_bytes: Arc::new(AtomicUsize::new(0)),
|
||||||
|
allocation_count: Arc::new(AtomicU64::new(0)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_object(&mut self, obj: StoredObject) -> HeapRef {
|
fn insert_object(&mut self, obj: StoredObject) -> HeapRef {
|
||||||
self.used_bytes.fetch_add(obj.bytes(), Ordering::Relaxed);
|
self.used_bytes.fetch_add(obj.bytes(), Ordering::Relaxed);
|
||||||
|
self.allocation_count.fetch_add(1, Ordering::Relaxed);
|
||||||
if let Some(idx) = self.free_list.pop() {
|
if let Some(idx) = self.free_list.pop() {
|
||||||
debug_assert!(self.objects.get(idx).is_some_and(|slot| slot.is_none()));
|
debug_assert!(self.objects.get(idx).is_some_and(|slot| slot.is_none()));
|
||||||
self.objects[idx] = Some(obj);
|
self.objects[idx] = Some(obj);
|
||||||
@ -411,6 +415,10 @@ impl Heap {
|
|||||||
self.objects.iter().filter(|s| s.is_some()).count()
|
self.objects.iter().filter(|s| s.is_some()).count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allocation_count(&self) -> u64 {
|
||||||
|
self.allocation_count.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
/// Enumerate handles of coroutines that are currently suspended (i.e., not running):
|
/// Enumerate handles of coroutines that are currently suspended (i.e., not running):
|
||||||
/// Ready or Sleeping. These must be treated as GC roots by the runtime so their
|
/// Ready or Sleeping. These must be treated as GC roots by the runtime so their
|
||||||
/// stacks/frames are scanned during mark.
|
/// stacks/frames are scanned during mark.
|
||||||
|
|||||||
@ -642,7 +642,7 @@ impl VirtualMachine {
|
|||||||
let mut out = String::with_capacity(a.string_len_hint() + b.string_len_hint());
|
let mut out = String::with_capacity(a.string_len_hint() + b.string_len_hint());
|
||||||
a.append_to_string(&mut out);
|
a.append_to_string(&mut out);
|
||||||
b.append_to_string(&mut out);
|
b.append_to_string(&mut out);
|
||||||
Ok(Value::String(out.into()))
|
Ok(Value::string(out))
|
||||||
}
|
}
|
||||||
(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))),
|
||||||
|
|||||||
@ -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":"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-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":"done","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"}]}
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
id: PLN-0034
|
id: PLN-0034
|
||||||
ticket: perf-vm-allocation-and-copy-pressure
|
ticket: perf-vm-allocation-and-copy-pressure
|
||||||
title: Plan - Internal Allocation Evidence and Hot Path Measurement
|
title: Plan - Internal Allocation Evidence and Hot Path Measurement
|
||||||
status: review
|
status: done
|
||||||
created: 2026-04-20
|
created: 2026-04-20
|
||||||
completed:
|
completed: 2026-04-20
|
||||||
tags: [perf, runtime, telemetry, allocation, engineering]
|
tags: [perf, runtime, telemetry, allocation, engineering]
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user