From 58f8cc102e8552ee272e63bd0b794ee8eed8ff1d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:01:37 +0000 Subject: [PATCH] perf: eliminate redundant stack allocations in VM instruction tracing Added a `data_slice` method to `ValueStack` to provide zero-copy slice access. Updated the instruction tracer in `executor.rs` to use this slice instead of cloning the entire stack via `get_dump()` on every instruction execution. Co-authored-by: Tcode-Motion <188012755+Tcode-Motion@users.noreply.github.com> --- .jules/bolt.md | 3 +++ runtime/vm/src/executor.rs | 2 +- runtime/vm/src/stack.rs | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index f86707a6..c80a25c7 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -5,3 +5,6 @@ ## 2024-05-18 - Rust lifetime limits reuse of mutably borrowed locals in hot VM loop **Learning:** In `runtime/vm/src/executor.rs`, the main interpreter loop `execute_loop` defines variables `frame` and `func` for the current execution frame and function. While avoiding redundant deep indexing (e.g. `self.frames.last_mut().ok_or(VMError::StackUnderflow)?;`) inside match arms for `Opcode::Jump`, `Opcode::JumpIfTrue`, `Opcode::JumpIfFalse`, `Opcode::Try`, and `Opcode::EndTry` by reusing the existing local `frame` variable reduces bounds checks and overhead, this local `frame` reference cannot be reused inside other match arms like `Opcode::Return` without triggering severe Rust borrow checker issues (e.g., cannot call `self.frames.len()` while `self.frames` is mutably borrowed via `frame`). The previous implementation relied on Non-Lexical Lifetimes (NLL) implicitly ending the borrow of `frame` before reaching opcodes that needed to borrow `self.frames` again. Removing the redundant inner lookups caused the compiler to extend the mutable borrow across the entire loop iteration if not careful, but safely removing them just from control flow opcodes where no further frame manipulation is needed works correctly. **Action:** Be extremely cautious when extending the lifetime of mutable borrows (especially on central state like a call stack) across large `match` blocks in Rust interpreters, as even correct performance optimizations can easily introduce fatal compilation errors if the borrow inadvertently overlaps with other mutable or immutable accesses. +## 2024-05-18 - Removed redundant clone of VM stack during trace logs +**Learning:** In `runtime/vm/src/executor.rs`, the debugging instruction trace `self.debugger.trace_instruction` was cloning the entire VM stack using `&self.stack.get_dump()` for every single instruction executed. This caused significant `O(N)` overhead inside the main fetch-decode-execute loop just to format debug output. A new `data_slice()` method was added to `ValueStack` to provide zero-copy slice access (`&[RuntimeValue]`) instead, completely eliminating the allocation overhead. +**Action:** Always scrutinize deep clones in logging, tracing, or hot path loops. Use slice references (`&[T]`) instead of `Vec::clone` when the caller only needs read-only access to a collection. diff --git a/runtime/vm/src/executor.rs b/runtime/vm/src/executor.rs index d4032d97..718997a7 100644 --- a/runtime/vm/src/executor.rs +++ b/runtime/vm/src/executor.rs @@ -40,7 +40,7 @@ impl VM { ip, inst_op, inst_operands, - &self.stack.get_dump(), + self.stack.data_slice(), ); } diff --git a/runtime/vm/src/stack.rs b/runtime/vm/src/stack.rs index 842abe15..f064ad7f 100644 --- a/runtime/vm/src/stack.rs +++ b/runtime/vm/src/stack.rs @@ -74,4 +74,9 @@ impl ValueStack { pub fn get_dump(&self) -> Vec { self.data.clone() } + + /// Returns a slice of the stack data. + pub fn data_slice(&self) -> &[RuntimeValue] { + &self.data + } }