-
-
Notifications
You must be signed in to change notification settings - Fork 460
Add Vm
trace functionality and add trace to boa_wasm
#3227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 10 commits
0baa009
a1900c8
2ae0204
e0e64d2
a07ba2a
8099c04
39f973e
ed1cf6c
b20bb5b
1b95f81
94045e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,17 +21,24 @@ mod code_block; | |
mod completion_record; | ||
mod inline_cache; | ||
mod opcode; | ||
#[cfg(feature = "trace")] | ||
pub mod trace; | ||
|
||
mod runtime_limits; | ||
|
||
#[cfg(feature = "flowgraph")] | ||
pub mod flowgraph; | ||
|
||
pub(crate) use inline_cache::InlineCache; | ||
|
||
#[cfg(feature = "trace")] | ||
use trace::VmTrace; | ||
|
||
// TODO: see if this can be exposed on all features. | ||
#[allow(unused_imports)] | ||
pub(crate) use opcode::{Instruction, InstructionIterator, Opcode, VaryingOperandKind}; | ||
pub use runtime_limits::RuntimeLimits; | ||
|
||
pub use { | ||
call_frame::{CallFrame, GeneratorResumeKind}, | ||
code_block::CodeBlock, | ||
|
@@ -76,7 +83,7 @@ pub struct Vm { | |
pub(crate) realm: Realm, | ||
|
||
#[cfg(feature = "trace")] | ||
pub(crate) trace: bool, | ||
pub(crate) trace: VmTrace, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A thought that I've had over the last day or so is whether it would be worthwhile redesigning so that the struct Vm<T: ExecutionTrace=()> {
...
trace: T
} The only issue with this that I haven't totally thought through is that I'm not sure we'd be able to feature flag the fields (all though we could probably still feature flag the method calls) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the current design is fine. What would be the benefit of having it as a trait? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly I'm not sure if I'm totally in love with the Edit: Using a trait would allow the trace to be defined at compile time essentially. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Imo the
I'm not sure if that is a benefit. The upside would be reducing dynamic dispatch, but if you enable tracing, you probably do not care about that last bit of performance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a nice feature! And you're right, if someone enables tracing then they are probably not concerned about performance. But if it's switched to a trait that doesn't mean that there couldn't be an implementation of the trait that can be updated at runtime. And if we were to update this to something without dynamic dispatching, then we even provide an impl of the trait that could be updated at runtime. The primary difference would be that it would be a user deciding to use dynamic dispatching over Boa locking the user into using it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say the current implementation is good engough. If there are shortcomings we could change it. I would prefer that to adding a generic to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do think we could offer "combinators" for tracers. Instead of having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😕 Maybe. There's probably a better option with how pub trait Tracer: std::fmt::Debug {
fn trace_instruction(&self, ctx: &TraceDecisionCtx<'_>) -> InstructionTraceAction;
fn trace_bytecode(&self, ctx: &TraceDecisionCtx<'_>) -> BytecodeTraceAction;
fn emit(&self, event: &TraceEvent<'_>);
} Where pub enum TraceEvent<'a> {
NewCallFrame(&'a NewFrameEvent<'a>),
ExitCallFrame(&'a ExitFrameEvent<'a>),
Instruction(&'a InstructionTrace),
ByteCode(&'a BytecodeTraceEvent),
} But that design still has |
||
} | ||
|
||
/// Active runnable in the current vm context. | ||
|
@@ -108,7 +115,7 @@ impl Vm { | |
native_active_function: None, | ||
realm, | ||
#[cfg(feature = "trace")] | ||
trace: false, | ||
trace: VmTrace::default(), | ||
} | ||
} | ||
|
||
|
@@ -181,6 +188,9 @@ impl Vm { | |
} | ||
|
||
self.frames.push(frame); | ||
|
||
#[cfg(feature = "trace")] | ||
self.trace.trace_call_frame(self); | ||
} | ||
|
||
pub(crate) fn push_frame_with_stack( | ||
|
@@ -193,6 +203,9 @@ impl Vm { | |
self.push(function); | ||
|
||
self.push_frame(frame); | ||
|
||
#[cfg(feature = "trace")] | ||
self.trace.trace_call_frame(self); | ||
} | ||
|
||
pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> { | ||
|
@@ -264,38 +277,6 @@ pub(crate) enum CompletionType { | |
|
||
#[cfg(feature = "trace")] | ||
impl Context { | ||
const COLUMN_WIDTH: usize = 26; | ||
const TIME_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH / 2; | ||
const OPCODE_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH; | ||
const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH; | ||
const NUMBER_OF_COLUMNS: usize = 4; | ||
|
||
pub(crate) fn trace_call_frame(&self) { | ||
let msg = if self.vm.frames.last().is_some() { | ||
format!( | ||
" Call Frame -- {} ", | ||
self.vm.frame().code_block().name().to_std_string_escaped() | ||
) | ||
} else { | ||
" VM Start ".to_string() | ||
}; | ||
|
||
println!("{}", self.vm.frame().code_block); | ||
println!( | ||
"{msg:-^width$}", | ||
width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 | ||
); | ||
println!( | ||
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {:<OPERAND_COLUMN_WIDTH$} Stack\n", | ||
"Time", | ||
"Opcode", | ||
"Operands", | ||
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH, | ||
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH, | ||
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH, | ||
); | ||
} | ||
|
||
fn trace_execute_instruction<F>(&mut self, f: F) -> JsResult<CompletionType> | ||
where | ||
F: FnOnce(Opcode, &mut Context) -> JsResult<CompletionType>, | ||
|
@@ -368,13 +349,12 @@ impl Context { | |
VaryingOperandKind::U32 => ".U32", | ||
}; | ||
|
||
println!( | ||
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}", | ||
format!("{}μs", duration.as_micros()), | ||
format!("{}{varying_operand_kind}", opcode.as_str()), | ||
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH, | ||
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH, | ||
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH, | ||
self.vm.trace.trace_instruction( | ||
duration.as_micros(), | ||
varying_operand_kind, | ||
opcode.as_str(), | ||
&operands, | ||
&stack, | ||
); | ||
|
||
result | ||
|
@@ -417,7 +397,7 @@ impl Context { | |
} | ||
|
||
#[cfg(feature = "trace")] | ||
let result = if self.vm.trace || self.vm.frame().code_block.traceable() { | ||
let result = if self.vm.trace.should_trace(self.vm.frame()) { | ||
self.trace_execute_instruction(f) | ||
} else { | ||
self.execute_instruction(f) | ||
|
@@ -445,6 +425,8 @@ impl Context { | |
} | ||
self.vm.environments.truncate(env_fp); | ||
self.vm.stack.truncate(fp); | ||
#[cfg(feature = "trace")] | ||
self.vm.trace.trace_frame_end(&self.vm, "Throw"); | ||
return ControlFlow::Break(CompletionRecord::Throw(err)); | ||
} | ||
|
||
|
@@ -469,6 +451,9 @@ impl Context { | |
let fp = self.vm.frame().fp() as usize; | ||
self.vm.stack.truncate(fp); | ||
|
||
#[cfg(feature = "trace")] | ||
self.vm.trace.trace_frame_end(&self.vm, "Return"); | ||
|
||
let result = self.vm.take_return_value(); | ||
if self.vm.frame().exit_early() { | ||
return ControlFlow::Break(CompletionRecord::Normal(result)); | ||
|
@@ -480,6 +465,8 @@ impl Context { | |
CompletionType::Throw => { | ||
let mut fp = self.vm.frame().fp(); | ||
let mut env_fp = self.vm.frame().env_fp; | ||
#[cfg(feature = "trace")] | ||
self.vm.trace.trace_frame_end(&self.vm, "Throw"); | ||
if self.vm.frame().exit_early() { | ||
self.vm.environments.truncate(env_fp as usize); | ||
self.vm.stack.truncate(fp as usize); | ||
|
@@ -519,6 +506,9 @@ impl Context { | |
} | ||
// Early return immediately. | ||
CompletionType::Yield => { | ||
#[cfg(feature = "trace")] | ||
self.vm.trace.trace_frame_end(&self.vm, "Yield"); | ||
|
||
let result = self.vm.take_return_value(); | ||
if self.vm.frame().exit_early() { | ||
return ControlFlow::Break(CompletionRecord::Return(result)); | ||
|
@@ -538,11 +528,6 @@ impl Context { | |
pub(crate) async fn run_async_with_budget(&mut self, budget: u32) -> CompletionRecord { | ||
let _timer = Profiler::global().start_event("run_async_with_budget", "vm"); | ||
|
||
#[cfg(feature = "trace")] | ||
if self.vm.trace { | ||
self.trace_call_frame(); | ||
} | ||
|
||
let mut runtime_budget: u32 = budget; | ||
|
||
loop { | ||
|
@@ -563,11 +548,6 @@ impl Context { | |
pub(crate) fn run(&mut self) -> CompletionRecord { | ||
let _timer = Profiler::global().start_event("run", "vm"); | ||
|
||
#[cfg(feature = "trace")] | ||
if self.vm.trace { | ||
self.trace_call_frame(); | ||
} | ||
|
||
loop { | ||
match self.execute_one(Opcode::execute) { | ||
ControlFlow::Continue(()) => {} | ||
|
Uh oh!
There was an error while loading. Please reload this page.