Skip to content

Commit e48a863

Browse files
Jeremy Braunfacebook-github-bot
authored andcommitted
improve statement profiler accuracy
Summary: Prior, we kept a `BcStatementLocation` that tracked which BC instruction was the first associated with a statement. This is used for a few things, including breakpoint evaluation, statement profiling, etc. For statement profiling, this would lead to bytecode like this for a statement misreporting all of the time it takes to `Call Bar` as time spent in "whatever the last first-statement instruction was executed during the Call Foo->Return". ``` A: Add B: Call Foo C: Call Bar ``` Previously, only `A` here was marked in BcStatementLocations. Now we mark `A` and `C` as belonging to the statement, too. This causes extra `before_stmt` calls that can then do the additional lookups/accounting in the profiler to credit the correct statement with the time to execute the instruction at `C`. `BcStatementLocations` still differentiates between the "first" instruction for a statement, vs all the others (the `continued` ones), so that components like the debug adaptor can still "break on next statement", rather than "next statement-annotated instruction". Reviewed By: cjhopman Differential Revision: D73038348 fbshipit-source-id: 6804c86ffb2a082b8848b99850dd75e0d6ba1afa
1 parent d48b37c commit e48a863

30 files changed

+137
-31
lines changed

starlark/src/assert/assert.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,10 @@ impl<'a> Assert<'a> {
289289
}
290290
let loader = ReturnFileLoader { modules: &modules };
291291
let ast = AstModule::parse(path, program.to_owned(), &self.dialect)?;
292-
let gc_always = |_span: FileSpanRef, eval: &mut Evaluator| {
293-
eval.trigger_gc();
292+
let gc_always = |_span: FileSpanRef, continued: bool, eval: &mut Evaluator| {
293+
if !continued {
294+
eval.trigger_gc();
295+
}
294296
};
295297
let mut eval = Evaluator::new(module);
296298
eval.enable_static_typechecking(self.static_typechecking);

starlark/src/debug/adapter/implementation.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,16 @@ impl<'a, 'e: 'a> BeforeStmtFuncDyn<'a, 'e> for DapAdapterEvalHookImpl {
111111
fn call<'v>(
112112
&mut self,
113113
span_loc: FileSpanRef,
114+
continued: bool,
114115
eval: &mut Evaluator<'v, 'a, 'e>,
115116
) -> crate::Result<()> {
117+
// The debug adapter should only break on the "initial" instruction that
118+
// makes up any given statement. "Continued" instructions are part of
119+
// the still-executing/previous statement, and should be ignored.
120+
if continued {
121+
return Ok(());
122+
}
123+
116124
let stop = if self.state.disable_breakpoints.load(Ordering::SeqCst) > 0 {
117125
false
118126
} else {

starlark/src/eval/bc/instrs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ impl BcInstrs {
239239
let loop_pad = loop_ends.len() * 2;
240240

241241
if newline {
242-
if let Some(loc) = self.stmt_locs.stmt_at(ip) {
242+
if let Some((loc, _continued)) = self.stmt_locs.stmt_at(ip) {
243243
writeln!(f, "{:loop_pad$} {:ip_pad$} # {}", "", "", loc.span.span)?;
244244
}
245245
} else {

starlark/src/eval/bc/opcode.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,25 @@ impl BcOpcode {
148148
BcOpcode::do_dispatch_all(handler)
149149
}
150150

151+
#[inline(always)]
152+
pub(crate) fn is_call(self) -> bool {
153+
match self {
154+
BcOpcode::Call
155+
| BcOpcode::CallPos
156+
| BcOpcode::CallFrozenDef
157+
| BcOpcode::CallFrozenDefPos
158+
| BcOpcode::CallFrozenNative
159+
| BcOpcode::CallFrozenNativePos
160+
| BcOpcode::CallFrozen
161+
| BcOpcode::CallFrozenPos
162+
| BcOpcode::CallMethod
163+
| BcOpcode::CallMethodPos
164+
| BcOpcode::CallMaybeKnownMethod
165+
| BcOpcode::CallMaybeKnownMethodPos => true,
166+
_ => false,
167+
}
168+
}
169+
151170
/// Get opcode by opcode number.
152171
pub(crate) fn by_number(n: u32) -> Option<BcOpcode> {
153172
struct ByNumber {

starlark/src/eval/bc/writer.rs

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use crate::eval::bc::instr_impl::InstrMov;
4040
use crate::eval::bc::instr_impl::InstrStoreLocalCaptured;
4141
use crate::eval::bc::instrs::BcInstrsWriter;
4242
use crate::eval::bc::instrs::PatchAddr;
43+
use crate::eval::bc::opcode::BcOpcode;
4344
use crate::eval::bc::repr::BC_INSTR_ALIGN;
4445
use crate::eval::bc::slow_arg::BcInstrSlowArg;
4546
use crate::eval::bc::stack_ptr::BcSlot;
@@ -73,6 +74,8 @@ pub(crate) struct BcStatementLocations {
7374
}
7475

7576
impl BcStatementLocations {
77+
const CONTINUED_BIT: u32 = 1 << 31;
78+
7679
pub(crate) fn new() -> Self {
7780
Self {
7881
locs: Vec::new(),
@@ -89,6 +92,7 @@ impl BcStatementLocations {
8992
fn push(&mut self, addr: BcAddr, span: BcStmtLoc) {
9093
let idx = Self::idx_for(addr);
9194
let stmt_idx = self.locs.len().try_into().unwrap();
95+
debug_assert_eq!(0, stmt_idx & Self::CONTINUED_BIT);
9296
self.locs.push(span);
9397
while self.stmts.len() <= idx {
9498
self.stmts.push(u32::MAX);
@@ -97,10 +101,39 @@ impl BcStatementLocations {
97101
self.stmts[idx] = stmt_idx;
98102
}
99103

100-
pub(crate) fn stmt_at(&self, offset: BcAddr) -> Option<&BcStmtLoc> {
104+
fn last_stmt_idx(&self) -> Option<u32> {
105+
for stmt_idx in (&self.stmts).iter().rev() {
106+
if *stmt_idx != u32::MAX {
107+
return Some(*stmt_idx & !Self::CONTINUED_BIT);
108+
}
109+
}
110+
None
111+
}
112+
113+
fn push_prev(&mut self, addr: BcAddr) {
114+
if let Some(stmt_idx) = self.last_stmt_idx() {
115+
let idx = Self::idx_for(addr);
116+
while self.stmts.len() <= idx {
117+
self.stmts.push(u32::MAX);
118+
}
119+
// If the preceding statement ended in a call opcode, and the
120+
// current opcode is the start of a new statement, it may already be
121+
// marked. If so, preserve that, rather than marking it as a part of
122+
// the previous statement (which it is not).
123+
if self.stmts[idx] == u32::MAX {
124+
self.stmts[idx] = stmt_idx | Self::CONTINUED_BIT;
125+
}
126+
}
127+
}
128+
129+
pub(crate) fn stmt_at(&self, offset: BcAddr) -> Option<(&BcStmtLoc, bool)> {
101130
match self.stmts.get(Self::idx_for(offset)) {
102131
None | Some(&u32::MAX) => None,
103-
Some(v) => Some(&self.locs[*v as usize]),
132+
Some(v) => {
133+
let continued = 0 != (v & Self::CONTINUED_BIT);
134+
let idx = v & !Self::CONTINUED_BIT;
135+
Some((&self.locs[idx as usize], continued))
136+
}
104137
}
105138
}
106139
}
@@ -123,8 +156,10 @@ pub(crate) struct BcWriter<'f> {
123156
instrs: BcInstrsWriter,
124157
/// Instruction spans, used for errors.
125158
slow_args: Vec<(BcAddr, BcInstrSlowArg)>,
126-
/// For each statement, will store the span and the BcAddr for the first instruction.
159+
/// For each statement, will store the span for the first instruction and any instruction after a call.
127160
stmt_locs: BcStatementLocations,
161+
/// The last-written opcode
162+
last_opcode: BcOpcode,
128163
/// Current stack size.
129164
stack_size: u32,
130165
/// Local slot count.
@@ -159,6 +194,7 @@ impl<'f> BcWriter<'f> {
159194
instrs: BcInstrsWriter::new(),
160195
slow_args: Vec::new(),
161196
stmt_locs: BcStatementLocations::new(),
197+
last_opcode: BcOpcode::End,
162198
stack_size: 0,
163199
local_names,
164200
definitely_assigned,
@@ -176,6 +212,7 @@ impl<'f> BcWriter<'f> {
176212
instrs,
177213
slow_args: spans,
178214
stmt_locs,
215+
last_opcode: _,
179216
stack_size,
180217
local_names,
181218
definitely_assigned,
@@ -219,6 +256,15 @@ impl<'f> BcWriter<'f> {
219256
slow_arg: BcInstrSlowArg,
220257
arg: I::Arg,
221258
) -> (BcAddr, *const I::Arg) {
259+
// If the previously written instruction was a form of a call
260+
// instruction, instrument this instruction with the current statement
261+
// span so that the time this and following instructions take can be
262+
// attributed to the correct statement.
263+
if self.last_opcode.is_call() {
264+
self.stmt_locs.push_prev(self.ip());
265+
}
266+
self.last_opcode = BcOpcode::for_instr::<I>();
267+
222268
self.slow_args.push((self.ip(), slow_arg));
223269
self.instrs.write::<I>(arg)
224270
}

starlark/src/eval/runtime/before_stmt.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,23 @@ pub(crate) struct BeforeStmt<'a, 'e: 'a> {
3535
// TODO(cjhopman): pull DAP into the crate, and hide this function.
3636
#[doc(hidden)]
3737
pub enum BeforeStmtFunc<'a, 'e: 'a> {
38-
Fn(&'a dyn for<'v1> Fn(FileSpanRef, &mut Evaluator<'v1, 'a, 'e>)),
38+
Fn(&'a dyn for<'v1> Fn(FileSpanRef, bool, &mut Evaluator<'v1, 'a, 'e>)),
3939
Dyn(Box<dyn BeforeStmtFuncDyn<'a, 'e>>),
4040
}
4141

4242
impl<'a, 'e: 'a> BeforeStmtFunc<'a, 'e> {
4343
pub(crate) fn call<'v>(
4444
&mut self,
4545
span: FileSpanRef,
46+
continued: bool,
4647
eval: &mut Evaluator<'v, 'a, 'e>,
4748
) -> crate::Result<()> {
4849
match self {
4950
BeforeStmtFunc::Fn(f) => {
50-
f(span, eval);
51+
f(span, continued, eval);
5152
Ok(())
5253
}
53-
BeforeStmtFunc::Dyn(d) => d.call(span, eval),
54+
BeforeStmtFunc::Dyn(d) => d.call(span, continued, eval),
5455
}
5556
}
5657
}
@@ -65,6 +66,7 @@ pub trait BeforeStmtFuncDyn<'a, 'e: 'a> {
6566
fn call<'v>(
6667
&mut self,
6768
span: FileSpanRef,
69+
continued: bool,
6870
eval: &mut Evaluator<'v, 'a, 'e>,
6971
) -> crate::Result<()>;
7072
}
@@ -75,10 +77,10 @@ impl<'a, 'e: 'a> BeforeStmt<'a, 'e> {
7577
}
7678
}
7779

78-
impl<'a, 'e: 'a> From<&'a dyn for<'v1> Fn(FileSpanRef, &mut Evaluator<'v1, 'a, 'e>)>
80+
impl<'a, 'e: 'a> From<&'a dyn for<'v1> Fn(FileSpanRef, bool, &mut Evaluator<'v1, 'a, 'e>)>
7981
for BeforeStmtFunc<'a, 'e>
8082
{
81-
fn from(value: &'a dyn for<'v1> Fn(FileSpanRef, &mut Evaluator<'v1, 'a, 'e>)) -> Self {
83+
fn from(value: &'a dyn for<'v1> Fn(FileSpanRef, bool, &mut Evaluator<'v1, 'a, 'e>)) -> Self {
8284
Self::Fn(value)
8385
}
8486
}

starlark/src/eval/runtime/evaluator.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ impl<'v, 'a, 'e: 'a> Evaluator<'v, 'a, 'e> {
309309
}
310310
ProfileMode::Statement | ProfileMode::Coverage => {
311311
self.stmt_profile.enable();
312-
self.before_stmt_fn(&|span, eval| eval.stmt_profile.before_stmt(span));
312+
self.before_stmt_fn(&|span, _continued, eval| eval.stmt_profile.before_stmt(span));
313313
}
314314
ProfileMode::TimeFlame => {
315315
self.time_flame_profile.enable();
@@ -422,7 +422,7 @@ impl<'v, 'a, 'e: 'a> Evaluator<'v, 'a, 'e> {
422422

423423
pub(crate) fn before_stmt_fn(
424424
&mut self,
425-
f: &'a dyn for<'v1> Fn(FileSpanRef, &mut Evaluator<'v1, 'a, 'e>),
425+
f: &'a dyn for<'v1> Fn(FileSpanRef, bool, &mut Evaluator<'v1, 'a, 'e>),
426426
) {
427427
self.before_stmt(f.into())
428428
}
@@ -891,8 +891,8 @@ pub(crate) struct EvalCallbacksEnabled<'a> {
891891
impl<'a> EvalCallbacksEnabled<'a> {
892892
fn before_stmt(&mut self, eval: &mut Evaluator, ip: BcPtrAddr) -> crate::Result<()> {
893893
let offset = ip.offset_from(self.bc_start_ptr);
894-
if let Some(loc) = self.stmt_locs.stmt_at(offset) {
895-
before_stmt(loc.span, eval)?;
894+
if let Some((loc, continued)) = self.stmt_locs.stmt_at(offset) {
895+
before_stmt(loc.span, continued, eval)?;
896896
}
897897
Ok(())
898898
}
@@ -916,11 +916,15 @@ impl<'a> EvaluationCallbacks for EvalCallbacksEnabled<'a> {
916916
}
917917
}
918918

919-
// This function should be called before every meaningful statement.
919+
// This function should be called before every meaningful statement (continued==false), and after a call that returns into a previously entered statement (continued==true).
920920
// The purposes are GC, profiling and debugging.
921921
//
922922
// This function is called only if `before_stmt` is set before compilation start.
923-
pub(crate) fn before_stmt(span: FrameSpan, eval: &mut Evaluator) -> crate::Result<()> {
923+
pub(crate) fn before_stmt(
924+
span: FrameSpan,
925+
continued: bool,
926+
eval: &mut Evaluator,
927+
) -> crate::Result<()> {
924928
assert!(
925929
eval.eval_instrumentation.before_stmt.enabled(),
926930
"this code should only be called if `before_stmt` is set"
@@ -931,7 +935,7 @@ pub(crate) fn before_stmt(span: FrameSpan, eval: &mut Evaluator) -> crate::Resul
931935
let mut result = Ok(());
932936
for f in &mut fs {
933937
if result.is_ok() {
934-
result = f.call(span.span.file_span_ref(), eval);
938+
result = f.call(span.span.file_span_ref(), continued, eval);
935939
}
936940
}
937941
let added = eval.eval_instrumentation.change(|eval_instrumentation| {

starlark/src/eval/runtime/profile/golden/statement.csv.golden

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@
55
# ```
66

77
File,Span,Duration(s),Count
8-
"TOTAL","",2.128,304
9-
"test.star","7:13-19",1.400,200
10-
"test.star","3:5-9:1",0.140,20
8+
"TOTAL","",3.864,552
9+
"test.star","7:13-19",2.800,400
10+
"test.star","3:5-9:1",0.280,40
11+
"test.star","13:9-27",0.280,40
1112
"test.star","6:9-9:1",0.140,20
1213
"test.star","12:9-17",0.140,20
13-
"test.star","13:9-27",0.140,20
14+
"test.star","11:5-14:1",0.056,8
1415
"test.star","10:5-11",0.028,4
15-
"test.star","11:5-14:1",0.028,4
1616
"test.star","14:5-13",0.028,4
17+
"test.star","16:1-7",0.021,3
18+
"test.star","17:1-7",0.021,3
19+
"test.star","18:1-7",0.021,3
20+
"test.star","20:1-11",0.021,3
1721
"test.star","2:1-9:1",0.014,2
1822
"test.star","9:1-16:1",0.014,2
19-
"test.star","16:1-7",0.014,2
20-
"test.star","17:1-7",0.014,2
21-
"test.star","18:1-7",0.014,2
22-
"test.star","20:1-11",0.014,2

starlark/src/tests/bc/golden/call.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ Instructions:
2626
72: Const 40 ->&6
2727
96: Const 50 ->&7
2828
120: CallFrozenNative noop {&3..&8 2 p q r *&0 **&1} instrs.star.bzl:2:5-10:6 ->&2
29+
# instrs.star.bzl:2:5-10:6
2930
208: ReturnConst None
3031
224: End

starlark/src/tests/bc/golden/def_inline_const_args_inlined.golden

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ Instructions:
1919
0: Const True ->&1
2020
24: Const 10 ->&2
2121
48: CallFrozenNativePos noop &1..&3 instrs.star.bzl:2:12-22 ->&0
22+
# instrs.star.bzl:6:5-25
2223
104: Return &0
2324
112: End

0 commit comments

Comments
 (0)