-
-
Notifications
You must be signed in to change notification settings - Fork 400
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
Add Vm
trace functionality and add trace to boa_wasm
#3227
base: main
Are you sure you want to change the base?
Conversation
Test262 conformance changes
|
Seeing this, I think it would be worth to create a |
Kind of what I was going for. I was initially thinking of how to plug the trace output into a tui out of curiosity which led me to the wasm. I don't think it should be too crazy to switch it over to being trait based. 😄 |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3227 +/- ##
==========================================
- Coverage 47.24% 47.21% -0.03%
==========================================
Files 476 476
Lines 46892 46084 -808
==========================================
- Hits 22154 21760 -394
+ Misses 24738 24324 -414 ☔ View full report in Codecov by Sentry. |
Switched the custom closures bits over to a trait. It may be possible to go even further with the trait than currently implemented and selectively emit |
d40ba61
to
58ca1b2
Compare
@HalidOdat and @jedel1043 will review this to see if we want to expose the instructions or if this is good enough. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice API! This just needs a rebase, since it's still using the old std::time::Instant
. I also have some suggestions and a small question.
boa_engine/src/vm/trace.rs
Outdated
if self.is_full_trace() { | ||
self.trace_compiled_bytecode(vm, interner); | ||
self.call_frame_header(vm); | ||
} else if self.is_partial_trace() && vm.frame().code_block().traceable() { | ||
if !vm.frame().code_block().frame_traced() { | ||
self.trace_current_bytecode(vm, interner); | ||
vm.frame().code_block().set_frame_traced(true); | ||
} | ||
self.call_frame_header(vm); | ||
self.activate(); | ||
} else { | ||
self.call_frame_header(vm); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think we can reduce the complexity of this by adding a Trace::should_trace(code: &Gc<CodeBlock>) -> ShouldTrace
method with an enum defined by:
enum ShouldTrace {
No,
OnlyInstructions,
Full
}
Tracers can carry a traceable
list of Gc<Codeblock>
to determine if a code should be traced, and also a first_trace
list to determine which codeblocks haven't been traced so that it displays their bytecode first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still need to work on this portion. The rest is rebased, so it shouldn't take too long 😄
EDIT: Actually I was looking at the above, and I'm not really sure why that was so convoluted. I cleaned up the logic a bit, so it may be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added an enum (went with TraceAction
over ShouldTrace
), but now some of that logic is loaded into should_trace
. Let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really nice! Though, I think I didn't explain myself good enough. The goal of adding that enum was so that we can move the "should be traced partially or fully" logic from VmTracer
to the tracers itself, by adding a Tracer::should_trace(&self, code: Gc<CodeBlock>) -> TraceAction
method. Then, implementors would be the ones taking care of choosing when to just print the instructions vs the compiled bytecode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be exposing that much of the Vm
internals to Tracer
? Part of the current implementation is to only expose preformatted strings in a bit more of a "controlled environment" so to speak, which included handling the logic aspect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure we already expose CodeBlock
publicly, and users cannot do much with it aside from getting its address and name, so I think it should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm playing around with the update, and it's nicer in some places, but feels a lot more convoluted in others. I'm also not entirely sure on how to calling multiple $boa.function.traceable()
might work as calling the function would remove the previous Tracer
, which may cause problems on generators and async functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say that we should try to find an architecture that allows us to always use the same Tracer
, mostly for simplicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the late review.
Besides jedel1043's comments, this looks good me! :)
58ca1b2
to
89344fd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did another review, just some small nitpicks that don't block merging. The rest looks good to me! :)
abacf7f
to
80d8ac0
Compare
Made some changes to have |
Looks good to me! Just needs a rebase :) |
606f48e
to
6fa254b
Compare
@@ -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 comment
The 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 Trace
is a trait on Vm
.
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 comment
The 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 comment
The 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 Vec<Box<dyn Tracer>>
on VmTrace
. It works and improves the current functionality, but I'm mostly just thinking whether there may be a bit of a better design 😕 That being said, I'm not sure a theoretically better design out weighs the functionality here.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Imo the Vec<Box<dyn Tracer>>
is totally fine for this use case.
Using a trait would allow the trace to be defined at compile time essentially.
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.
Being able to change the tracing at runtime also seems like a nice feature right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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 comment
The 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 Vm
struct. But if you think it is significantly better, go for it :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think we could offer "combinators" for tracers. Instead of having Vec<Box<dyn Tracer>>
, why not something like Box<dyn Tracer>
, where we impl<T: Tracer, U: Tracer> Tracer for (T, U)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😕 Maybe.
There's probably a better option with how Tracer
is defined too. I was playing around with a new design and have something like:
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 TraceEvent
is something like:
pub enum TraceEvent<'a> {
NewCallFrame(&'a NewFrameEvent<'a>),
ExitCallFrame(&'a ExitFrameEvent<'a>),
Instruction(&'a InstructionTrace),
ByteCode(&'a BytecodeTraceEvent),
}
But that design still has VmTrace
using a Vec
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just found some leftover comments / typos.
@@ -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 comment
The 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?
This is the branch that was used for the
web-debugger
concept.This branch is technically ready to be merged, but thought I'd post it as a draft for now to get feedback as it has some API changes along with quite a few changes to the
Vm
trace overall.It changes the following:
BoaJs
object and anevaluate_with_debug_hooks
function inboa_wasm
VmTrace
(not the biggest fan of the name here if anyone has other ideas) for handling tracing and allowing users to define handle trace themselves via "registering" a function (defaults to println).For reference, the standard trace output would now print like the below.
Let me know what you think 😄