diff --git a/Cargo.toml b/Cargo.toml index 3b5ae71..9e88088 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,10 @@ doctest = false anyhow = "1.0" thiserror = "2.0" semver = "1.0" -serde = { version = "1.0", "features" = [ "derive" ] } +serde = { version = "1.0", features = [ "derive" ] } num = "0.4" itertools = "0.14" +indexmap = { version = "2.10", features = ["serde"] } zkevm_opcode_defs = "=0.150.6" diff --git a/src/context/function/block/evmla_data.rs b/src/context/function/block/evmla_data.rs index bda7924..755c5b1 100644 --- a/src/context/function/block/evmla_data.rs +++ b/src/context/function/block/evmla_data.rs @@ -10,14 +10,14 @@ #[derive(Debug, Clone)] pub struct EVMLAData { /// The initial hashes of the allowed stack states. - pub stack_hashes: Vec<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>, + pub stack_hashes: Vec, } impl EVMLAData { /// /// A shortcut constructor. /// - pub fn new(stack_hashes: Vec<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>) -> Self { + pub fn new(stack_hashes: Vec) -> Self { Self { stack_hashes } } } diff --git a/src/context/traits/evmla_function.rs b/src/context/traits/evmla_function.rs index ca7dbd0..3ae0c98 100644 --- a/src/context/traits/evmla_function.rs +++ b/src/context/traits/evmla_function.rs @@ -14,9 +14,5 @@ pub trait IEVMLAFunction<'ctx> { /// /// If there is only one block, it is returned unconditionally. /// - fn find_block( - &self, - key: &BlockKey, - stack_hash: &[u8; era_compiler_common::BYTE_LENGTH_FIELD], - ) -> anyhow::Result>; + fn find_block(&self, key: &BlockKey, stack_hash: &u64) -> anyhow::Result>; } diff --git a/src/eravm/context/function/mod.rs b/src/eravm/context/function/mod.rs index 46f08fa..b6e7102 100644 --- a/src/eravm/context/function/mod.rs +++ b/src/eravm/context/function/mod.rs @@ -452,11 +452,7 @@ impl<'ctx> Function<'ctx> { } impl<'ctx> IEVMLAFunction<'ctx> for Function<'ctx> { - fn find_block( - &self, - key: &BlockKey, - stack_hash: &[u8; era_compiler_common::BYTE_LENGTH_FIELD], - ) -> anyhow::Result> { + fn find_block(&self, key: &BlockKey, stack_hash: &u64) -> anyhow::Result> { let evmla_data = self.evmla(); if evmla_data diff --git a/src/evm/context/function/mod.rs b/src/evm/context/function/mod.rs index b95b13a..aa6c219 100644 --- a/src/evm/context/function/mod.rs +++ b/src/evm/context/function/mod.rs @@ -325,11 +325,7 @@ impl<'ctx> Function<'ctx> { } impl<'ctx> IEVMLAFunction<'ctx> for Function<'ctx> { - fn find_block( - &self, - key: &BlockKey, - stack_hash: &[u8; era_compiler_common::BYTE_LENGTH_FIELD], - ) -> anyhow::Result> { + fn find_block(&self, key: &BlockKey, stack_hash: &u64) -> anyhow::Result> { let evmla_data = self.evmla(); if evmla_data diff --git a/src/evm/context/mod.rs b/src/evm/context/mod.rs index 68f3f68..c37d8e8 100644 --- a/src/evm/context/mod.rs +++ b/src/evm/context/mod.rs @@ -21,6 +21,7 @@ use crate::context::r#loop::Loop; use crate::context::IContext; use crate::debug_config::DebugConfig; use crate::evm::build::Build as EVMBuild; +use crate::evm::profiler::Profiler; use crate::evm::warning::Warning; use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::Optimizer; @@ -121,10 +122,17 @@ impl<'ctx> Context<'ctx> { output_assembly: bool, output_bytecode: bool, is_size_fallback: bool, + profiler: &mut Profiler, ) -> anyhow::Result { let module_clone = self.module.clone(); let contract_path = self.module.get_name().to_str().expect("Always valid"); + let run_init_verify = profiler.start_evm_translation_unit( + contract_path, + self.code_segment, + "InitVerify", + self.optimizer.settings(), + ); let target_machine = TargetMachine::new( era_compiler_common::Target::EVM, self.optimizer.settings(), @@ -158,7 +166,14 @@ impl<'ctx> Context<'ctx> { self.code_segment, ) })?; + run_init_verify.borrow_mut().finish(); + let run_optimize_verify = profiler.start_evm_translation_unit( + contract_path, + self.code_segment, + "OptimizeVerify", + self.optimizer.settings(), + ); self.optimizer .run(&target_machine, self.module()) .map_err(|error| anyhow::anyhow!("{} code optimizing: {error}", self.code_segment))?; @@ -176,8 +191,15 @@ impl<'ctx> Context<'ctx> { self.code_segment, ) })?; + run_optimize_verify.borrow_mut().finish(); let assembly_buffer = if output_assembly || self.debug_config.is_some() { + let run_emit_llvm_assembly = profiler.start_evm_translation_unit( + contract_path, + self.code_segment, + "EmitLLVMAssembly", + self.optimizer.settings(), + ); let assembly_buffer = target_machine .write_to_memory_buffer(self.module(), inkwell::targets::FileType::Assembly) .map_err(|error| anyhow::anyhow!("assembly emitting: {error}"))?; @@ -193,6 +215,7 @@ impl<'ctx> Context<'ctx> { )?; } + run_emit_llvm_assembly.borrow_mut().finish(); Some(assembly_buffer) } else { None @@ -201,11 +224,18 @@ impl<'ctx> Context<'ctx> { .map(|assembly_buffer| String::from_utf8_lossy(assembly_buffer.as_slice()).to_string()); if output_bytecode { + let run_emit_bytecode = profiler.start_evm_translation_unit( + contract_path, + self.code_segment, + "EmitBytecode", + self.optimizer.settings(), + ); let bytecode_buffer = target_machine .write_to_memory_buffer(self.module(), inkwell::targets::FileType::Object) .map_err(|error| { anyhow::anyhow!("{} bytecode emitting: {error}", self.code_segment) })?; + run_emit_bytecode.borrow_mut().finish(); let immutables = match self.code_segment { era_compiler_common::CodeSegment::Deploy => None, @@ -242,7 +272,7 @@ impl<'ctx> Context<'ctx> { for function in self.module.get_functions() { Function::set_size_attributes(self.llvm, function); } - return self.build(output_assembly, output_bytecode, true); + return self.build(output_assembly, output_bytecode, true, profiler); } else { warnings.push(match self.code_segment { era_compiler_common::CodeSegment::Deploy => Warning::DeployCodeSize { diff --git a/src/evm/mod.rs b/src/evm/mod.rs index 99aa33c..cf8afc7 100644 --- a/src/evm/mod.rs +++ b/src/evm/mod.rs @@ -6,6 +6,7 @@ pub mod build; pub mod r#const; pub mod context; pub mod instructions; +pub mod profiler; pub mod warning; use std::collections::BTreeMap; diff --git a/src/evm/profiler/mod.rs b/src/evm/profiler/mod.rs new file mode 100644 index 0000000..af70fe5 --- /dev/null +++ b/src/evm/profiler/mod.rs @@ -0,0 +1,93 @@ +//! +//! Compiler pipeline profiler. +//! + +pub mod run; + +use std::cell::RefCell; +use std::rc::Rc; + +use indexmap::IndexMap; + +use crate::optimizer::settings::Settings as OptimizerSettings; + +use self::run::Run; + +/// +/// Compiler pipeline profiler. +/// +#[derive(Debug, Default)] +pub struct Profiler { + /// Indexed map of timing entries. + pub timings: IndexMap>>, +} + +impl Profiler { + /// + /// Starts a new run for a generic part of the pipeline. + /// + pub fn start_pipeline_element(&mut self, description: &str) -> Rc> { + let run_name = description.to_owned(); + assert!( + !self.timings.contains_key(run_name.as_str()), + "Translation unit run `{run_name}` already exists" + ); + + self.start_run(run_name) + } + + /// + /// Starts a new run for an EVM translation unit. + /// + pub fn start_evm_translation_unit( + &mut self, + full_path: &str, + code_segment: era_compiler_common::CodeSegment, + description: &str, + optimizer_settings: &OptimizerSettings, + ) -> Rc> { + let spill_area_description = format!( + "SpillArea({})", + optimizer_settings.spill_area_size().unwrap_or_default() + ); + let run_name = format!( + "{full_path}:{code_segment}/{description}/{optimizer_settings}/{spill_area_description}", + ); + assert!( + !self.timings.contains_key(run_name.as_str()), + "Translation unit run `{run_name}` already exists" + ); + + self.start_run(run_name) + } + + /// + /// Returns a serializeable vector of the profiler runs. + /// + pub fn to_vec(&self) -> Vec<(String, u64)> { + self.timings + .iter() + .map(|(name, run)| { + let run = run.borrow(); + ( + name.clone(), + run.duration.expect("Always exists").as_millis() as u64, + ) + }) + .collect() + } + + /// + /// Starts a new run with the given name. + /// + fn start_run(&mut self, name: String) -> Rc> { + assert!( + !self.timings.contains_key(name.as_str()), + "Run `{name}` already exists" + ); + + let run = Rc::new(RefCell::new(Run::default())); + self.timings.insert(name, run.clone()); + run + } +} diff --git a/src/evm/profiler/run.rs b/src/evm/profiler/run.rs new file mode 100644 index 0000000..ccc0f32 --- /dev/null +++ b/src/evm/profiler/run.rs @@ -0,0 +1,52 @@ +//! +//! Compiler pipeline profiler run. +//! + +use std::time::Duration; +use std::time::Instant; + +/// +/// Compiler pipeline profiler run. +/// +#[derive(Debug)] +pub struct Run { + /// Start time. + pub start_time: Instant, + /// Recorded duration. + pub duration: Option, +} + +impl Default for Run { + fn default() -> Self { + Run { + start_time: Instant::now(), + duration: None, + } + } +} + +impl Run { + /// + /// Records the duration of the run. + /// + pub fn finish(&mut self) { + assert!( + self.duration.is_none(), + "Duration has already been recorded" + ); + + self.duration = Some(self.start_time.elapsed()); + } +} + +impl std::fmt::Display for Run { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let duration = self + .duration + .as_ref() + .expect("Duration has not been recorded yet"); + + write!(f, "{duration:?}")?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 5cfc7e9..282de55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,6 +109,8 @@ pub use self::evm::instructions::return_data as evm_return_data; pub use self::evm::instructions::storage as evm_storage; pub use self::evm::link as evm_link; pub use self::evm::minimal_deploy_code as evm_minimal_deploy_code; +pub use self::evm::profiler::run::Run as EVMProfilerRun; +pub use self::evm::profiler::Profiler as EVMProfiler; pub use self::evm::r#const as evm_const; pub use self::evm::warning::Warning as EVMWarning; pub use self::evm::DummyLLVMWritable as EVMDummyLLVMWritable;