Skip to content

Commit

Permalink
Optimize the coverage computation logic to avoid redundant processing…
Browse files Browse the repository at this point in the history
… of trace files referencing the same Sierra program. (#42)

Closes #41
  • Loading branch information
ksew1 authored Sep 3, 2024
1 parent 870e6c6 commit 8ad49bf
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 50 deletions.
3 changes: 2 additions & 1 deletion crates/cairo-coverage/src/coverage_data/function.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::input::{InputData, LineRange, SierraToCairoMap};
use crate::data_loader::LineRange;
use crate::input::{InputData, SierraToCairoMap};
use crate::types::{FileLocation, FunctionName, HitCount, LineNumber};
use std::collections::HashMap;

Expand Down
70 changes: 70 additions & 0 deletions crates/cairo-coverage/src/data_loader/loaded_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use anyhow::Context;
use anyhow::Result;
use cairo_lang_sierra::debug_info::DebugInfo;
use cairo_lang_sierra::program::{Program, ProgramArtifact, VersionedProgram};
use camino::Utf8PathBuf;
use derived_deref::Deref;
use serde::de::DeserializeOwned;
use std::collections::HashMap;
use std::fs;
use trace_data::CallTrace;

type SourceSierraPath = String;

#[derive(Deref)]
pub struct LoadedDataMap(HashMap<SourceSierraPath, LoadedData>);

pub struct LoadedData {
pub program: Program,
pub debug_info: DebugInfo,
pub call_traces: Vec<CallTrace>,
}

impl LoadedDataMap {
pub fn load(call_trace_paths: &Vec<Utf8PathBuf>) -> Result<Self> {
let mut map: HashMap<SourceSierraPath, LoadedData> = HashMap::new();
for call_trace_path in call_trace_paths {
let call_trace: CallTrace = read_and_deserialize(call_trace_path)?;

let source_sierra_path = &call_trace
.cairo_execution_info
.as_ref()
.context("Missing key 'cairo_execution_info' in call trace. Perhaps you have outdated scarb?")?
.source_sierra_path;

if let Some(loaded_data) = map.get_mut(&source_sierra_path.to_string()) {
loaded_data.call_traces.push(call_trace);
} else {
let VersionedProgram::V1 {
program:
ProgramArtifact {
program,
debug_info,
},
..
} = read_and_deserialize(source_sierra_path)?;

map.insert(
source_sierra_path.to_string(),
LoadedData {
program,
debug_info: debug_info
.context(format!("Debug info not found in: {source_sierra_path}"))?,
call_traces: vec![call_trace],
},
);
}
}
Ok(Self(map))
}
}

fn read_and_deserialize<T: DeserializeOwned>(file_path: &Utf8PathBuf) -> anyhow::Result<T> {
fs::read_to_string(file_path)
.context(format!("Failed to read file at path: {file_path}"))
.and_then(|content| {
serde_json::from_str(&content).context(format!(
"Failed to deserialize JSON content from file at path: {file_path}"
))
})
}
5 changes: 5 additions & 0 deletions crates/cairo-coverage/src/data_loader/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod loaded_data;
mod types;

pub use loaded_data::{LoadedData, LoadedDataMap};
pub use types::{CodeLocation, CoverageAnnotations, LineRange, ProfilerAnnotations};
62 changes: 22 additions & 40 deletions crates/cairo-coverage/src/input/data.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
use crate::data_loader::LoadedData;
use crate::input::test_function_filter::TestFunctionFilter;
use crate::input::{create_sierra_to_cairo_map, SierraToCairoMap, UniqueExecutedSierraIds};
use anyhow::{Context, Result};
use cairo_lang_sierra::program::{Program, ProgramArtifact, VersionedProgram};
use cairo_lang_sierra::program::Program;
use cairo_lang_sierra_to_casm::compiler::{CairoProgram, SierraToCasmConfig};
use cairo_lang_sierra_to_casm::metadata::{calc_metadata, MetadataComputationConfig};
use camino::Utf8PathBuf;
use serde::de::DeserializeOwned;
use std::fs;
use trace_data::CallTrace;

const SNFORGE_TEST_EXECUTABLE: &str = "snforge_internal_test_executable";

Expand All @@ -17,27 +14,14 @@ pub struct InputData {
}

impl InputData {
pub fn new(call_trace_path: &Utf8PathBuf, include_test_functions: bool) -> Result<Self> {
let call_trace: CallTrace = read_and_deserialize(call_trace_path)?;

let source_sierra_path = &call_trace
.cairo_execution_info
.as_ref()
.context("Missing key 'cairo_execution_info' in call trace")?
.source_sierra_path;

let VersionedProgram::V1 {
program:
ProgramArtifact {
program,
debug_info,
},
..
} = read_and_deserialize(source_sierra_path)?;

let debug_info =
debug_info.context(format!("Debug info not found in: {source_sierra_path}"))?;

pub fn new(
LoadedData {
program,
debug_info,
call_traces,
}: &LoadedData,
include_test_functions: bool,
) -> Result<Self> {
let test_function_filter = TestFunctionFilter::new(
debug_info
.executables
Expand All @@ -46,10 +30,18 @@ impl InputData {
include_test_functions,
);

let sierra_to_cairo_map = create_sierra_to_cairo_map(&debug_info, &test_function_filter)?;
let casm = compile_to_casm(&program)?;
let unique_executed_sierra_ids =
UniqueExecutedSierraIds::new(&casm, &call_trace, &sierra_to_cairo_map)?;
let sierra_to_cairo_map = create_sierra_to_cairo_map(debug_info, &test_function_filter)?;
let casm = compile_to_casm(program)?;
let unique_executed_sierra_ids = call_traces
.iter()
.map(|call_trace| UniqueExecutedSierraIds::new(&casm, call_trace, &sierra_to_cairo_map))
.collect::<Result<Vec<_>>>()?
.into_iter()
.reduce(|mut acc, unique_executed_sierra_ids| {
acc.extend(unique_executed_sierra_ids.clone().into_iter());
acc
})
.context("Failed to create unique executed sierra ids")?;

Ok(Self {
unique_executed_sierra_ids,
Expand All @@ -70,13 +62,3 @@ fn compile_to_casm(program: &Program) -> Result<CairoProgram> {
)
.context("Failed to compile sierra to casm")
}

fn read_and_deserialize<T: DeserializeOwned>(file_path: &Utf8PathBuf) -> Result<T> {
fs::read_to_string(file_path)
.context(format!("Failed to read file at path: {file_path}"))
.and_then(|content| {
serde_json::from_str(&content).context(format!(
"Failed to deserialize JSON content from file at path: {file_path}"
))
})
}
1 change: 0 additions & 1 deletion crates/cairo-coverage/src/input/data_loader/mod.rs

This file was deleted.

2 changes: 0 additions & 2 deletions crates/cairo-coverage/src/input/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
mod data;
mod data_loader;
mod sierra_to_cairo_map;
mod test_function_filter;
mod unique_executed_sierra_ids;

pub use data::InputData;
pub use data_loader::types::LineRange;
pub use sierra_to_cairo_map::{create_sierra_to_cairo_map, SierraToCairoMap};
pub use unique_executed_sierra_ids::UniqueExecutedSierraIds;
4 changes: 1 addition & 3 deletions crates/cairo-coverage/src/input/sierra_to_cairo_map.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::input::data_loader::types::{
CodeLocation, CoverageAnnotations, LineRange, ProfilerAnnotations,
};
use crate::data_loader::{CodeLocation, CoverageAnnotations, LineRange, ProfilerAnnotations};
use crate::input::test_function_filter::TestFunctionFilter;
use crate::types::{FileLocation, FunctionName};
use anyhow::{Context, Result};
Expand Down
11 changes: 11 additions & 0 deletions crates/cairo-coverage/src/input/unique_executed_sierra_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ use trace_data::{CallTrace, CasmLevelInfo};
#[derive(Deref)]
pub struct UniqueExecutedSierraIds(HashMap<StatementIdx, usize>);

impl Extend<(StatementIdx, usize)> for UniqueExecutedSierraIds {
fn extend<T: IntoIterator<Item = (StatementIdx, usize)>>(&mut self, iter: T) {
for (key, value) in iter {
self.0
.entry(key)
.and_modify(|e| *e += value)
.or_insert(value);
}
}
}

impl UniqueExecutedSierraIds {
pub fn new(
casm: &CairoProgram,
Expand Down
7 changes: 5 additions & 2 deletions crates/cairo-coverage/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod cli;
mod coverage_data;
mod data_loader;
mod input;
mod output;
mod types;

use crate::coverage_data::create_files_coverage_data_with_hits;
use crate::data_loader::LoadedDataMap;
use crate::input::InputData;
use crate::output::lcov::LcovFormat;
use anyhow::{Context, Result};
Expand All @@ -24,8 +26,9 @@ fn main() -> Result<()> {
.open(output_path)
.context(format!("Failed to open output file at path: {output_path}"))?;

for trace_file in &cli.trace_files {
let input_data = InputData::new(trace_file, cli.include_test_functions)?;
let loaded_data = LoadedDataMap::load(&cli.trace_files)?;
for (_, loaded_data) in loaded_data.iter() {
let input_data = InputData::new(loaded_data, cli.include_test_functions)?;
let coverage_data = create_files_coverage_data_with_hits(&input_data);
let output_data = LcovFormat::from(coverage_data);

Expand Down
55 changes: 54 additions & 1 deletion crates/cairo-coverage/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,66 @@ fn scarb_template() {
}

#[test]
#[ignore] // Output to big fix in next test
fn complex_calculator() {
let output = run_test_project("complex_calculator").unwrap();
assert_eq!(
output,
indoc! {
"
TN:
SF:tests/data/complex_calculator/src/lib.cairo
FN:2,2,complex_calculator::add
FNDA:2,complex_calculator::add
FN:17,21,complex_calculator::divide
FNDA:1,complex_calculator::divide
FN:25,30,complex_calculator::factorial
FNDA:12,complex_calculator::factorial
FN:45,49,complex_calculator::fibonacci
FNDA:2,complex_calculator::fibonacci
FN:53,63,complex_calculator::is_prime
FNDA:84,complex_calculator::is_prime
FN:10,10,complex_calculator::multiply
FNDA:2,complex_calculator::multiply
FN:35,40,complex_calculator::power
FNDA:10,complex_calculator::power
FN:6,6,complex_calculator::subtract
FNDA:2,complex_calculator::subtract
FN:14,14,complex_calculator::unsafe_divide
FNDA:0,complex_calculator::unsafe_divide
FNF:9
FNH:8
DA:2,2
DA:6,2
DA:10,2
DA:14,0
DA:17,1
DA:18,1
DA:19,1
DA:21,0
DA:25,1
DA:28,12
DA:29,10
DA:30,10
DA:35,2
DA:38,10
DA:39,6
DA:40,6
DA:45,2
DA:46,1
DA:47,0
DA:48,1
DA:49,0
DA:53,2
DA:54,4
DA:55,0
DA:58,0
DA:59,84
DA:60,80
DA:61,0
DA:63,80
LF:29
LH:22
end_of_record
"
}
);
Expand Down

0 comments on commit 8ad49bf

Please sign in to comment.