diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index f01c9bb47dd..225b576503f 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -21,9 +21,6 @@ use std::{future::Future, ops::ControlFlow, path::Path, pin::Pin, task}; #[cfg(feature = "trace")] use crate::sys::time::Instant; -#[cfg(feature = "trace")] -use std::fmt::Write as _; - #[allow(unused_imports)] pub(crate) use opcode::{Instruction, InstructionIterator, Opcode}; @@ -277,26 +274,100 @@ impl Stack { } #[cfg(feature = "trace")] - /// Display the stack trace of the current frame. + const MAX_VALUE_LEN: usize = 18; + #[cfg(feature = "trace")] + const MAX_STACK_WIDTH: usize = 68; + + #[cfg(feature = "trace")] + fn raw_value(value: &JsValue) -> String { + match value { + v if v.is_callable() => "func".to_string(), + v if v.is_object() => "obj".to_string(), + v if v.is_undefined() => "und".to_string(), + v if v.is_null() => "null".to_string(), + v => v.display().to_string(), + } + } + + #[cfg(feature = "trace")] + fn truncate_display(val: &str) -> String { + if val.len() <= Self::MAX_VALUE_LEN { + return val.to_string(); + } + let mut end = Self::MAX_VALUE_LEN - 2; + while !val.is_char_boundary(end) && end > 0 { + end -= 1; + } + format!("{}..", &val[..end]) + } + + #[cfg(feature = "trace")] fn display_trace(&self, frame: &CallFrame, frame_count: usize) -> String { - let mut string = String::from("[ "); - for (i, (j, value)) in self.stack.iter().enumerate().rev().enumerate() { - match value { - value if value.is_callable() => string.push_str("[function]"), - value if value.is_object() => string.push_str("[object]"), - value => string.push_str(&value.display().to_string()), - } + let total = self.stack.len(); + if total == 0 { + return "[ ]".to_string(); + } + + let mut groups: Vec<(String, usize, Option)> = Vec::new(); + let mut force_truncate = false; - if frame.frame_pointer() == j { - let _ = write!(string, " |{frame_count}|"); - } else if i + 1 != self.stack.len() { - string.push(','); + // Lazily group values to avoid eagerly evaluating `raw_value` for the entire stack. + for (idx, v) in self.stack.iter().enumerate().rev() { + let is_frame = frame.frame_pointer() == idx; + let raw = Self::raw_value(v); + + if !is_frame + && let Some(last) = groups.last_mut() + && last.0 == raw + && last.2.is_none() + { + last.1 += 1; + } else { + let marker = if is_frame { Some(frame_count) } else { None }; + groups.push((raw, 1, marker)); + + // If groups is large enough to mathematically guarantee overflowing the display width, + // we can stop evaluating to save instruction budget / time. + if groups.len() > Self::MAX_STACK_WIDTH / 2 { + force_truncate = true; + break; + } } + } + + let mut string = String::from("[ "); + let mut truncated = force_truncate; + let suffix = format!(".. ({total} total) ]"); + + for (i, (val, count, marker)) in groups.iter().enumerate() { + let display_val = Self::truncate_display(val); + let part = if *count > 1 { + format!("{display_val} (x{count})") + } else { + display_val + }; + + let separator = if let Some(fc) = marker { + format!(" |{fc}|") + } else if i + 1 < groups.len() { + ",".to_string() + } else { + String::new() + }; - string.push(' '); + let addition = format!("{part}{separator} "); + if string.len() + addition.len() + suffix.len() > Self::MAX_STACK_WIDTH { + truncated = true; + break; + } + string.push_str(&addition); } - string.push(']'); + if truncated { + string.push_str(&suffix); + } else { + string.push(']'); + } string } }