Skip to content

Commit

Permalink
Inlined functions profiling (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
piotmag769 authored Jul 1, 2024
1 parent 3742e24 commit 5d1cdcd
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 119 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `--max-function-stack-trace-depth` allowing to specify maximum depth of the function tree in function level profiling
- `--split-generics` flag allowing to differentiate between non-inlined generics monomorphised with different types
- `--show-inlined-functions` flag to show inlined functions in the profile. Requires Scarb >= 2.7.0 and setting
`unstable-add-statements-functions-debug-info = true` in `[cairo]` section of Scarb.toml.

## [0.3.0] - 2024-05-20

Expand Down
5 changes: 5 additions & 0 deletions crates/cairo-profiler/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ struct Cli {
/// E.g. treat `function<felt252>` as different from `function<u8>`.
#[arg(long)]
split_generics: bool,

/// Show inlined function in a trace tree. Requires Scarb >= 2.7.0 and setting
/// `unstable-add-statements-functions-debug-info = true` in `[cairo]` section of Scarb.toml.
#[arg(long)]
show_inlined_functions: bool,
}

fn main() -> Result<()> {
Expand Down
4 changes: 4 additions & 0 deletions crates/cairo-profiler/src/profiler_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub struct ProfilerConfig {
pub show_details: bool,
pub max_function_stack_trace_depth: usize,
pub split_generics: bool,
pub show_inlined_functions: bool,
}

impl From<&Cli> for ProfilerConfig {
Expand All @@ -12,20 +13,23 @@ impl From<&Cli> for ProfilerConfig {
show_details: cli.show_details,
max_function_stack_trace_depth: cli.max_function_stack_trace_depth,
split_generics: cli.split_generics,
show_inlined_functions: cli.show_inlined_functions,
}
}
}

pub struct FunctionLevelConfig {
pub max_function_stack_trace_depth: usize,
pub split_generics: bool,
pub show_inlined_functions: bool,
}

impl From<&ProfilerConfig> for FunctionLevelConfig {
fn from(profiler_config: &ProfilerConfig) -> FunctionLevelConfig {
FunctionLevelConfig {
max_function_stack_trace_depth: profiler_config.max_function_stack_trace_depth,
split_generics: profiler_config.split_generics,
show_inlined_functions: profiler_config.show_inlined_functions,
}
}
}
16 changes: 9 additions & 7 deletions crates/cairo-profiler/src/sierra_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub struct CompiledArtifactsCache(HashMap<Utf8PathBuf, CompiledArtifacts>);
pub struct CompiledArtifacts {
pub sierra_program: SierraProgram,
pub casm_debug_info: CairoProgramDebugInfo,
pub maybe_statements_functions_map: Option<StatementsFunctionsMap>,
pub statements_functions_map: Option<StatementsFunctionsMap>,
}

pub enum SierraProgram {
Expand All @@ -37,11 +37,13 @@ impl SierraProgram {
}
}

#[allow(dead_code)]
/// This struct maps sierra statement index to a stack of fully qualified paths of cairo functions
/// consisting of a function which caused the statement to be generated and all functions that were
/// inlined or generated along the way, up to the first non-inlined function from the original code.
/// The map represents the stack from the least meaningful elements.
#[derive(Default, Clone)]
pub struct StatementsFunctionsMap(HashMap<StatementIdx, Vec<FunctionName>>);

#[allow(dead_code)]
impl StatementsFunctionsMap {
pub fn get(&self, key: StatementIdx) -> Option<&Vec<FunctionName>> {
self.0.get(&key)
Expand Down Expand Up @@ -79,7 +81,7 @@ pub fn compile_sierra_and_add_compiled_artifacts_to_cache(
.extract_sierra_program()
.context("Failed to extract sierra program from contract code")?;

let maybe_statements_functions_map =
let statements_functions_map =
maybe_get_statements_functions_map(contract_class.sierra_program_debug_info);

let contract_class = ContractClass {
Expand All @@ -101,7 +103,7 @@ pub fn compile_sierra_and_add_compiled_artifacts_to_cache(
CompiledArtifacts {
sierra_program: SierraProgram::ContractClass(program),
casm_debug_info,
maybe_statements_functions_map,
statements_functions_map,
},
);

Expand All @@ -113,7 +115,7 @@ pub fn compile_sierra_and_add_compiled_artifacts_to_cache(
.into_v1()
.context("Failed to extract program artifact from versioned program. Make sure your versioned program is of version 1")?;

let maybe_statements_functions_map = maybe_get_statements_functions_map(debug_info);
let statements_functions_map = maybe_get_statements_functions_map(debug_info);

let casm = cairo_lang_sierra_to_casm::compiler::compile(
&program,
Expand All @@ -131,7 +133,7 @@ pub fn compile_sierra_and_add_compiled_artifacts_to_cache(
CompiledArtifacts {
sierra_program: SierraProgram::VersionedProgram(program),
casm_debug_info: casm.debug_info,
maybe_statements_functions_map,
statements_functions_map,
},
);

Expand Down
1 change: 1 addition & 0 deletions crates/cairo-profiler/src/trace_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ fn collect_samples<'a>(
compiled_artifacts.sierra_program.get_program(),
&compiled_artifacts.casm_debug_info,
cairo_execution_info.casm_level_info.run_with_call_header,
&compiled_artifacts.statements_functions_map,
&FunctionLevelConfig::from(profiler_config),
);

Expand Down
90 changes: 51 additions & 39 deletions crates/cairo-profiler/src/trace_reader/function_trace_builder.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
use crate::profiler_config::FunctionLevelConfig;
use crate::sierra_loader::StatementsFunctionsMap;
use crate::trace_reader::function_name::FunctionName;
use crate::trace_reader::function_trace_builder::function_stack_trace::{
CallStack, CurrentCallType,
CallStack, VecWithLimitedCapacity,
};
use crate::trace_reader::function_trace_builder::inlining::build_original_call_stack_with_inlined_calls;
use crate::trace_reader::sample::{
FunctionCall, InternalFunctionCall, MeasurementUnit, MeasurementValue, Sample,
};
use cairo_lang_sierra::extensions::core::{CoreConcreteLibfunc, CoreLibfunc, CoreType};
use cairo_lang_sierra::program::{GenStatement, Program, StatementIdx};
use cairo_lang_sierra::program_registry::ProgramRegistry;
use cairo_lang_sierra_to_casm::compiler::CairoProgramDebugInfo;
use itertools::{chain, Itertools};
use itertools::Itertools;
use std::collections::HashMap;
use std::ops::AddAssign;
use trace_data::TraceEntry;

mod function_stack_trace;
mod inlining;

pub struct FunctionLevelProfilingInfo {
pub functions_samples: Vec<Sample>,
Expand Down Expand Up @@ -48,6 +51,7 @@ pub fn collect_function_level_profiling_info(
program: &Program,
casm_debug_info: &CairoProgramDebugInfo,
run_with_call_header: bool,
statements_functions_map: &Option<StatementsFunctionsMap>,
function_level_config: &FunctionLevelConfig,
) -> FunctionLevelProfilingInfo {
let sierra_program_registry = &ProgramRegistry::<CoreType, CoreLibfunc>::new(program).unwrap();
Expand Down Expand Up @@ -76,12 +80,11 @@ pub fn collect_function_level_profiling_info(
// profile tree. It is because technically they don't belong to any function, but still increase
// the number of total steps. The value is different from zero only for functions run with header.
let mut header_steps = Steps(0);
let mut current_function_steps = Steps(0);
let mut end_of_program_reached = false;

for step in trace {
// Skip the header. This only makes sense when a header was added to CASM program.
if step.pc < real_minimal_pc && run_with_call_header {
// Skip the header.
if step.pc < real_minimal_pc {
header_steps += 1;
continue;
}
Expand All @@ -93,8 +96,6 @@ pub fn collect_function_level_profiling_info(
unreachable!("End of program reached, but trace continues.");
}

current_function_steps += 1;

let maybe_sierra_statement_idx =
maybe_sierra_statement_index_by_pc(casm_debug_info, real_pc_code_offset);
let sierra_statement_idx = match maybe_sierra_statement_idx {
Expand All @@ -112,6 +113,18 @@ pub fn collect_function_level_profiling_info(
function_level_config.split_generics,
);

let current_call_stack = build_current_call_stack(
&call_stack,
current_function_name,
function_level_config.show_inlined_functions,
sierra_statement_idx,
statements_functions_map.as_ref(),
);

*functions_stack_traces
.entry(current_call_stack.clone().into())
.or_insert(Steps(0)) += 1;

let Some(gen_statement) = program.statements.get(sierra_statement_idx.0) else {
panic!("Failed fetching statement index {}", sierra_statement_idx.0);
};
Expand All @@ -122,42 +135,12 @@ pub fn collect_function_level_profiling_info(
sierra_program_registry.get_libfunc(&invocation.libfunc_id),
Ok(CoreConcreteLibfunc::FunctionCall(_))
) {
call_stack
.enter_function_call(current_function_name, &mut current_function_steps);
call_stack.enter_function_call(current_call_stack);
}
}
GenStatement::Return(_) => {
if let Some(exited_call) = call_stack.exit_function_call() {
if let CurrentCallType::Regular((function_name, steps)) = exited_call {
let names_call_stack = chain!(
call_stack.current_function_names_stack(),
[function_name, current_function_name]
)
.collect_vec();

let call_stack = names_call_stack
.into_iter()
.map(|function_name| {
FunctionCall::InternalFunctionCall(
InternalFunctionCall::NonInlined(function_name),
)
})
.collect();

*functions_stack_traces.entry(call_stack).or_insert(Steps(0)) +=
current_function_steps;
// Set to the caller function steps to continue counting its cost.
current_function_steps = steps;
}
} else {
if call_stack.exit_function_call().is_none() {
end_of_program_reached = true;

let call_stack = vec![FunctionCall::InternalFunctionCall(
InternalFunctionCall::NonInlined(current_function_name),
)];

*functions_stack_traces.entry(call_stack).or_insert(Steps(0)) +=
current_function_steps;
}
}
}
Expand Down Expand Up @@ -208,3 +191,32 @@ fn maybe_sierra_statement_index_by_pc(
MaybeSierraStatementIndex::SierraStatementIndex(statement_index)
}
}

fn build_current_call_stack(
call_stack: &CallStack,
current_function_name: FunctionName,
show_inlined_functions: bool,
sierra_statement_idx: StatementIdx,
statements_functions_map: Option<&StatementsFunctionsMap>,
) -> VecWithLimitedCapacity<FunctionCall> {
let mut current_call_stack = call_stack.current_call_stack().clone();

if current_call_stack.len() == 0
|| *current_call_stack[current_call_stack.len() - 1].function_name()
!= current_function_name
{
current_call_stack.push(FunctionCall::InternalFunctionCall(
InternalFunctionCall::NonInlined(current_function_name),
));
}

if show_inlined_functions {
build_original_call_stack_with_inlined_calls(
sierra_statement_idx,
statements_functions_map,
current_call_stack,
)
} else {
current_call_stack
}
}
Loading

0 comments on commit 5d1cdcd

Please sign in to comment.