diff --git a/crates/cli/src/commands/evm_opt.rs b/crates/cli/src/commands/evm_opt.rs index 6b7d3fe77..7b78e3a0c 100644 --- a/crates/cli/src/commands/evm_opt.rs +++ b/crates/cli/src/commands/evm_opt.rs @@ -10,7 +10,7 @@ use super::print_pass_diff; use clap::ValueHint; use solar_codegen::backend::evm::ir; use solar_config::CompileOpts; -use solar_interface::Session; +use solar_sema::Gcx; use std::{path::Path, process::ExitCode}; #[derive(clap::Args)] @@ -26,9 +26,6 @@ pub(crate) struct EvmOptArgs { default_value = "none" )] passes: Vec>, - /// If true, print EVM IR after every pass; otherwise only after the last. - #[arg(long)] - print_after_each: bool, /// Path to input file. Extension determines whether it's .evmir. #[arg(value_hint = ValueHint::FilePath)] input: String, @@ -72,20 +69,17 @@ fn print_module(module: &ir::Module, name: &str, after: &str) { print!("{}", module.to_text()); } -fn run_pipeline(sess: &Session, module: &mut ir::Module, name: &str, args: &EvmOptArgs) { +fn run_pipeline(gcx: Gcx<'_>, module: &mut ir::Module, name: &str, args: &EvmOptArgs) { + let sess = gcx.sess; let dcx = &sess.dcx; - let options = ir::PassOptions { - time_passes: sess.opts.unstable.time_passes, - evm_version: sess.opts.evm_version, - optimization: sess.opts.optimization, - }; + let print_after_each = sess.opts.unstable.print_after_each; let pipeline_label = selected_pass_list_label(&args.passes, ","); for (index, &pass) in args.passes.iter().enumerate() { let before = sess.opts.unstable.pass_diff.then(|| module.to_text().to_string()); if let Some(pass) = pass { - ir::run_pass(module, pass, options); + ir::run_pass(gcx, module, pass); } - if before.is_some() || args.print_after_each || index + 1 == args.passes.len() { + if before.is_some() || print_after_each || index + 1 == args.passes.len() { ir::validate(dcx, module); if dcx.has_errors().is_err() { break; @@ -94,14 +88,15 @@ fn run_pipeline(sess: &Session, module: &mut ir::Module, name: &str, args: &EvmO let after = module.to_text().to_string(); print_pass_diff(name, pass_label(pass), &before, &after); } else { - let label = if args.print_after_each { pass_label(pass) } else { &pipeline_label }; + let label = if print_after_each { pass_label(pass) } else { &pipeline_label }; print_module(module, name, label); } } } } -fn process_evmir(sess: &Session, args: &EvmOptArgs) -> solar_interface::Result { +fn process_evmir(gcx: Gcx<'_>, args: &EvmOptArgs) -> solar_interface::Result { + let sess = gcx.sess; let source = sess .source_map() .load_file(Path::new(&args.input)) @@ -109,7 +104,7 @@ fn process_evmir(sess: &Session, args: &EvmOptArgs) -> solar_interface::Result { let mut module = ir::Module::parse(sess, &source)?; ir::validate(&sess.dcx, &module); if sess.dcx.has_errors().is_ok() { - run_pipeline(sess, &mut module, &args.input, args); + run_pipeline(gcx, &mut module, &args.input, args); } Ok(()) } @@ -117,9 +112,11 @@ fn process_evmir(sess: &Session, args: &EvmOptArgs) -> solar_interface::Result { pub(crate) fn run(args: EvmOptArgs, mut opts: CompileOpts) -> ExitCode { opts.input.push(args.input.clone()); let ext = Path::new(&args.input).extension().and_then(|s| s.to_str()).unwrap_or(""); - let result = super::compile::run_session_with(opts, |sess| match ext { - "evmir" => process_evmir(sess, &args), - _ => Err(sess + let result = super::compile::run_compiler_with(opts, |compiler| match ext { + "evmir" => process_evmir(compiler.gcx(), &args), + _ => Err(compiler + .gcx() + .sess .dcx .err(format!("unsupported input file extension `.{ext}` (expected .evmir)")) .emit()), diff --git a/crates/cli/src/commands/mir_opt.rs b/crates/cli/src/commands/mir_opt.rs index 78276c16e..dead10545 100644 --- a/crates/cli/src/commands/mir_opt.rs +++ b/crates/cli/src/commands/mir_opt.rs @@ -16,14 +16,13 @@ use solar_codegen::{ lower, mir::{Module, validate}, pass::{ - DEFAULT_CLEANUP_PIPELINE, DEFAULT_PIPELINE, PASS_REGISTRY, PassInfo, PipelineOptions, - lookup_pass, run_default_pipeline, run_pass, + DEFAULT_CLEANUP_PIPELINE, DEFAULT_PIPELINE, PASS_REGISTRY, PassInfo, lookup_pass, + run_default_pipeline, run_pass, }, }; use solar_config::CompileOpts; use solar_data_structures::fmt::{self, FmtIteratorExt}; -use solar_interface::Session; -use solar_sema::CompilerRef; +use solar_sema::{CompilerRef, Gcx}; use std::{ops::ControlFlow, path::Path, process::ExitCode}; fn after_help() -> String { @@ -80,9 +79,6 @@ pub(crate) struct MirOptArgs { conflicts_with = "pipeline_default" )] passes: Option>>, - /// If true, print MIR after every pass; otherwise only after the last. - #[arg(long)] - print_after_each: bool, /// Run the same pass pipeline as EvmCodegen::run_optimization_passes. #[arg(long, conflicts_with = "passes")] pipeline_default: bool, @@ -125,38 +121,28 @@ fn selected_pass_list_label(passes: &[Option<&PassInfo>], separator: &str) -> St /// Runs the pass pipeline on a single module and emits output. /// Used for both .sol contracts and .mir input. -fn run_pipeline( - module: &mut Module, - name: &str, - args: &MirOptArgs, - pass_diff: bool, - time_passes: bool, -) { +fn run_pipeline(gcx: Gcx<'_>, module: &mut Module, name: &str, args: &MirOptArgs) { + let print_after_each = gcx.sess.opts.unstable.print_after_each; if args.pipeline_default { - let mut options = PipelineOptions::default(); - options.print_after_each = args.print_after_each; - options.time_passes = time_passes; - run_default_pipeline(module, options); - if !args.print_after_each { + run_default_pipeline(gcx, module); + if !print_after_each { print_module(module, name, "pipeline-default"); } return; } let passes = args.selected_passes(); - let mut options = PipelineOptions::default(); - options.time_passes = time_passes; let pipeline_label = args.pipeline_label(&passes); for (index, &pass) in passes.iter().enumerate() { - let before = pass_diff.then(|| module.to_text().to_string()); + let before = gcx.sess.opts.unstable.pass_diff.then(|| module.to_text().to_string()); if let Some(pass) = pass { - run_pass(module, pass, options); + run_pass(gcx, module, pass); } if let Some(before) = before { let after = module.to_text().to_string(); print_pass_diff(name, pass_label(pass), &before, &after); - } else if args.print_after_each || index + 1 == passes.len() { - let label = if args.print_after_each { pass_label(pass) } else { &pipeline_label }; + } else if print_after_each || index + 1 == passes.len() { + let label = if print_after_each { pass_label(pass) } else { &pipeline_label }; print_module(module, name, label); } } @@ -169,7 +155,8 @@ fn print_module(module: &Module, name: &str, after: &str) { } /// Process a `.mir` input: read file, parse, run passes, print. -fn process_mir(sess: &Session, args: &MirOptArgs) -> solar_interface::Result { +fn process_mir(gcx: Gcx<'_>, args: &MirOptArgs) -> solar_interface::Result { + let sess = gcx.sess; let source = sess .source_map() .load_file(Path::new(&args.input)) @@ -179,13 +166,7 @@ fn process_mir(sess: &Session, args: &MirOptArgs) -> solar_interface::Result { // diagnostic instead of tripping the post-pass validator ICE. validate(&sess.dcx, &module); if sess.dcx.has_errors().is_ok() { - run_pipeline( - &mut module, - &args.input, - args, - sess.opts.unstable.pass_diff, - sess.opts.unstable.time_passes, - ); + run_pipeline(gcx, &mut module, &args.input, args); } Ok(()) } @@ -209,13 +190,7 @@ fn process_sol(compiler: &mut CompilerRef<'_>, args: &MirOptArgs) -> solar_inter } let mut module = lower::lower_contract(gcx, id); let name = gcx.contract_fully_qualified_name(id).to_string(); - run_pipeline( - &mut module, - &name, - args, - gcx.sess.opts.unstable.pass_diff, - gcx.sess.opts.unstable.time_passes, - ); + run_pipeline(gcx, &mut module, &name, args); } Ok(()) } @@ -227,7 +202,9 @@ pub(super) fn run(args: MirOptArgs, mut opts: CompileOpts) -> ExitCode { let ext = Path::new(&args.input).extension().and_then(|s| s.to_str()).unwrap_or(""); let result = match ext { "sol" => super::compile::run_compiler_with(opts, |compiler| process_sol(compiler, &args)), - "mir" => super::compile::run_session_with(opts, |sess| process_mir(sess, &args)), + "mir" => { + super::compile::run_compiler_with(opts, |compiler| process_mir(compiler.gcx(), &args)) + } _ => super::compile::run_session_with(opts, |sess| { Err(sess .dcx diff --git a/crates/cli/src/emit.rs b/crates/cli/src/emit.rs index 4ba11bb43..93a5bf2ec 100644 --- a/crates/cli/src/emit.rs +++ b/crates/cli/src/emit.rs @@ -1,6 +1,6 @@ use alloy_json_abi::AbiItem; use alloy_primitives::Bytes; -use solar_codegen::{Backend, EvmCodegen, EvmCodegenConfig, backend::evm::ir, lower}; +use solar_codegen::{Backend, EvmCodegen, backend::evm::ir, lower}; use solar_config::{CompilerOutput, Dump, DumpKind}; use solar_data_structures::{bit_set::DenseBitSet, map::FxHashMap}; use solar_interface::Result; @@ -345,9 +345,8 @@ fn ensure_contract_bytecode( let mut module = lower::lower_contract_with_bytecodes(gcx, contract_id, all_bytecodes); gcx.dcx().has_errors()?; - let mut config = EvmCodegenConfig::from(gcx); - config.capture_evm_ir = capture_evm_ir; - let mut codegen = EvmCodegen::new(config); + let mut codegen = EvmCodegen::new(gcx); + codegen.set_capture_evm_ir(capture_evm_ir); let artifact = codegen.lower_module(&mut module); all_bytecodes.insert(contract_id, artifact.deployment.clone()); artifacts.insert( diff --git a/crates/codegen/src/backend/evm/assembler/mod.rs b/crates/codegen/src/backend/evm/assembler/mod.rs index 63f91aa9a..5e8e37663 100644 --- a/crates/codegen/src/backend/evm/assembler/mod.rs +++ b/crates/codegen/src/backend/evm/assembler/mod.rs @@ -14,9 +14,10 @@ use crate::{ mir::IMMUTABLE_WORD_SIZE, }; use alloy_primitives::U256; -use solar_config::{EvmVersion, OptimizationMode}; +use solar_config::OptimizationMode; use solar_data_structures::{bit_set::GrowableBitSet, map::FxHashMap}; use solar_interface::diagnostics::DiagCtxt; +use solar_sema::Gcx; const EVM_WORD_BYTES: usize = 32; @@ -65,24 +66,10 @@ pub(in crate::backend::evm) struct PreparedAssembly { deferred_values: FxHashMap, } -/// Configuration for EVM bytecode assembly. -#[derive(Clone, Copy, Debug, Default)] -pub(crate) struct AssemblerConfig { - /// EVM version to target when selecting hardfork-gated opcodes. - pub evm_version: EvmVersion, - /// Optimization mode for alternate byte encodings. - pub optimization: OptimizationMode, - /// Print the time spent in each EVM IR pass. - pub time_passes: bool, - /// Capture the final EVM IR without running additional passes. - pub capture_evm_ir: bool, -} - /// Relocating assembler for finalized EVM IR. #[derive(Debug)] -pub(crate) struct Assembler { - /// Bytecode assembly configuration. - config: AssemblerConfig, +pub(crate) struct Assembler<'gcx> { + gcx: Gcx<'gcx>, /// EVM IR emitted directly by MIR lowering. program: ir::Module, /// Block currently receiving emitted instructions. @@ -107,18 +94,12 @@ pub(crate) struct Assembler { deferred_values: FxHashMap, } -impl Assembler { +impl<'gcx> Assembler<'gcx> { /// Creates a new assembler. #[must_use] - pub(crate) fn new() -> Self { - Self::with_config(AssemblerConfig::default()) - } - - /// Creates a new assembler with the given configuration. - #[must_use] - pub(crate) fn with_config(config: AssemblerConfig) -> Self { + pub(crate) fn new(gcx: Gcx<'gcx>) -> Self { Self { - config, + gcx, program: Self::new_ir_module(), current_block: None, block_labels: Vec::new(), @@ -305,8 +286,14 @@ impl Assembler { /// Resolves relocations and encodes finalized EVM IR as bytecode. #[must_use] + #[cfg(test)] pub(crate) fn assemble(&mut self) -> AssembledCode { - let prepared = self.prepare(); + self.assemble_with_evm_ir(false) + } + + #[must_use] + pub(crate) fn assemble_with_evm_ir(&mut self, capture_evm_ir: bool) -> AssembledCode { + let prepared = self.prepare(capture_evm_ir); let result = self.assemble_prepared(&prepared, &[]); self.clear(); result @@ -318,27 +305,22 @@ impl Assembler { skip_all, fields(program = %self.program.name()), )] - pub(in crate::backend::evm) fn prepare(&mut self) -> PreparedAssembly { + pub(in crate::backend::evm) fn prepare(&mut self, capture_evm_ir: bool) -> PreparedAssembly { let Some((mut ir_program, mut labels)) = self.finish_evm_ir() else { return PreparedAssembly::default(); }; Self::resolve_known_deferred_constants(&mut ir_program, &self.deferred_values); - let pass_options = ir::PassOptions { - time_passes: self.config.time_passes, - evm_version: self.config.evm_version, - optimization: self.config.optimization, - }; - if self.config.optimization != OptimizationMode::None { + if self.gcx.sess.opts.optimization != OptimizationMode::None { let input_is_valid = cfg!(debug_assertions) && is_valid_evm_ir(&ir_program); for pass in ir::DEFAULT_PIPELINE { - ir::run_pass(&mut ir_program, pass, pass_options); + ir::run_pass(self.gcx, &mut ir_program, pass); } debug_assert!(!input_is_valid || is_valid_evm_ir(&ir_program)); } - let evm_ir = self.config.capture_evm_ir.then(|| ir_program.clone()); + let evm_ir = capture_evm_ir.then(|| ir_program.clone()); let program = assembly::lower_evm_ir(&ir_program, &mut labels, self); PreparedAssembly { evm_ir, @@ -449,7 +431,7 @@ impl Assembler { let mut offset = 0usize; let mut label_offsets = FxHashMap::default(); let mut new_widths = FxHashMap::default(); - let out = BytecodeAssembler::new(self.config); + let out = BytecodeAssembler::new(self.gcx); for (idx, inst) in program.instructions.iter().enumerate() { match inst.kind() { @@ -501,7 +483,7 @@ impl Assembler { label_offsets: FxHashMap, push_widths: &FxHashMap, ) -> AssembledCode { - let mut out = BytecodeAssembler::new(self.config); + let mut out = BytecodeAssembler::new(self.gcx); for (idx, inst) in program.instructions.iter().enumerate() { match inst.kind() { @@ -550,22 +532,16 @@ fn is_valid_evm_ir(module: &ir::Module) -> bool { dcx.has_errors().is_ok() } -impl Default for Assembler { - fn default() -> Self { - Self::new() - } -} - #[derive(Debug)] -struct BytecodeAssembler { - config: AssemblerConfig, +struct BytecodeAssembler<'gcx> { + gcx: Gcx<'gcx>, bytecode: Vec, immutable_refs: Vec, } -impl BytecodeAssembler { - fn new(config: AssemblerConfig) -> Self { - Self { config, bytecode: Vec::new(), immutable_refs: Vec::new() } +impl<'gcx> BytecodeAssembler<'gcx> { + fn new(gcx: Gcx<'gcx>) -> Self { + Self { gcx, bytecode: Vec::new(), immutable_refs: Vec::new() } } fn emit_op(&mut self, opcode: u8) { @@ -602,7 +578,7 @@ impl BytecodeAssembler { } fn emit_push_zero(&mut self) { - if self.config.evm_version.has_push0() { + if self.gcx.sess.opts.evm_version.has_push0() { self.bytecode.push(op::PUSH0); } else { self.bytecode.push(op::PUSH1); @@ -615,12 +591,12 @@ impl BytecodeAssembler { } fn zero_push_len(&self) -> usize { - if self.config.evm_version.has_push0() { 1 } else { 2 } + if self.gcx.sess.opts.evm_version.has_push0() { 1 } else { 2 } } /// Returns the minimum immediate width needed to push a value for this EVM version. fn push_width(&self, value: U256) -> u8 { - if value.is_zero() && !self.config.evm_version.has_push0() { + if value.is_zero() && !self.gcx.sess.opts.evm_version.has_push0() { 1 } else { value.byte_len() as u8 @@ -637,12 +613,27 @@ mod tests { use super::*; use crate::backend::evm::test_utils::disassemble; use snapbox::{assert_data_eq, str}; + use solar_config::{CompileOpts, EvmVersion}; + use solar_interface::Session; + use solar_sema::Compiler; + + fn with_assembler(opts: CompileOpts, f: impl FnOnce(Assembler<'_>) -> T + Send) -> T { + let compiler = Compiler::new(Session::builder().opts(opts).build()); + compiler.enter(|c| f(Assembler::new(c.gcx()))) + } + + fn size_optimized_opts() -> CompileOpts { + opts(EvmVersion::Shanghai, OptimizationMode::Size) + } - fn size_optimized_assembler() -> Assembler { - Assembler::with_config(AssemblerConfig { - evm_version: EvmVersion::Shanghai, - optimization: OptimizationMode::Size, - ..AssemblerConfig::default() + fn opts(evm_version: EvmVersion, optimization: OptimizationMode) -> CompileOpts { + CompileOpts { evm_version, optimization, ..Default::default() } + } + + fn assemble(opts: CompileOpts, f: impl FnOnce(&mut Assembler<'_>) + Send) -> AssembledCode { + with_assembler(opts, |mut asm| { + f(&mut asm); + asm.assemble() }) } @@ -679,65 +670,62 @@ mod tests { #[test] fn push_values_are_inline_or_interned() { - let mut asm = Assembler::new(); - let inline = u32::MAX >> 1; - let large = U256::from(1u64 << 31); - - assert!(AsmInst::push_inline(inline).is_some()); - assert!(AsmInst::push_inline(1u32 << 31).is_none()); - - let inline = asm.push_inst(U256::from(inline)); - let first = asm.push_inst(large); - let second = asm.push_inst(large); - - assert_eq!(inline.kind(), AsmInstKind::PushInline(u32::MAX >> 1)); - assert_eq!(first.kind(), AsmInstKind::Push(PushValueId::from_usize(0))); - assert_eq!(first, second); - assert_eq!(asm.push_values.len(), 1); - assert_eq!(*asm.push_values.get(PushValueId::from_usize(0)), large); + with_assembler(CompileOpts::default(), |mut asm| { + let inline = u32::MAX >> 1; + let large = U256::from(1u64 << 31); + + assert!(AsmInst::push_inline(inline).is_some()); + assert!(AsmInst::push_inline(1u32 << 31).is_none()); + + let inline = asm.push_inst(U256::from(inline)); + let first = asm.push_inst(large); + let second = asm.push_inst(large); + + assert_eq!(inline.kind(), AsmInstKind::PushInline(u32::MAX >> 1)); + assert_eq!(first.kind(), AsmInstKind::Push(PushValueId::from_usize(0))); + assert_eq!(first, second); + assert_eq!(asm.push_values.len(), 1); + assert_eq!(*asm.push_values.get(PushValueId::from_usize(0)), large); + }); } #[test] fn assembler_can_be_reused_after_assembly() { - let mut asm = Assembler::new(); - let large = U256::from(1u64 << 31); + with_assembler(CompileOpts::default(), |mut asm| { + let large = U256::from(1u64 << 31); - asm.emit_push(large); - let first = asm.assemble(); + asm.emit_push(large); + let first = asm.assemble(); - assert_data_eq!( - disassemble(&first.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&first.bytecode), + str![[r#" PUSH4 0x80000000 "#]] - ); - assert!(asm.program.blocks.is_empty()); - assert_eq!(asm.push_values.len(), 0); + ); + assert!(asm.program.blocks.is_empty()); + assert_eq!(asm.push_values.len(), 0); - asm.emit_push(U256::from(2)); - let second = asm.assemble(); + asm.emit_push(U256::from(2)); + let second = asm.assemble(); - assert_data_eq!( - disassemble(&second.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&second.bytecode), + str![[r#" PUSH1 0x02 "#]] - ); + ); + }); } #[test] fn push_zero_uses_push0_when_available() { - let mut asm = Assembler::with_config(AssemblerConfig { - evm_version: EvmVersion::Shanghai, - optimization: OptimizationMode::None, - ..AssemblerConfig::default() + let result = assemble(opts(EvmVersion::Shanghai, OptimizationMode::None), |asm| { + asm.emit_push(U256::ZERO); }); - asm.emit_push(U256::ZERO); - let result = asm.assemble(); - assert_data_eq!( disassemble(&result.bytecode), str![[r#" @@ -749,15 +737,10 @@ PUSH0 #[test] fn push_zero_uses_push1_before_shanghai() { - let mut asm = Assembler::with_config(AssemblerConfig { - evm_version: EvmVersion::Berlin, - optimization: OptimizationMode::Gas, - ..AssemblerConfig::default() + let result = assemble(opts(EvmVersion::Berlin, OptimizationMode::Gas), |asm| { + asm.emit_push(U256::ZERO); }); - asm.emit_push(U256::ZERO); - let result = asm.assemble(); - assert_data_eq!( disassemble(&result.bytecode), str![[r#" @@ -769,29 +752,15 @@ PUSH1 0x00 #[test] fn compact_push_respects_optimization_mode() { - let mut size_optimized = Assembler::with_config(AssemblerConfig { - evm_version: EvmVersion::Shanghai, - optimization: OptimizationMode::Size, - ..AssemblerConfig::default() - }); - size_optimized.emit_push(U256::MAX); - - let mut gas_optimized = Assembler::with_config(AssemblerConfig { - evm_version: EvmVersion::Shanghai, - optimization: OptimizationMode::Gas, - ..AssemblerConfig::default() - }); - gas_optimized.emit_push(U256::MAX); - - let mut unoptimized = Assembler::with_config(AssemblerConfig { - evm_version: EvmVersion::Shanghai, - optimization: OptimizationMode::None, - ..AssemblerConfig::default() - }); - unoptimized.emit_push(U256::MAX); + let assemble_push = |optimization| { + assemble(opts(EvmVersion::Shanghai, optimization), |asm| asm.emit_push(U256::MAX)) + }; + let size_optimized = assemble_push(OptimizationMode::Size); + let gas_optimized = assemble_push(OptimizationMode::Gas); + let unoptimized = assemble_push(OptimizationMode::None); assert_data_eq!( - disassemble(&size_optimized.assemble().bytecode), + disassemble(&size_optimized.bytecode), str![[r#" PUSH0 NOT @@ -799,7 +768,7 @@ NOT "#]] ); assert_data_eq!( - disassemble(&gas_optimized.assemble().bytecode), + disassemble(&gas_optimized.bytecode), str![[r#" PUSH0 NOT @@ -807,7 +776,7 @@ NOT "#]] ); assert_data_eq!( - disassemble(&unoptimized.assemble().bytecode), + disassemble(&unoptimized.bytecode), str![[r#" PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff @@ -817,15 +786,10 @@ PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff #[test] fn compact_push_uses_push1_zero_before_shanghai() { - let mut asm = Assembler::with_config(AssemblerConfig { - evm_version: EvmVersion::Berlin, - optimization: OptimizationMode::Size, - ..AssemblerConfig::default() + let result = assemble(opts(EvmVersion::Berlin, OptimizationMode::Size), |asm| { + asm.emit_push(U256::MAX); }); - asm.emit_push(U256::MAX); - let result = asm.assemble(); - assert_data_eq!( disassemble(&result.bytecode), str![[r#" @@ -838,48 +802,47 @@ NOT #[test] fn test_simple_assembly() { - let mut asm = Assembler::new(); - - asm.emit_push(U256::from(42)); - asm.emit_push(U256::from(10)); - asm.emit_op(op::ADD); - asm.emit_op(op::STOP); + with_assembler(CompileOpts::default(), |mut asm| { + asm.emit_push(U256::from(42)); + asm.emit_push(U256::from(10)); + asm.emit_op(op::ADD); + asm.emit_op(op::STOP); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH1 0x2a PUSH1 0x0a ADD "#]] - ); + ); + }); } #[test] fn test_label_resolution() { - let mut asm = Assembler::new(); - - let loop_label = asm.new_label(); - let end_label = asm.new_label(); + with_assembler(CompileOpts::default(), |mut asm| { + let loop_label = asm.new_label(); + let end_label = asm.new_label(); - asm.define_label(loop_label); - asm.emit_push(U256::from(1)); - asm.emit_push_label(end_label); - asm.emit_op(op::JUMPI); - asm.emit_push_label(loop_label); - asm.emit_op(op::JUMP); + asm.define_label(loop_label); + asm.emit_push(U256::from(1)); + asm.emit_push_label(end_label); + asm.emit_op(op::JUMPI); + asm.emit_push_label(loop_label); + asm.emit_op(op::JUMP); - asm.define_label(end_label); - asm.emit_op(op::STOP); + asm.define_label(end_label); + asm.emit_op(op::STOP); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" JUMPDEST PUSH1 0x01 PUSH1 0x08 @@ -889,30 +852,26 @@ JUMP JUMPDEST "#]] - ); + ); + }); } #[test] fn label_push_width_relaxation_cascades() { - let mut asm = Assembler::with_config(AssemblerConfig { - evm_version: EvmVersion::Shanghai, - optimization: OptimizationMode::None, - ..AssemblerConfig::default() + let result = assemble(opts(EvmVersion::Shanghai, OptimizationMode::None), |asm| { + let first = asm.new_label(); + let second = asm.new_label(); + + asm.emit_push_label(first); + asm.define_label(first); + asm.emit_push_label(second); + for _ in 0..7 { + asm.emit_push(U256::MAX); + } + asm.emit_push(U256::from(1) << 144); + asm.define_label(second); + asm.emit_op(op::STOP); }); - let first = asm.new_label(); - let second = asm.new_label(); - - asm.emit_push_label(first); - asm.define_label(first); - asm.emit_push_label(second); - for _ in 0..7 { - asm.emit_push(U256::MAX); - } - asm.emit_push(U256::from(1) << 144); - asm.define_label(second); - asm.emit_op(op::STOP); - - let result = asm.assemble(); assert_data_eq!( disassemble(&result.bytecode), @@ -936,28 +895,28 @@ JUMPDEST #[test] fn cold_terminal_block_moves_after_hot_block() { - let mut asm = Assembler::new(); - let cold = asm.new_label(); - let hot = asm.new_label(); - - asm.emit_push(U256::ONE); - asm.emit_push_label(cold); - asm.emit_op(op::JUMPI); - asm.emit_push_label(hot); - asm.emit_op(op::JUMP); - asm.mark_label_cold(cold); - asm.define_label(cold); - asm.emit_push(U256::ZERO); - asm.emit_push(U256::ZERO); - asm.emit_op(op::REVERT); - asm.define_label(hot); - asm.emit_op(op::STOP); - - let result = asm.assemble(); + with_assembler(CompileOpts::default(), |mut asm| { + let cold = asm.new_label(); + let hot = asm.new_label(); + + asm.emit_push(U256::ONE); + asm.emit_push_label(cold); + asm.emit_op(op::JUMPI); + asm.emit_push_label(hot); + asm.emit_op(op::JUMP); + asm.mark_label_cold(cold); + asm.define_label(cold); + asm.emit_push(U256::ZERO); + asm.emit_push(U256::ZERO); + asm.emit_op(op::REVERT); + asm.define_label(hot); + asm.emit_op(op::STOP); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + let result = asm.assemble(); + + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH1 0x01 PUSH1 0x06 JUMPI @@ -968,32 +927,33 @@ PUSH0 REVERT "#]] - ); + ); + }); } #[test] fn block_layout_materializes_moved_implicit_stop() { - let mut asm = Assembler::new(); - let cold = asm.new_label(); - let eof = asm.new_label(); - - asm.emit_push(U256::ONE); - asm.emit_push_label(cold); - asm.emit_op(op::JUMPI); - asm.emit_push_label(eof); - asm.emit_op(op::JUMP); - asm.mark_label_cold(cold); - asm.define_label(cold); - asm.emit_push(U256::ZERO); - asm.emit_push(U256::ZERO); - asm.emit_op(op::REVERT); - asm.define_label(eof); - - let result = asm.assemble(); + with_assembler(CompileOpts::default(), |mut asm| { + let cold = asm.new_label(); + let eof = asm.new_label(); + + asm.emit_push(U256::ONE); + asm.emit_push_label(cold); + asm.emit_op(op::JUMPI); + asm.emit_push_label(eof); + asm.emit_op(op::JUMP); + asm.mark_label_cold(cold); + asm.define_label(cold); + asm.emit_push(U256::ZERO); + asm.emit_push(U256::ZERO); + asm.emit_op(op::REVERT); + asm.define_label(eof); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + let result = asm.assemble(); + + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH1 0x01 PUSH1 0x06 JUMPI @@ -1004,31 +964,32 @@ PUSH0 REVERT "#]] - ); + ); + }); } #[test] fn terminal_dedup_labels_prior_unlabeled_target() { - let mut asm = Assembler::new(); - let duplicate = asm.new_label(); + with_assembler(CompileOpts::default(), |mut asm| { + let duplicate = asm.new_label(); - for copy in 0..2 { - if copy == 1 { - asm.define_label(duplicate); + for copy in 0..2 { + if copy == 1 { + asm.define_label(duplicate); + } + asm.emit_push(U256::from(0x1234)); + asm.emit_push(U256::ZERO); + asm.emit_op(op::MSTORE); + asm.emit_push(U256::ZERO); + asm.emit_push(U256::ZERO); + asm.emit_op(op::REVERT); } - asm.emit_push(U256::from(0x1234)); - asm.emit_push(U256::ZERO); - asm.emit_op(op::MSTORE); - asm.emit_push(U256::ZERO); - asm.emit_push(U256::ZERO); - asm.emit_op(op::REVERT); - } - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH2 0x1234 PUSH0 MSTORE @@ -1037,30 +998,31 @@ PUSH0 REVERT "#]] - ); + ); + }); } #[test] fn block_layout_elides_jump_after_jumpi() { - let mut asm = Assembler::new(); - let conditional = asm.new_label(); - let default = asm.new_label(); - - asm.emit_push(U256::ONE); - asm.emit_push_label(conditional); - asm.emit_op(op::JUMPI); - asm.emit_push_label(default); - asm.emit_op(op::JUMP); - asm.define_label(conditional); - asm.emit_op(op::INVALID); - asm.define_label(default); - asm.emit_op(op::STOP); - - let result = asm.assemble(); - - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + with_assembler(CompileOpts::default(), |mut asm| { + let conditional = asm.new_label(); + let default = asm.new_label(); + + asm.emit_push(U256::ONE); + asm.emit_push_label(conditional); + asm.emit_op(op::JUMPI); + asm.emit_push_label(default); + asm.emit_op(op::JUMP); + asm.define_label(conditional); + asm.emit_op(op::INVALID); + asm.define_label(default); + asm.emit_op(op::STOP); + + let result = asm.assemble(); + + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH1 0x01 PUSH1 0x06 JUMPI @@ -1069,154 +1031,159 @@ JUMPDEST INVALID "#]] - ); + ); + }); } #[test] fn cold_terminal_block_keeps_fallthrough_position() { - let mut asm = Assembler::new(); - let cold = asm.new_label(); + with_assembler(CompileOpts::default(), |mut asm| { + let cold = asm.new_label(); - asm.emit_push(U256::ONE); - asm.mark_label_cold(cold); - asm.define_label(cold); - asm.emit_push(U256::ZERO); - asm.emit_push(U256::ZERO); - asm.emit_op(op::REVERT); + asm.emit_push(U256::ONE); + asm.mark_label_cold(cold); + asm.define_label(cold); + asm.emit_push(U256::ZERO); + asm.emit_push(U256::ZERO); + asm.emit_op(op::REVERT); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH1 0x01 PUSH0 PUSH0 REVERT "#]] - ); + ); + }); } #[test] fn compact_full_word_all_ones_push() { - let mut asm = size_optimized_assembler(); - - asm.emit_push(U256::MAX); - asm.emit_op(op::STOP); + with_assembler(size_optimized_opts(), |mut asm| { + asm.emit_push(U256::MAX); + asm.emit_op(op::STOP); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH0 NOT "#]] - ); + ); + }); } #[test] fn compact_lower_all_ones_mask_push() { - let mut asm = size_optimized_assembler(); - let mask = (U256::from(1) << 160) - U256::from(1); + with_assembler(size_optimized_opts(), |mut asm| { + let mask = (U256::from(1) << 160) - U256::from(1); - asm.emit_push(mask); - asm.emit_op(op::STOP); + asm.emit_push(mask); + asm.emit_op(op::STOP); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH0 NOT PUSH1 0x60 SHR "#]] - ); + ); + }); } #[test] fn compact_not_small_push() { - let mut asm = size_optimized_assembler(); - - asm.emit_push(!U256::from(31)); - asm.emit_op(op::STOP); + with_assembler(size_optimized_opts(), |mut asm| { + asm.emit_push(!U256::from(31)); + asm.emit_op(op::STOP); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH1 0x1f NOT "#]] - ); + ); + }); } #[test] fn compact_not_byte_push() { - let mut asm = size_optimized_assembler(); - - asm.emit_push(!U256::from(255)); - asm.emit_op(op::STOP); + with_assembler(size_optimized_opts(), |mut asm| { + asm.emit_push(!U256::from(255)); + asm.emit_op(op::STOP); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH1 0xff NOT "#]] - ); + ); + }); } #[test] fn compact_left_aligned_selector_push() { - let mut asm = size_optimized_assembler(); - let selector = U256::from(0x35ea6a75u64) << 224; + with_assembler(size_optimized_opts(), |mut asm| { + let selector = U256::from(0x35ea6a75u64) << 224; - asm.emit_push(selector); - asm.emit_op(op::STOP); + asm.emit_push(selector); + asm.emit_op(op::STOP); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH4 0x35ea6a75 PUSH1 0xe0 SHL "#]] - ); + ); + }); } #[test] fn compact_right_padded_text_push() { - let mut asm = size_optimized_assembler(); - let text = U256::from_be_slice(b"Machine finished:"); - let value = text << ((32 - "Machine finished:".len()) * 8); + with_assembler(size_optimized_opts(), |mut asm| { + let text = U256::from_be_slice(b"Machine finished:"); + let value = text << ((32 - "Machine finished:".len()) * 8); - asm.emit_push(value); - asm.emit_op(op::STOP); + asm.emit_push(value); + asm.emit_op(op::STOP); - let result = asm.assemble(); + let result = asm.assemble(); - assert_data_eq!( - disassemble(&result.bytecode), - str![[r#" + assert_data_eq!( + disassemble(&result.bytecode), + str![[r#" PUSH17 0x4d616368696e652066696e69736865643a PUSH1 0x78 SHL "#]] - ); + ); + }); } } diff --git a/crates/codegen/src/backend/evm/codegen.rs b/crates/codegen/src/backend/evm/codegen.rs index 39e507cb0..c4fe66b54 100644 --- a/crates/codegen/src/backend/evm/codegen.rs +++ b/crates/codegen/src/backend/evm/codegen.rs @@ -7,7 +7,7 @@ //! - EVM IR optimization, relocation, and byte encoding use super::{ - assembler::{Assembler, AssemblerConfig, DeferredConst, ImmutableRef, Label, PreparedAssembly}, + assembler::{Assembler, DeferredConst, ImmutableRef, Label, PreparedAssembly}, ir, op, stack::{ MAX_STACK_ACCESS, ScheduledOp, SpillSlot, StackModel, StackOp, StackScheduler, TargetSlot, @@ -20,16 +20,16 @@ use crate::{ PhiEliminator, }, mir::{BlockId, Function, FunctionId, InstId, InstKind, MirType, Module, Terminator, ValueId}, - pass::{PipelineOptions, run_default_pipeline, run_pass}, + pass::{run_default_pipeline, run_pass}, }; use alloy_primitives::U256; -use solar_config::{EvmVersion, OptimizationMode}; +use solar_config::OptimizationMode; use solar_data_structures::{ bit_set::{DenseBitSet, GrowableBitSet}, map::FxHashMap, }; -use solar_interface::{Session, sym}; -use solar_sema::hir::StateMutability; +use solar_interface::sym; +use solar_sema::{Gcx, hir::StateMutability}; // 0x00..0x7f follows Solidity's scratch/free-pointer/zero-slot convention, and // 0x80 is used as the static ABI return buffer. Keep the internal-call frame @@ -58,59 +58,6 @@ struct PreparedDeploymentPrefix { runtime_offset: DeferredConst, } -/// Configuration for the EVM backend. -#[derive(Clone, Copy, Debug, Default)] -pub struct EvmCodegenConfig { - /// EVM version to target when selecting hardfork-gated opcodes. - pub(crate) evm_version: EvmVersion, - /// Optimization mode for MIR and EVM IR passes. - pub(crate) optimization: OptimizationMode, - /// Print MIR after each pass before bytecode generation. - pub(crate) mir_print_after_each: bool, - /// Print the time spent in each MIR and EVM IR pass. - pub(crate) time_passes: bool, - /// Lower dispatch/ABI as MIR phases and consume them here. - pub(crate) mir_dispatch: bool, - /// Capture final EVM IR immediately before assembly. - pub capture_evm_ir: bool, -} - -impl EvmCodegenConfig { - /// Creates backend configuration from a compiler session. - #[must_use] - fn from_session(sess: &Session) -> Self { - Self { - evm_version: sess.opts.evm_version, - optimization: sess.opts.optimization, - mir_print_after_each: sess.opts.unstable.mir_print_after_each, - time_passes: sess.opts.unstable.time_passes, - mir_dispatch: !sess.opts.unstable.no_mir_dispatch, - capture_evm_ir: false, - } - } - - fn assembler_config(self) -> AssemblerConfig { - AssemblerConfig { - evm_version: self.evm_version, - optimization: self.optimization, - time_passes: self.time_passes, - capture_evm_ir: self.capture_evm_ir, - } - } -} - -impl From<&Session> for EvmCodegenConfig { - fn from(sess: &Session) -> Self { - Self::from_session(sess) - } -} - -impl From> for EvmCodegenConfig { - fn from(gcx: solar_sema::Gcx<'_>) -> Self { - Self::from_session(gcx.sess) - } -} - /// Describes the stack effect of an EVM instruction. /// This is used to keep the scheduler's stack model in sync with the actual EVM stack. #[derive(Clone, Copy, Debug)] @@ -559,9 +506,10 @@ impl<'a> StackPhiPlanner<'a> { } /// EVM code generator. -pub struct EvmCodegen { +pub struct EvmCodegen<'gcx> { + gcx: Gcx<'gcx>, /// The assembler for bytecode generation. - asm: Assembler, + asm: Assembler<'gcx>, /// Stack scheduler. scheduler: StackScheduler, /// Block labels. @@ -643,21 +591,16 @@ pub struct EvmCodegen { /// so the leftover word can neither accumulate nor disturb an internal /// return. emitting_dispatch_entry: bool, - /// Optimization mode for MIR and EVM IR passes. - optimization: OptimizationMode, - /// Print MIR after each pass before bytecode generation. - mir_print_after_each: bool, - time_passes: bool, - mir_dispatch: bool, + capture_evm_ir: bool, } -impl EvmCodegen { +impl<'gcx> EvmCodegen<'gcx> { /// Creates a new EVM code generator. #[must_use] - pub fn new(config: impl Into) -> Self { - let config = config.into(); + pub fn new(gcx: Gcx<'gcx>) -> Self { Self { - asm: Assembler::with_config(config.assembler_config()), + gcx, + asm: Assembler::new(gcx), scheduler: StackScheduler::new(), block_labels: FxHashMap::default(), function_labels: FxHashMap::default(), @@ -685,13 +628,15 @@ impl EvmCodegen { constructor_param_count: 0, in_internal_function: false, emitting_dispatch_entry: false, - optimization: config.optimization, - mir_print_after_each: config.mir_print_after_each, - time_passes: config.time_passes, - mir_dispatch: config.mir_dispatch, + capture_evm_ir: false, } } + /// Controls whether generated artifacts include final EVM IR. + pub fn set_capture_evm_ir(&mut self, capture: bool) { + self.capture_evm_ir = capture; + } + // ==================== Stack-Aware Emitter API ==================== // // These helpers ensure that all EVM stack mutations are tracked by the scheduler. @@ -915,11 +860,12 @@ impl EvmCodegen { self.block_labels.clear(); self.block_copies.clear(); self.function_labels.clear(); - self.cold_functions = if matches!(self.optimization, OptimizationMode::None) { - DenseBitSet::new_empty(module.functions.len()) - } else { - Self::collect_cold_functions(module) - }; + self.cold_functions = + if matches!(self.gcx.sess.opts.optimization, OptimizationMode::None) { + DenseBitSet::new_empty(module.functions.len()) + } else { + Self::collect_cold_functions(module) + }; self.function_static_frame_sizes.clear(); self.function_spill_sizes.clear(); self.pending_frame_size_consts.clear(); @@ -1023,7 +969,7 @@ impl EvmCodegen { self.emit_deployment_postlude(runtime_offset, runtime_len, copy_base, immutable_refs); PreparedDeploymentPrefix { - assembly: self.asm.prepare(), + assembly: self.asm.prepare(self.capture_evm_ir), constructor_arg_offset, runtime_offset, } @@ -1046,28 +992,22 @@ impl EvmCodegen { /// Runs the canonical MIR optimization pipeline on the module. fn run_optimization_passes(&mut self, module: &mut Module) { - module.optimize_for_size = self.optimization == OptimizationMode::Size; - let options = PipelineOptions { - print_after_each: self.mir_print_after_each, - time_passes: self.time_passes, - ..PipelineOptions::default() - }; - if self.optimization != OptimizationMode::None { - run_default_pipeline(module, options); + if self.gcx.sess.opts.optimization != OptimizationMode::None { + run_default_pipeline(self.gcx, module); // MIR outlining remains profitable even though EVM IR can merge // equivalent terminal blocks: lowering and stack scheduling can // hide their shared semantic shape from the backend passes. - run_pass(module, &crate::pass::OUTLINE_REVERTS_PASS, options); + run_pass(self.gcx, module, &crate::pass::OUTLINE_REVERTS_PASS); } - run_pass(module, &crate::pass::LOWER_MAPPING_SLOTS_PASS, options); + run_pass(self.gcx, module, &crate::pass::LOWER_MAPPING_SLOTS_PASS); // Progressive lowering: materialize ABI wrappers, the dispatcher, and // tail-call edges as MIR. Each pass bails without advancing the phase // when the module is outside its scope, in which case runtime // generation falls back to the backend dispatcher. - if self.mir_dispatch { - run_pass(module, &crate::pass::LOWER_ABI_PASS, options); - run_pass(module, &crate::pass::LOWER_DISPATCH_PASS, options); - run_pass(module, &crate::pass::LOWER_EVM_SHAPED_PASS, options); + if !self.gcx.sess.opts.unstable.no_mir_dispatch { + run_pass(self.gcx, module, &crate::pass::LOWER_ABI_PASS); + run_pass(self.gcx, module, &crate::pass::LOWER_DISPATCH_PASS); + run_pass(self.gcx, module, &crate::pass::LOWER_EVM_SHAPED_PASS); } } @@ -1076,7 +1016,7 @@ impl EvmCodegen { self.asm.clear(); self.block_labels.clear(); self.function_labels.clear(); - self.cold_functions = if matches!(self.optimization, OptimizationMode::None) { + self.cold_functions = if matches!(self.gcx.sess.opts.optimization, OptimizationMode::None) { DenseBitSet::new_empty(module.functions.len()) } else { Self::collect_cold_functions(module) @@ -1105,7 +1045,7 @@ impl EvmCodegen { } } - let result = self.asm.assemble(); + let result = self.asm.assemble_with_evm_ir(self.capture_evm_ir); self.runtime_immutable_refs = result.immutable_refs; GeneratedCode { bytecode: result.bytecode, evm_ir: result.evm_ir } } @@ -1885,7 +1825,7 @@ impl EvmCodegen { worklist.push(block_id); } } - if matches!(self.optimization, OptimizationMode::None) { + if matches!(self.gcx.sess.opts.optimization, OptimizationMode::None) { return cold; } @@ -1979,7 +1919,7 @@ impl EvmCodegen { *target } Some(Terminator::Branch { then_block, else_block, .. }) - if !matches!(self.optimization, OptimizationMode::None) => + if !matches!(self.gcx.sess.opts.optimization, OptimizationMode::None) => { match (self.block_is_cold(*then_block), self.block_is_cold(*else_block)) { (true, false) => *else_block, @@ -5018,12 +4958,6 @@ impl EvmCodegen { } } -impl Default for EvmCodegen { - fn default() -> Self { - Self::new(EvmCodegenConfig::default()) - } -} - /// The artifact produced by the EVM backend. #[derive(Clone, Debug, Default)] pub struct EvmArtifact { @@ -5037,7 +4971,7 @@ pub struct EvmArtifact { pub runtime_evm_ir: Option, } -impl crate::backend::Backend for EvmCodegen { +impl crate::backend::Backend for EvmCodegen<'_> { type Output = EvmArtifact; fn lower_module(&mut self, module: &mut Module) -> EvmArtifact { @@ -5055,41 +4989,45 @@ mod tests { use solar_sema::{Compiler, hir::Visibility}; use std::{ops::ControlFlow, path::PathBuf}; + fn with_codegen(opts: CompileOpts, f: impl FnOnce(EvmCodegen<'_>) -> T + Send) -> T { + let compiler = Compiler::new(Session::builder().opts(opts).build()); + compiler.enter(|c| f(EvmCodegen::new(c.gcx()))) + } + #[test] fn constructor_success_jumps_to_deployment_postlude() { - let mut module = Module::new(Ident::with_dummy_span(sym::Test)); - let mut constructor = Function::new(Ident::with_dummy_span(kw::Constructor)); - constructor.attributes.is_constructor = true; - { - let mut builder = FunctionBuilder::new(&mut constructor); - let condition = builder.imm_u64(1); - let revert = builder.create_block(); - let success = builder.create_block(); - builder.branch(condition, revert, success); - - builder.switch_to_block(revert); - let zero = builder.imm_u64(0); - builder.revert(zero, zero); - - builder.switch_to_block(success); - builder.stop(); - } - module.add_function(constructor); - - let mut codegen = EvmCodegen::new(EvmCodegenConfig::default()); - let prepared = codegen.prepare_deployment_prefix(&module, 0, 0, &[]); - let mut deploy_code_len = 0; - let deployment = loop { - let code = codegen.assemble_deployment_prefix(&prepared, 0, deploy_code_len); - if code.bytecode.len() == deploy_code_len { - break code; - } - deploy_code_len = code.bytecode.len(); - }; + with_codegen(CompileOpts::default(), |mut codegen| { + let mut module = Module::new(Ident::with_dummy_span(sym::Test)); + let mut constructor = Function::new(Ident::with_dummy_span(kw::Constructor)); + constructor.attributes.is_constructor = true; + { + let mut builder = FunctionBuilder::new(&mut constructor); + let condition = builder.imm_u64(1); + let revert = builder.create_block(); + let success = builder.create_block(); + builder.branch(condition, revert, success); + + builder.switch_to_block(revert); + let zero = builder.imm_u64(0); + builder.revert(zero, zero); + + builder.switch_to_block(success); + builder.stop(); + } + module.add_function(constructor); + let prepared = codegen.prepare_deployment_prefix(&module, 0, 0, &[]); + let mut deploy_code_len = 0; + let deployment = loop { + let code = codegen.assemble_deployment_prefix(&prepared, 0, deploy_code_len); + if code.bytecode.len() == deploy_code_len { + break code; + } + deploy_code_len = code.bytecode.len(); + }; - assert_data_eq!( - disassemble(&deployment.bytecode), - snapbox::str![[r#" + assert_data_eq!( + disassemble(&deployment.bytecode), + snapbox::str![[r#" PUSH2 0x4000 PUSH1 0x40 MSTORE @@ -5109,83 +5047,92 @@ DUP1 REVERT "#]] - ); + ); + }); } #[test] fn empty_external_return_falls_off_end() { - let mut function = Function::new(Ident::with_dummy_span(sym::Test)); - function.attributes.visibility = Visibility::External; - FunctionBuilder::new(&mut function).ret(Vec::new()); + with_codegen(CompileOpts::default(), |mut codegen| { + let mut function = Function::new(Ident::with_dummy_span(sym::Test)); + function.attributes.visibility = Visibility::External; + FunctionBuilder::new(&mut function).ret(Vec::new()); + codegen.generate_function_body(&function); - let mut codegen = EvmCodegen::new(EvmCodegenConfig::default()); - codegen.generate_function_body(&function); - - assert!(codegen.asm.assemble().bytecode.is_empty()); + assert!(codegen.asm.assemble().bytecode.is_empty()); + }); } #[test] fn cold_forwarder_selects_hot_fallthrough() { - let mut module = Module::new(Ident::with_dummy_span(sym::Test)); + let (module, caller_id, entry, cold_forwarder, cold_block, hot_block) = + with_codegen(CompileOpts::default(), |mut codegen| { + let mut module = Module::new(Ident::with_dummy_span(sym::Test)); - let mut cold_func = Function::new(Ident::with_dummy_span(sym::__revert_error)); - { - let mut builder = FunctionBuilder::new(&mut cold_func); - let zero = builder.imm_u64(0); - builder.revert(zero, zero); - } - let cold_func = module.add_function(cold_func); + let mut cold_func = Function::new(Ident::with_dummy_span(sym::__revert_error)); + { + let mut builder = FunctionBuilder::new(&mut cold_func); + let zero = builder.imm_u64(0); + builder.revert(zero, zero); + } + let cold_func = module.add_function(cold_func); - let mut cold_wrapper = Function::new(Ident::with_dummy_span(sym::Test)); - { - let mut builder = FunctionBuilder::new(&mut cold_wrapper); - builder.internal_call_void(cold_func, Vec::new(), 0); - builder.ret(Vec::new()); - } - let cold_wrapper = module.add_function(cold_wrapper); + let mut cold_wrapper = Function::new(Ident::with_dummy_span(sym::Test)); + { + let mut builder = FunctionBuilder::new(&mut cold_wrapper); + builder.internal_call_void(cold_func, Vec::new(), 0); + builder.ret(Vec::new()); + } + let cold_wrapper = module.add_function(cold_wrapper); - let mut caller = Function::new(Ident::with_dummy_span(sym::Test)); - let entry = caller.entry_block; - let (cold_forwarder, cold_block, hot_block); - { - let mut builder = FunctionBuilder::new(&mut caller); - let condition = builder.add_param(MirType::Bool); - cold_forwarder = builder.create_block(); - cold_block = builder.create_block(); - hot_block = builder.create_block(); - builder.branch(condition, cold_forwarder, hot_block); - - builder.switch_to_block(cold_forwarder); - builder.jump(cold_block); - - builder.switch_to_block(cold_block); - builder.tail_call(cold_wrapper, Vec::new()); - - builder.switch_to_block(hot_block); - builder.ret(Vec::new()); - } - let caller = module.add_function(caller); - - let mut codegen = EvmCodegen::new(EvmCodegenConfig::default()); - codegen.cold_functions = EvmCodegen::collect_cold_functions(&module); - let caller = &module.functions[caller]; - codegen.cold_blocks = codegen.collect_cold_blocks(caller); - - assert!(codegen.cold_functions.contains(cold_func)); - assert!(codegen.cold_functions.contains(cold_wrapper)); - assert!(codegen.block_aborts(caller, cold_block)); - assert!(!codegen.block_aborts(caller, cold_forwarder)); - assert!(codegen.block_is_cold(cold_forwarder)); - assert_eq!( - codegen.block_layout_order(caller), - [entry, hot_block, cold_forwarder, cold_block] - ); + let mut caller = Function::new(Ident::with_dummy_span(sym::Test)); + let entry = caller.entry_block; + let (cold_forwarder, cold_block, hot_block); + { + let mut builder = FunctionBuilder::new(&mut caller); + let condition = builder.add_param(MirType::Bool); + cold_forwarder = builder.create_block(); + cold_block = builder.create_block(); + hot_block = builder.create_block(); + builder.branch(condition, cold_forwarder, hot_block); - codegen.optimization = OptimizationMode::None; - assert_eq!( - codegen.block_layout_order(caller), - [entry, cold_forwarder, cold_block, hot_block] - ); + builder.switch_to_block(cold_forwarder); + builder.jump(cold_block); + + builder.switch_to_block(cold_block); + builder.tail_call(cold_wrapper, Vec::new()); + + builder.switch_to_block(hot_block); + builder.ret(Vec::new()); + } + let caller_id = module.add_function(caller); + codegen.cold_functions = EvmCodegen::collect_cold_functions(&module); + let caller = &module.functions[caller_id]; + codegen.cold_blocks = codegen.collect_cold_blocks(caller); + + assert!(codegen.cold_functions.contains(cold_func)); + assert!(codegen.cold_functions.contains(cold_wrapper)); + assert!(codegen.block_aborts(caller, cold_block)); + assert!(!codegen.block_aborts(caller, cold_forwarder)); + assert!(codegen.block_is_cold(cold_forwarder)); + assert_eq!( + codegen.block_layout_order(caller), + [entry, hot_block, cold_forwarder, cold_block] + ); + + (module, caller_id, entry, cold_forwarder, cold_block, hot_block) + }); + + let opts = CompileOpts { optimization: OptimizationMode::None, ..Default::default() }; + with_codegen(opts, |mut codegen| { + codegen.cold_functions = EvmCodegen::collect_cold_functions(&module); + let caller = &module.functions[caller_id]; + codegen.cold_blocks = codegen.collect_cold_blocks(caller); + assert_eq!( + codegen.block_layout_order(caller), + [entry, cold_forwarder, cold_block, hot_block] + ); + }); } /// Helper to compile Solidity source to bytecode, returning Result. @@ -5226,7 +5173,7 @@ REVERT for (contract_id, contract) in gcx.hir.contracts_enumerated() { if contract.name.name == sym::Test { let mut module = lower::lower_contract(gcx, contract_id); - let mut codegen = EvmCodegen::new(EvmCodegenConfig::from(gcx)); + let mut codegen = EvmCodegen::new(gcx); let bytecode = codegen.generate_deployment_artifact(&mut module).runtime; return Ok(bytecode); } diff --git a/crates/codegen/src/backend/evm/ir/assembly/lower.rs b/crates/codegen/src/backend/evm/ir/assembly/lower.rs index dca2a2c02..c84e3e581 100644 --- a/crates/codegen/src/backend/evm/ir/assembly/lower.rs +++ b/crates/codegen/src/backend/evm/ir/assembly/lower.rs @@ -12,7 +12,7 @@ use solar_data_structures::bit_set::DenseBitSet; pub(in crate::backend::evm) fn lower_evm_ir( module: &ir::Module, labels: &mut Vec>, - assembler: &mut Assembler, + assembler: &mut Assembler<'_>, ) -> Program { allocate_referenced_labels(module, labels, assembler); @@ -37,7 +37,7 @@ pub(in crate::backend::evm) fn lower_evm_ir( fn allocate_referenced_labels( module: &ir::Module, labels: &mut Vec>, - assembler: &mut Assembler, + assembler: &mut Assembler<'_>, ) { let mut referenced = DenseBitSet::new_empty(module.blocks.len()); for (block_id, block) in module.blocks.iter_enumerated() { @@ -70,7 +70,7 @@ fn lower_instruction( inst: &ir::Instruction, module: &ir::Module, labels: &mut Vec>, - assembler: &mut Assembler, + assembler: &mut Assembler<'_>, ) -> AsmInst { if let Some(id) = inst.deferred_push() { AsmInst::push_deferred(id) @@ -95,7 +95,7 @@ fn lower_terminator( kind: &ir::TerminatorKind, module: &ir::Module, labels: &mut Vec>, - assembler: &mut Assembler, + assembler: &mut Assembler<'_>, ) { match kind { ir::TerminatorKind::Jump(target) => { @@ -143,7 +143,7 @@ fn label_for_block( module: &ir::Module, block: BlockId, labels: &mut Vec>, - assembler: &mut Assembler, + assembler: &mut Assembler<'_>, ) -> Label { let original = module.blocks[block].label as usize; if original >= labels.len() { @@ -160,6 +160,8 @@ mod tests { ir::{Block, Instruction, Terminator, TerminatorKind}, }; use alloy_primitives::U256; + use solar_interface::Session; + use solar_sema::Compiler; #[test] fn branch_inverts_when_then_target_falls_through() { let mut module = ir::Module::new("module"); @@ -172,21 +174,24 @@ mod tests { module.blocks[then_block].terminator = Some(Terminator::new(TerminatorKind::Op(op::STOP))); module.blocks[else_block].terminator = Some(Terminator::new(TerminatorKind::Op(op::STOP))); - let mut labels = vec![None; 3]; - let mut assembler = Assembler::new(); - let program = lower_evm_ir(&module, &mut labels, &mut assembler); - let kinds: Vec<_> = program.instructions.iter().map(|inst| inst.kind()).collect(); + let compiler = Compiler::new(Session::builder().opts(Default::default()).build()); + compiler.enter(|c| { + let mut labels = vec![None; 3]; + let mut assembler = Assembler::new(c.gcx()); + let program = lower_evm_ir(&module, &mut labels, &mut assembler); + let kinds: Vec<_> = program.instructions.iter().map(|inst| inst.kind()).collect(); - assert!(matches!( - kinds.as_slice(), - [ - AsmInstKind::PushInline(1), - AsmInstKind::Op(op::ISZERO), - AsmInstKind::PushLabel(_), - AsmInstKind::Op(op::JUMPI), - AsmInstKind::Op(op::STOP), - AsmInstKind::Label(_), - ] - )); + assert!(matches!( + kinds.as_slice(), + [ + AsmInstKind::PushInline(1), + AsmInstKind::Op(op::ISZERO), + AsmInstKind::PushLabel(_), + AsmInstKind::Op(op::JUMPI), + AsmInstKind::Op(op::STOP), + AsmInstKind::Label(_), + ] + )); + }); } } diff --git a/crates/codegen/src/backend/evm/ir/mod.rs b/crates/codegen/src/backend/evm/ir/mod.rs index 792dbd8d8..21ac0594a 100644 --- a/crates/codegen/src/backend/evm/ir/mod.rs +++ b/crates/codegen/src/backend/evm/ir/mod.rs @@ -22,7 +22,7 @@ mod verify; pub(in crate::backend::evm) mod assembly; -pub use passes::{PASS_REGISTRY, PassInfo, PassOptions, lookup_pass, run_pass}; +pub use passes::{PASS_REGISTRY, PassInfo, lookup_pass, run_pass}; pub(crate) use passes::DEFAULT_PIPELINE; diff --git a/crates/codegen/src/backend/evm/ir/passes/block_layout.rs b/crates/codegen/src/backend/evm/ir/passes/block_layout.rs index d7e38754f..a275c1652 100644 --- a/crates/codegen/src/backend/evm/ir/passes/block_layout.rs +++ b/crates/codegen/src/backend/evm/ir/passes/block_layout.rs @@ -15,8 +15,9 @@ use crate::backend::evm::{ }; use alloy_primitives::U256; use solar_data_structures::bit_set::DenseBitSet; +use solar_sema::Gcx; -pub(super) fn run(module: &mut Module, options: super::PassOptions) -> bool { +pub(super) fn run(gcx: Gcx<'_>, module: &mut Module) -> bool { if module.blocks.len() <= 1 { return false; } @@ -43,7 +44,7 @@ pub(super) fn run(module: &mut Module, options: super::PassOptions) -> bool { } } - pack_hot_terminal_blocks(module, &mut state, options); + pack_hot_terminal_blocks(gcx, module, &mut state); for cold in [false, true] { for block in module.blocks.indices() { if is_cold_terminal_block(&module.blocks[block]) == cold { @@ -112,7 +113,7 @@ struct Candidate { references: usize, } -fn pack_hot_terminal_blocks(module: &Module, state: &mut RunState, options: super::PassOptions) { +fn pack_hot_terminal_blocks(gcx: Gcx<'_>, module: &Module, state: &mut RunState) { let Some(first_terminal) = state.order.iter().enumerate().position(|(position, &block)| { is_physical_terminal_boundary(&module.blocks[block], state.order.get(position + 1).copied()) }) else { @@ -125,10 +126,10 @@ fn pack_hot_terminal_blocks(module: &Module, state: &mut RunState, options: supe .enumerate() .map(|(index, &block)| { estimated_block_size( + gcx, &module.blocks[block], state.order.get(index + 1).copied(), state.references[block.index()] != 0, - options, ) }) .sum(); @@ -148,10 +149,10 @@ fn pack_hot_terminal_blocks(module: &Module, state: &mut RunState, options: supe continue; } let size = estimated_block_size( + gcx, &module.blocks[block], state.order.get(position + 1).copied(), state.references[block.index()] != 0, - options, ); let count = state.references[block.index()]; if size <= 32 && count >= 2 { @@ -196,31 +197,24 @@ fn block_reference_counts(module: &Module, order: &[BlockId], references: &mut [ } fn estimated_block_size( + gcx: Gcx<'_>, block: &Block, next: Option, addressed: bool, - options: super::PassOptions, ) -> usize { usize::from(addressed) - + block - .instructions - .iter() - .map(|inst| estimated_instruction_size(inst, options)) - .sum::() - + block - .terminator - .as_ref() - .map_or(0, |term| estimated_terminator_size(&term.kind, next, options)) + + block.instructions.iter().map(|inst| estimated_instruction_size(gcx, inst)).sum::() + + block.terminator.as_ref().map_or(0, |term| estimated_terminator_size(&term.kind, next)) } -fn estimated_instruction_size(inst: &Instruction, options: super::PassOptions) -> usize { +fn estimated_instruction_size(gcx: Gcx<'_>, inst: &Instruction) -> usize { if inst.immutable_push().is_some() { 33 } else if inst.deferred_push().is_some() { 3 } else if inst.is_encoded_push() { match &inst.value { - Some(PushValue::Immediate(value)) => push_len(*value, options), + Some(PushValue::Immediate(value)) => push_len(gcx, *value), Some(PushValue::Block(_)) => 3, _ => 1, } @@ -229,11 +223,7 @@ fn estimated_instruction_size(inst: &Instruction, options: super::PassOptions) - } } -fn estimated_terminator_size( - kind: &TerminatorKind, - next: Option, - _options: super::PassOptions, -) -> usize { +fn estimated_terminator_size(kind: &TerminatorKind, next: Option) -> usize { match kind { TerminatorKind::Jump(target) => usize::from(Some(*target) != next) * 4, TerminatorKind::Op(op::STOP) => usize::from(next.is_some()), @@ -250,9 +240,9 @@ fn estimated_terminator_size( } } -fn push_len(value: U256, options: super::PassOptions) -> usize { +fn push_len(gcx: Gcx<'_>, value: U256) -> usize { let width = value.byte_len(); - if width == 0 && !options.evm_version.has_push0() { 2 } else { width + 1 } + if width == 0 && !gcx.sess.opts.evm_version.has_push0() { 2 } else { width + 1 } } fn is_terminal_block(block: &Block) -> bool { diff --git a/crates/codegen/src/backend/evm/ir/passes/cfg_simplify.rs b/crates/codegen/src/backend/evm/ir/passes/cfg_simplify.rs index 8bb3a2238..00998ef47 100644 --- a/crates/codegen/src/backend/evm/ir/passes/cfg_simplify.rs +++ b/crates/codegen/src/backend/evm/ir/passes/cfg_simplify.rs @@ -6,8 +6,9 @@ use crate::backend::evm::{ op, }; use solar_data_structures::bit_set::DenseBitSet; +use solar_sema::Gcx; -pub(super) fn run(module: &mut Module, _options: super::PassOptions) -> bool { +pub(super) fn run(_gcx: Gcx<'_>, module: &mut Module) -> bool { let mut state = RunState::default(); state.reserve(module.blocks.len()); let mut changed = false; diff --git a/crates/codegen/src/backend/evm/ir/passes/compact_pushes.rs b/crates/codegen/src/backend/evm/ir/passes/compact_pushes.rs index 2e829c1ec..76436c896 100644 --- a/crates/codegen/src/backend/evm/ir/passes/compact_pushes.rs +++ b/crates/codegen/src/backend/evm/ir/passes/compact_pushes.rs @@ -1,23 +1,22 @@ //! Target-dependent selection of compact immediate materializations. -use super::PassOptions; use crate::backend::evm::{ ir::{Instruction, Module, PushValue}, op, }; use alloy_primitives::U256; +use solar_sema::Gcx; const EVM_WORD_BYTES: usize = 32; const EVM_WORD_BITS: usize = EVM_WORD_BYTES * 8; const MIN_COMPACT_MASK_WIDTH: u8 = 5; -pub(super) fn run(module: &mut Module, options: PassOptions) -> bool { +pub(super) fn run(gcx: Gcx<'_>, module: &mut Module) -> bool { let mut changed = false; let mut scratch = Vec::new(); for block in &mut module.blocks { if !block.instructions.iter().any(|inst| { - immediate(inst) - .is_some_and(|value| !matches!(select(value, options), CompactPush::Literal)) + immediate(inst).is_some_and(|value| !matches!(select(gcx, value), CompactPush::Literal)) }) { continue; } @@ -29,7 +28,7 @@ pub(super) fn run(module: &mut Module, options: PassOptions) -> bool { block.instructions.push(inst); continue; }; - match select(value, options) { + match select(gcx, value) { CompactPush::Literal => block.instructions.push(inst), CompactPush::FullWord => { block.instructions.push(push(U256::ZERO)); @@ -75,9 +74,9 @@ fn push(value: U256) -> Instruction { Instruction::push_value(value) } -fn select(value: U256, options: PassOptions) -> CompactPush { - let width = push_width(value, options); - let normal_len = fixed_push_len(width, options); +fn select(gcx: Gcx<'_>, value: U256) -> CompactPush { + let width = push_width(gcx, value); + let normal_len = fixed_push_len(gcx, width); let mut best = (normal_len, CompactPush::Literal); let mut consider = |len, compact| { if len < best.0 { @@ -86,7 +85,7 @@ fn select(value: U256, options: PassOptions) -> CompactPush { }; if value == U256::MAX { - consider(zero_push_len(options) + 1, CompactPush::FullWord); + consider(zero_push_len(gcx) + 1, CompactPush::FullWord); } if width >= MIN_COMPACT_MASK_WIDTH { @@ -94,16 +93,13 @@ fn select(value: U256, options: PassOptions) -> CompactPush { let start = EVM_WORD_BYTES - width as usize; if bytes[start..].iter().all(|&byte| byte == 0xff) { let shift = EVM_WORD_BITS - usize::from(width) * 8; - consider( - zero_push_len(options) + 4, - CompactPush::LowerAllOnesMask { shift: shift as u8 }, - ); + consider(zero_push_len(gcx) + 4, CompactPush::LowerAllOnesMask { shift: shift as u8 }); } } if width as usize == EVM_WORD_BYTES { let inverted = !value; - consider(fixed_push_len(push_width(inverted, options), options) + 1, CompactPush::Not); + consider(fixed_push_len(gcx, push_width(gcx, inverted)) + 1, CompactPush::Not); } let trailing_zero_bytes = value.trailing_zeros() / 8; @@ -111,7 +107,7 @@ fn select(value: U256, options: PassOptions) -> CompactPush { let shift = trailing_zero_bytes * 8; let shifted = value >> shift; consider( - fixed_push_len(push_width(shifted, options), options) + 3, + fixed_push_len(gcx, push_width(gcx, shifted)) + 3, CompactPush::Shl { shift: shift as u8 }, ); } @@ -119,16 +115,20 @@ fn select(value: U256, options: PassOptions) -> CompactPush { best.1 } -fn fixed_push_len(width: u8, options: PassOptions) -> usize { - if width == 0 { zero_push_len(options) } else { 1 + width as usize } +fn fixed_push_len(gcx: Gcx<'_>, width: u8) -> usize { + if width == 0 { zero_push_len(gcx) } else { 1 + width as usize } } -fn zero_push_len(options: PassOptions) -> usize { - if options.evm_version.has_push0() { 1 } else { 2 } +fn zero_push_len(gcx: Gcx<'_>) -> usize { + if gcx.sess.opts.evm_version.has_push0() { 1 } else { 2 } } -fn push_width(value: U256, options: PassOptions) -> u8 { - if value.is_zero() && !options.evm_version.has_push0() { 1 } else { value.byte_len() as u8 } +fn push_width(gcx: Gcx<'_>, value: U256) -> u8 { + if value.is_zero() && !gcx.sess.opts.evm_version.has_push0() { + 1 + } else { + value.byte_len() as u8 + } } #[derive(Clone, Copy)] diff --git a/crates/codegen/src/backend/evm/ir/passes/mod.rs b/crates/codegen/src/backend/evm/ir/passes/mod.rs index 3bcbf6978..6b172b7e5 100644 --- a/crates/codegen/src/backend/evm/ir/passes/mod.rs +++ b/crates/codegen/src/backend/evm/ir/passes/mod.rs @@ -16,9 +16,9 @@ pub(super) mod utils; use super::Module; use crate::timing::PassTimer; -use solar_config::{EvmVersion, OptimizationMode}; +use solar_sema::Gcx; -type PassRunner = fn(&mut Module, PassOptions) -> bool; +type PassRunner = fn(Gcx<'_>, &mut Module) -> bool; /// Registry entry for an EVM IR transform pass. #[derive(Clone, Copy, Debug)] @@ -78,17 +78,6 @@ declare_passes! { pub(crate) const BLOCK_LAYOUT_PASS -> "block-layout" = block_layout::run; } -/// Options for running an EVM IR pass. -#[derive(Clone, Copy, Debug, Default)] -pub struct PassOptions { - /// Print the time spent in the pass. - pub time_passes: bool, - /// EVM version used for target-dependent instruction sizing. - pub evm_version: EvmVersion, - /// Optimization mode used for profitability decisions. - pub optimization: OptimizationMode, -} - /// All EVM IR passes exposed by `solar evm-opt`. pub const PASS_REGISTRY: &[PassInfo] = &[ PEEPHOLE_PASS, @@ -142,9 +131,9 @@ pub fn lookup_pass(name: &str) -> Option<&'static PassInfo> { skip_all, fields(pass = pass.name), )] -pub fn run_pass(module: &mut Module, pass: &PassInfo, options: PassOptions) -> bool { - let timer = PassTimer::new(options.time_passes); - let changed = (pass.run_pass)(module, options); +pub fn run_pass(gcx: Gcx<'_>, module: &mut Module, pass: &PassInfo) -> bool { + let timer = PassTimer::new(gcx.sess.opts.unstable.time_passes); + let changed = (pass.run_pass)(gcx, module); timer.finish("EVM IR", module.name(), pass.name, changed); changed } diff --git a/crates/codegen/src/backend/evm/ir/passes/outline.rs b/crates/codegen/src/backend/evm/ir/passes/outline.rs index 049a478eb..8955344b4 100644 --- a/crates/codegen/src/backend/evm/ir/passes/outline.rs +++ b/crates/codegen/src/backend/evm/ir/passes/outline.rs @@ -7,14 +7,15 @@ use crate::backend::evm::{ use alloy_primitives::U256; use smallvec::SmallVec; use solar_data_structures::{bit_set::DenseBitSet, map::FxHashMap}; +use solar_sema::Gcx; use std::hash::{Hash, Hasher}; const MIN_CLOSED_RUN: usize = 4; -pub(super) fn run(module: &mut Module, options: super::PassOptions) -> bool { +pub(super) fn run(gcx: Gcx<'_>, module: &mut Module) -> bool { let mut state = RunState::default(); outline_closed_computations(module, &mut state) - | outline_repeated_pushes(module, options, &mut state) + | outline_repeated_pushes(gcx, module, &mut state) } fn outline_closed_computations(module: &mut Module, state: &mut RunState) -> bool { @@ -103,11 +104,7 @@ fn outline_closed_computations(module: &mut Module, state: &mut RunState) -> boo true } -fn outline_repeated_pushes( - module: &mut Module, - options: super::PassOptions, - state: &mut RunState, -) -> bool { +fn outline_repeated_pushes(gcx: Gcx<'_>, module: &mut Module, state: &mut RunState) -> bool { let mut sites = FxHashMap::>::default(); for (block_id, block) in module.blocks.iter_enumerated() { for (index, inst) in block.instructions.iter().enumerate() { @@ -126,7 +123,7 @@ fn outline_repeated_pushes( let mut values: Vec<_> = sites .iter() .filter_map(|(&value, occurrences)| { - let push_len = push_len(value, options); + let push_len = push_len(gcx, value); let inline = occurrences.len() * push_len; let outlined = occurrences.len() * SITE_BYTES + push_len + 3; (occurrences.len() >= 2 && inline >= outlined + MIN_SAVING).then_some(value) @@ -218,9 +215,9 @@ fn lower_bound(instructions: &[Instruction]) -> usize { instructions.iter().map(|inst| if inst.is_encoded_push() { 2 } else { 1 }).sum() } -fn push_len(value: U256, options: super::PassOptions) -> usize { +fn push_len(gcx: Gcx<'_>, value: U256) -> usize { let width = value.byte_len(); - if width == 0 && !options.evm_version.has_push0() { 2 } else { width + 1 } + if width == 0 && !gcx.sess.opts.evm_version.has_push0() { 2 } else { width + 1 } } #[derive(Clone, Copy, Debug)] diff --git a/crates/codegen/src/backend/evm/ir/passes/peephole.rs b/crates/codegen/src/backend/evm/ir/passes/peephole.rs index 5a51a6a21..e9d70fe03 100644 --- a/crates/codegen/src/backend/evm/ir/passes/peephole.rs +++ b/crates/codegen/src/backend/evm/ir/passes/peephole.rs @@ -5,12 +5,13 @@ use crate::backend::evm::{ op, }; use alloy_primitives::U256; +use solar_sema::Gcx; use std::fmt; use tracing::trace; const TRACE_TARGET: &str = "solar::codegen::evm_ir::peephole"; -pub(super) fn run(module: &mut Module, _options: super::PassOptions) -> bool { +pub(super) fn run(_gcx: Gcx<'_>, module: &mut Module) -> bool { let mut changed = false; let mut scratch = Vec::new(); for block in &mut module.blocks { diff --git a/crates/codegen/src/backend/evm/ir/passes/share_reverts.rs b/crates/codegen/src/backend/evm/ir/passes/share_reverts.rs index 0fb27f26d..a7ce7e954 100644 --- a/crates/codegen/src/backend/evm/ir/passes/share_reverts.rs +++ b/crates/codegen/src/backend/evm/ir/passes/share_reverts.rs @@ -6,8 +6,9 @@ use crate::backend::evm::{ }; use alloy_primitives::U256; use solar_data_structures::bit_set::DenseBitSet; +use solar_sema::Gcx; -pub(super) fn run(module: &mut Module, _options: super::PassOptions) -> bool { +pub(super) fn run(_gcx: Gcx<'_>, module: &mut Module) -> bool { let mut empty_reverts = DenseBitSet::new_empty(module.blocks.len()); for block in module.blocks.indices().filter(|&block| is_empty_revert(module, block)) { empty_reverts.insert(block); diff --git a/crates/codegen/src/backend/evm/ir/passes/tail_merge.rs b/crates/codegen/src/backend/evm/ir/passes/tail_merge.rs index 32835a6b9..fbcdf7d54 100644 --- a/crates/codegen/src/backend/evm/ir/passes/tail_merge.rs +++ b/crates/codegen/src/backend/evm/ir/passes/tail_merge.rs @@ -5,8 +5,9 @@ use crate::backend::evm::ir::{ Block, BlockId, Hotness, Instruction, Module, Terminator, TerminatorKind, }; use solar_data_structures::map::FxHashMap; +use solar_sema::Gcx; -pub(super) fn run(module: &mut Module, _options: super::PassOptions) -> bool { +pub(super) fn run(_gcx: Gcx<'_>, module: &mut Module) -> bool { let mut state = RunState::default(); state.plan_merges(module); if state.merges.is_empty() { diff --git a/crates/codegen/src/backend/evm/ir/passes/terminal_dedup.rs b/crates/codegen/src/backend/evm/ir/passes/terminal_dedup.rs index 2e71d5f8a..11c7fc49b 100644 --- a/crates/codegen/src/backend/evm/ir/passes/terminal_dedup.rs +++ b/crates/codegen/src/backend/evm/ir/passes/terminal_dedup.rs @@ -8,6 +8,7 @@ use super::utils::is_evm_terminal; use crate::backend::evm::ir::{Block, BlockId, Module, PushValue, Terminator, TerminatorKind}; use solar_data_structures::map::{FxHashMap, StdEntry}; +use solar_sema::Gcx; #[derive(Default)] struct RunState { @@ -15,7 +16,7 @@ struct RunState { redirects: Vec<(BlockId, BlockId)>, } -pub(super) fn run(module: &mut Module, _options: super::PassOptions) -> bool { +pub(super) fn run(_gcx: Gcx<'_>, module: &mut Module) -> bool { let mut state = RunState::default(); for block_id in module.blocks.indices() { diff --git a/crates/codegen/src/backend/evm/mod.rs b/crates/codegen/src/backend/evm/mod.rs index 9fabdf846..d40346bd2 100644 --- a/crates/codegen/src/backend/evm/mod.rs +++ b/crates/codegen/src/backend/evm/mod.rs @@ -8,7 +8,7 @@ //! - `stack`: MIR-to-EVM stack scheduling for DUP/SWAP generation mod codegen; -pub use codegen::{EvmArtifact, EvmCodegen, EvmCodegenConfig}; +pub use codegen::{EvmArtifact, EvmCodegen}; pub mod ir; diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index 6f21e6694..852b15235 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -27,10 +27,7 @@ pub mod mir; mod analysis; pub mod backend; -pub use backend::{ - Backend, - evm::{EvmCodegen, EvmCodegenConfig}, -}; +pub use backend::{Backend, evm::EvmCodegen}; mod ir_parse; pub mod lower; diff --git a/crates/codegen/src/mir/module.rs b/crates/codegen/src/mir/module.rs index c47324815..62112c69c 100644 --- a/crates/codegen/src/mir/module.rs +++ b/crates/codegen/src/mir/module.rs @@ -93,9 +93,6 @@ pub struct Module { immutable_data_len: usize, /// Whether this is an interface (no bytecode generation). pub(crate) is_interface: bool, - /// Whether optimization passes should favor bytecode size over runtime - /// gas (`-O size`): multi-use functions are called rather than inlined. - pub(crate) optimize_for_size: bool, /// The lowering phase this module is in. pub(crate) phase: MirPhase, } @@ -117,7 +114,6 @@ impl Module { functions: IndexVec::new(), immutable_data_len: 0, is_interface: false, - optimize_for_size: false, phase: MirPhase::Built, } } diff --git a/crates/codegen/src/pass.rs b/crates/codegen/src/pass.rs index c57ed03d7..9ac41bef4 100644 --- a/crates/codegen/src/pass.rs +++ b/crates/codegen/src/pass.rs @@ -13,7 +13,7 @@ //! let mut am = AnalysisManager::new(); //! let liveness = am.get_or_compute(&LivenessAnalysis, &func); //! -//! let changed = run_pass(&mut module, &DCE_PASS, PipelineOptions::default()); +//! let changed = run_pass(gcx, &mut module, &DCE_PASS); //! ``` use crate::{ @@ -30,9 +30,10 @@ use crate::{ }; use solar_data_structures::map::FxHashMap; use solar_interface::diagnostics::DiagCtxt; +use solar_sema::Gcx; use std::any::{Any, TypeId}; -type PassRunner = fn(&mut Module) -> bool; +type PassRunner = fn(Gcx<'_>, &mut Module) -> bool; /// Registry entry for a MIR transform pass. #[derive(Clone, Copy, Debug)] @@ -84,7 +85,7 @@ macro_rules! declare_passes { $vis const $const_name: PassInfo = PassInfo::new( $name, concat!($($description, "\n"),+).trim_ascii(), - |module| ModulePass::run(&mut $pass, module), + |gcx, module| ModulePass::run(&mut $pass, gcx, module), ); )+ }; @@ -285,27 +286,6 @@ pub const DEFAULT_CLEANUP_PIPELINE: &[PassInfo] = &[ const DEFAULT_CLEANUP_MAX_ROUNDS: usize = 3; -/// Options for running a MIR pass pipeline. -#[derive(Clone, Copy, Debug)] -pub struct PipelineOptions { - /// Print the full module after every pass in the pipeline. - pub print_after_each: bool, - /// Print the time spent in each pass. - pub time_passes: bool, - /// Validate MIR after every pass. - pub(crate) validate_after_each: bool, -} - -impl Default for PipelineOptions { - fn default() -> Self { - Self { - print_after_each: false, - time_passes: false, - validate_after_each: cfg!(debug_assertions), - } - } -} - /// Runs a named MIR pass over a module. #[tracing::instrument( name = "mir_pass", @@ -313,30 +293,30 @@ impl Default for PipelineOptions { skip_all, fields(module = %module.name, pass = pass.name), )] -pub fn run_pass(module: &mut Module, pass: &PassInfo, options: PipelineOptions) -> bool { +pub fn run_pass(gcx: solar_sema::Gcx<'_>, module: &mut Module, pass: &PassInfo) -> bool { // Passes declare which phases they operate on; the manager enforces it so a // pipeline entry cannot silently corrupt a module in the wrong phase. if !pass.admits(module) { return false; } - if options.validate_after_each { + if cfg!(debug_assertions) { validate_module_after_pass(module, "input"); } - let timer = PassTimer::new(options.time_passes); - let changed = (pass.run_pass)(module); + let timer = PassTimer::new(gcx.sess.opts.unstable.time_passes); + let changed = (pass.run_pass)(gcx, module); timer.finish("MIR", module.name, pass.name, changed); - if options.validate_after_each { + if cfg!(debug_assertions) { validate_module_after_pass(module, pass.name); } changed } /// Runs a named MIR pass pipeline over a module. -fn run_pipeline(module: &mut Module, passes: &[PassInfo], options: PipelineOptions) -> bool { +fn run_pipeline(gcx: solar_sema::Gcx<'_>, module: &mut Module, passes: &[PassInfo]) -> bool { let mut changed = false; for pass in passes { - changed |= run_pass(module, pass, options); - if options.print_after_each { + changed |= run_pass(gcx, module, pass); + if gcx.sess.opts.unstable.print_after_each { println!("// === {} (after {}) ===", module.name, pass.name); print!("{}", module.to_text()); } @@ -355,27 +335,26 @@ fn run_pipeline(module: &mut Module, passes: &[PassInfo], options: PipelineOptio skip_all, fields(module = %module.name), )] -pub fn run_default_pipeline(module: &mut Module, options: PipelineOptions) -> bool { - let mut changed = run_pipeline(module, DEFAULT_PIPELINE, options); - changed |= - run_cleanup_pipeline_to_fixpoint(module, DEFAULT_CLEANUP_PIPELINE, options, "cleanup"); +pub fn run_default_pipeline(gcx: solar_sema::Gcx<'_>, module: &mut Module) -> bool { + let mut changed = run_pipeline(gcx, module, DEFAULT_PIPELINE); + changed |= run_cleanup_pipeline_to_fixpoint(gcx, module, DEFAULT_CLEANUP_PIPELINE, "cleanup"); module.advance_phase(crate::mir::MirPhase::Optimized); changed } fn run_cleanup_pipeline_to_fixpoint( + gcx: solar_sema::Gcx<'_>, module: &mut Module, passes: &[PassInfo], - options: PipelineOptions, label: &str, ) -> bool { let mut changed = false; for round in 1..=DEFAULT_CLEANUP_MAX_ROUNDS { let mut round_changed = false; for pass in passes { - let pass_changed = run_pass(module, pass, options); + let pass_changed = run_pass(gcx, module, pass); round_changed |= pass_changed; - if options.print_after_each { + if gcx.sess.opts.unstable.print_after_each { println!("// === {} (after {label}-{round}:{}) ===", module.name, pass.name); print!("{}", module.to_text()); } @@ -419,7 +398,7 @@ pub(crate) trait ModulePass { /// Runs the transformation on the given module. /// /// Returns true if the transform changed MIR. - fn run(&mut self, module: &mut Module) -> bool; + fn run(&mut self, gcx: Gcx<'_>, module: &mut Module) -> bool; } /// A transformation pass that mutates one function at a time. @@ -429,7 +408,7 @@ pub(crate) trait FunctionPass { } impl ModulePass for T { - fn run(&mut self, module: &mut Module) -> bool { + fn run(&mut self, _gcx: Gcx<'_>, module: &mut Module) -> bool { let mut changed = false; for func in module.functions.iter_mut().filter(|func| !func.blocks.is_empty()) { changed |= self.run_on_function(func); diff --git a/crates/codegen/src/transform/cfg_simplify.rs b/crates/codegen/src/transform/cfg_simplify.rs index 43042ec11..b16a5cff6 100644 --- a/crates/codegen/src/transform/cfg_simplify.rs +++ b/crates/codegen/src/transform/cfg_simplify.rs @@ -659,7 +659,7 @@ pub(crate) struct DeadFunctionEliminator { pub(crate) struct FunctionDcePass; impl ModulePass for FunctionDcePass { - fn run(&mut self, module: &mut Module) -> bool { + fn run(&mut self, _gcx: solar_sema::Gcx<'_>, module: &mut Module) -> bool { DeadFunctionEliminator::new().run(module) != 0 } } diff --git a/crates/codegen/src/transform/inline.rs b/crates/codegen/src/transform/inline.rs index a17648040..77dd105b0 100644 --- a/crates/codegen/src/transform/inline.rs +++ b/crates/codegen/src/transform/inline.rs @@ -15,36 +15,40 @@ use crate::{ use alloy_primitives::U256; use smallvec::SmallVec; use solar_data_structures::{bit_set::DenseBitSet, map::FxHashMap}; +use solar_sema::Gcx; -/// Configuration for MIR-level internal-call inlining. -#[derive(Clone, Debug)] -pub(crate) struct MirInlineConfig { +/// Module-level MIR internal-call inliner. +/// +/// This pass clones small internal/private callees into their callers. Each +/// inline expansion gets a fresh internal-frame range so copied local slots do +/// not overlap caller locals. +pub(crate) struct MirInliner { /// Maximum instruction count for ordinary inline candidates. - pub max_instructions: usize, + max_instructions: usize, /// Hard sanity limit for single-call-site callees. These bypass the normal /// size and block caps because function DCE removes their original body. - pub max_single_call_sanity_instructions: usize, + max_single_call_sanity_instructions: usize, /// Maximum number of blocks to clone from one callee. - pub max_blocks: usize, + max_blocks: usize, /// Whether a single call site may use the larger threshold. - pub inline_single_call: bool, + inline_single_call: bool, /// Maximum estimated runtime bytecode growth for a cold call site. - pub max_cold_code_growth: usize, + max_cold_code_growth: usize, /// Maximum estimated runtime bytecode growth for a call site inside a loop. - pub max_hot_code_growth: usize, + max_hot_code_growth: usize, /// Maximum number of instructions a single caller may gain from inlining /// multi-use callees, bounding total code growth per function. - pub max_caller_inlined_instructions: usize, + max_caller_inlined_instructions: usize, /// Minimum estimated internal-call protocol gas saved before inlining. - pub min_call_savings: u64, + min_call_savings: u64, /// Size-aware backstop: once a module's estimated runtime bytecode reaches /// this many bytes, stop inlining (which grows code) so the contract stays /// under the EIP-170 deployable-code limit. Small contracts never reach it /// and inline normally. - pub max_module_code_size: usize, + max_module_code_size: usize, } -impl Default for MirInlineConfig { +impl Default for MirInliner { fn default() -> Self { Self { max_instructions: 96, @@ -66,12 +70,12 @@ impl Default for MirInlineConfig { } } -impl MirInlineConfig { - /// Configuration for `-O size`: a module budget of zero disables all MIR +impl MirInliner { + /// Creates the `-O size` inliner: a module budget of zero disables all MIR /// inlining, which only ever grows emitted code on real contracts (both /// multi-use duplication and the cascades that single-call inlining sets /// off were measured to increase size). Lowering-time inlining of tiny - /// single-return helpers is deliberately kept: solar's internal-call + /// single-return helpers is deliberately kept: the compiler's internal-call /// protocol (memory frame setup) costs more bytes than those bodies, so /// sharing them was measured to *increase* code size as well. #[must_use] @@ -112,42 +116,21 @@ struct MirInlineSummary { no_inline: bool, } -/// Module-level MIR internal-call inliner. -/// -/// This pass clones small internal/private callees into their callers. Each -/// inline expansion gets a fresh internal-frame range so copied local slots do -/// not overlap caller locals. -pub(crate) struct MirInliner { - config: MirInlineConfig, -} - /// Module pass for metadata-backed MIR inlining. pub(crate) struct InlinePass; impl ModulePass for InlinePass { - fn run(&mut self, module: &mut Module) -> bool { - let config = if module.optimize_for_size { - MirInlineConfig::for_size() + fn run(&mut self, gcx: Gcx<'_>, module: &mut Module) -> bool { + let mut inliner = if gcx.sess.opts.optimization == solar_config::OptimizationMode::Size { + MirInliner::for_size() } else { - MirInlineConfig::default() + MirInliner::default() }; - MirInliner::new(config).run(module).inlined != 0 - } -} - -impl Default for MirInliner { - fn default() -> Self { - Self::new(MirInlineConfig::default()) + inliner.run(module).inlined != 0 } } impl MirInliner { - /// Creates a new MIR inliner with the given configuration. - #[must_use] - pub(crate) fn new(config: MirInlineConfig) -> Self { - Self { config } - } - /// Runs the inliner over the whole module. pub(crate) fn run(&mut self, module: &mut Module) -> MirInlineStats { let mut stats = MirInlineStats::default(); @@ -182,9 +165,9 @@ impl MirInliner { let call_count = call_counts.get(&site.callee).copied().unwrap_or_default(); let grew_too_much = summaries.get(&caller_id).is_some_and(|s| { s.instruction_count.saturating_sub(base_instructions) - > self.config.max_caller_inlined_instructions + > self.max_caller_inlined_instructions }); - if module_code_size >= self.config.max_module_code_size + if module_code_size >= self.max_module_code_size || grew_too_much || recursive_functions.contains(site.callee) || !self.is_inlineable(caller_id, site, summary, call_count) @@ -315,7 +298,7 @@ impl MirInliner { summary: MirInlineSummary, call_count: usize, ) -> bool { - let single_call = self.config.inline_single_call && call_count == 1; + let single_call = self.inline_single_call && call_count == 1; // `no_inline` prevents cloning a shared helper into every caller; with // a single call site there is nothing to duplicate, and absorbing the @@ -332,11 +315,11 @@ impl MirInliner { } if single_call { - if summary.instruction_count > self.config.max_single_call_sanity_instructions { + if summary.instruction_count > self.max_single_call_sanity_instructions { return false; } - } else if summary.block_count > self.config.max_blocks - || summary.instruction_count > self.config.max_instructions + } else if summary.block_count > self.max_blocks + || summary.instruction_count > self.max_instructions { return false; } @@ -357,17 +340,14 @@ impl MirInliner { } let code_growth = estimated_inline_code_growth(summary, site, single_call); - let max_growth = if site.loop_depth > 0 { - self.config.max_hot_code_growth - } else { - self.config.max_cold_code_growth - }; + let max_growth = + if site.loop_depth > 0 { self.max_hot_code_growth } else { self.max_cold_code_growth }; if code_growth > max_growth { return false; } let savings = estimated_internal_call_savings(site, summary); - savings >= self.config.min_call_savings + savings >= self.min_call_savings } } diff --git a/crates/codegen/src/transform/loop_opt.rs b/crates/codegen/src/transform/loop_opt.rs index cc215be81..fee9a7368 100644 --- a/crates/codegen/src/transform/loop_opt.rs +++ b/crates/codegen/src/transform/loop_opt.rs @@ -42,20 +42,23 @@ struct LoopOptContext<'a> { analyzer: &'a LoopAnalyzer, } -/// Loop optimization pass configuration. -#[derive(Clone, Debug)] -pub(crate) struct LoopOptConfig { - /// Enable Loop Invariant Code Motion. - pub enable_licm: bool, +/// Loop optimizer. +#[derive(Debug)] +pub(crate) struct LoopOptimizer { /// Minimum estimated gas saved per iteration before an instruction is considered a LICM root. - pub min_licm_profit: u16, + min_licm_profit: u16, /// Maximum number of instructions hoisted from one loop. - pub max_licm_hoisted_insts: usize, + max_licm_hoisted_insts: usize, + stats: LoopOptStats, } -impl Default for LoopOptConfig { +impl Default for LoopOptimizer { fn default() -> Self { - Self { enable_licm: true, min_licm_profit: 0, max_licm_hoisted_insts: usize::MAX } + Self { + min_licm_profit: 0, + max_licm_hoisted_insts: usize::MAX, + stats: LoopOptStats::default(), + } } } @@ -66,37 +69,21 @@ pub(crate) struct LoopOptStats { pub instructions_hoisted: usize, } -/// Loop optimizer. -#[derive(Debug)] -pub(crate) struct LoopOptimizer { - config: LoopOptConfig, - stats: LoopOptStats, -} - /// Function pass for loop-invariant code motion. pub(crate) struct LicmPass; impl FunctionPass for LicmPass { fn run_on_function(&mut self, func: &mut Function) -> bool { - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 3, max_licm_hoisted_insts: 8 }; - LoopOptimizer::new(config).optimize(func).instructions_hoisted != 0 - } -} - -impl Default for LoopOptimizer { - fn default() -> Self { - Self::new(LoopOptConfig::default()) + LoopOptimizer::with_limits(3, 8).optimize(func).instructions_hoisted != 0 } } impl LoopOptimizer { - /// Creates a new loop optimizer with the given configuration. - pub(crate) fn new(config: LoopOptConfig) -> Self { - Self { config, stats: LoopOptStats::default() } + fn with_limits(min_licm_profit: u16, max_licm_hoisted_insts: usize) -> Self { + Self { min_licm_profit, max_licm_hoisted_insts, stats: LoopOptStats::default() } } - /// Runs all enabled loop optimizations on a function. + /// Runs loop-invariant code motion on a function. pub(crate) fn optimize(&mut self, func: &mut Function) -> &LoopOptStats { self.stats = LoopOptStats::default(); func.annotate_storage_aliases(mir_utils::StorageAliasScope::StorageAndTransient); @@ -111,9 +98,7 @@ impl LoopOptimizer { let loop_headers: Vec = loop_info.loops.keys().copied().collect(); for header in loop_headers { - if let Some(loop_data) = loop_info.loops.get(&header) - && self.config.enable_licm - { + if let Some(loop_data) = loop_info.loops.get(&header) { self.apply_licm(func, loop_data, &analyzer); } } @@ -155,7 +140,7 @@ impl LoopOptimizer { } let new_count = closure.iter().filter(|&&inst_id| !selected.contains(inst_id)).count(); - if selected.count() + new_count > self.config.max_licm_hoisted_insts { + if selected.count() + new_count > self.max_licm_hoisted_insts { continue; } for &inst_id in &closure { @@ -423,7 +408,7 @@ impl LoopOptimizer { inst_id: InstId, ctx: LoopOptContext<'_>, ) -> bool { - self.licm_profit(func, inst_id) >= self.config.min_licm_profit + self.licm_profit(func, inst_id) >= self.min_licm_profit || (self.loop_has_known_multiple_iterations(ctx.loop_data) && self.is_affine_address_base_used_in_loop(func, inst_id, ctx)) || (self.inst_dominates_loop_backedges(func, inst_id, ctx.loop_data, ctx.analyzer) @@ -860,9 +845,7 @@ mod tests { let mul_inst = *mul_inst; assert!(func.blocks[body].instructions.contains(&mul_inst)); - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 5, max_licm_hoisted_insts: 4 }; - let mut optimizer = LoopOptimizer::new(config); + let mut optimizer = LoopOptimizer::with_limits(5, 4); optimizer.optimize(&mut func); assert!(func.blocks[entry].instructions.contains(&mul_inst)); @@ -909,9 +892,7 @@ mod tests { let load_inst = *load_inst; assert!(func.blocks[body].instructions.contains(&load_inst)); - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 3, max_licm_hoisted_insts: 4 }; - let mut optimizer = LoopOptimizer::new(config); + let mut optimizer = LoopOptimizer::with_limits(3, 4); optimizer.optimize(&mut func); assert!(func.blocks[entry].instructions.contains(&load_inst)); @@ -954,9 +935,7 @@ mod tests { }; let load_inst = *load_inst; - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 3, max_licm_hoisted_insts: 4 }; - let mut optimizer = LoopOptimizer::new(config); + let mut optimizer = LoopOptimizer::with_limits(3, 4); optimizer.optimize(&mut func); assert!(func.blocks[body].instructions.contains(&load_inst)); @@ -1001,9 +980,7 @@ mod tests { }; let hash_inst = *hash_inst; - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 5, max_licm_hoisted_insts: 4 }; - let mut optimizer = LoopOptimizer::new(config); + let mut optimizer = LoopOptimizer::with_limits(5, 4); optimizer.optimize(&mut func); assert!(func.blocks[entry].instructions.contains(&hash_inst)); @@ -1047,9 +1024,7 @@ mod tests { }; let hash_inst = *hash_inst; - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 5, max_licm_hoisted_insts: 4 }; - let mut optimizer = LoopOptimizer::new(config); + let mut optimizer = LoopOptimizer::with_limits(5, 4); optimizer.optimize(&mut func); assert!(func.blocks[body].instructions.contains(&hash_inst)); @@ -1093,9 +1068,7 @@ mod tests { }; let load_inst = *load_inst; - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 5, max_licm_hoisted_insts: 4 }; - let mut optimizer = LoopOptimizer::new(config); + let mut optimizer = LoopOptimizer::with_limits(5, 4); optimizer.optimize(&mut func); assert!(func.blocks[entry].instructions.contains(&load_inst)); @@ -1137,9 +1110,7 @@ mod tests { }; let load_inst = *load_inst; - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 5, max_licm_hoisted_insts: 4 }; - let mut optimizer = LoopOptimizer::new(config); + let mut optimizer = LoopOptimizer::with_limits(5, 4); optimizer.optimize(&mut func); assert!(func.blocks[body].instructions.contains(&load_inst)); @@ -1180,9 +1151,7 @@ mod tests { }; let mul_inst = *mul_inst; - let config = - LoopOptConfig { enable_licm: true, min_licm_profit: 5, max_licm_hoisted_insts: 4 }; - let mut optimizer = LoopOptimizer::new(config); + let mut optimizer = LoopOptimizer::with_limits(5, 4); optimizer.optimize(&mut func); assert!(func.blocks[body].instructions.contains(&mul_inst)); diff --git a/crates/codegen/src/transform/lower_abi.rs b/crates/codegen/src/transform/lower_abi.rs index 2dbca7cfd..1aa4f3028 100644 --- a/crates/codegen/src/transform/lower_abi.rs +++ b/crates/codegen/src/transform/lower_abi.rs @@ -215,7 +215,7 @@ impl LowerAbiPass { } impl ModulePass for LowerAbiPass { - fn run(&mut self, module: &mut Module) -> bool { + fn run(&mut self, _gcx: solar_sema::Gcx<'_>, module: &mut Module) -> bool { Self::run(self, module) } } diff --git a/crates/codegen/src/transform/lower_dispatch.rs b/crates/codegen/src/transform/lower_dispatch.rs index a6ee218aa..ceec054b4 100644 --- a/crates/codegen/src/transform/lower_dispatch.rs +++ b/crates/codegen/src/transform/lower_dispatch.rs @@ -241,7 +241,7 @@ fn load_selector(builder: &mut FunctionBuilder<'_>) -> ValueId { } impl ModulePass for LowerDispatchPass { - fn run(&mut self, module: &mut Module) -> bool { + fn run(&mut self, _gcx: solar_sema::Gcx<'_>, module: &mut Module) -> bool { Self::run(self, module) } } diff --git a/crates/codegen/src/transform/lower_evm_shaped.rs b/crates/codegen/src/transform/lower_evm_shaped.rs index 7ca4253fd..a5ed90705 100644 --- a/crates/codegen/src/transform/lower_evm_shaped.rs +++ b/crates/codegen/src/transform/lower_evm_shaped.rs @@ -107,7 +107,7 @@ impl LowerEvmShapedPass { } impl ModulePass for LowerEvmShapedPass { - fn run(&mut self, module: &mut Module) -> bool { + fn run(&mut self, _gcx: solar_sema::Gcx<'_>, module: &mut Module) -> bool { Self::run(self, module) } } diff --git a/crates/codegen/src/transform/outline_reverts.rs b/crates/codegen/src/transform/outline_reverts.rs index 55d6c1975..78dc32ece 100644 --- a/crates/codegen/src/transform/outline_reverts.rs +++ b/crates/codegen/src/transform/outline_reverts.rs @@ -104,7 +104,7 @@ impl OutlineRevertsPass { } impl ModulePass for OutlineRevertsPass { - fn run(&mut self, module: &mut Module) -> bool { + fn run(&mut self, _gcx: solar_sema::Gcx<'_>, module: &mut Module) -> bool { Self::run(self, module) } } diff --git a/crates/codegen/src/transform/static_alloc.rs b/crates/codegen/src/transform/static_alloc.rs index 6b54cb675..5a0eb9c11 100644 --- a/crates/codegen/src/transform/static_alloc.rs +++ b/crates/codegen/src/transform/static_alloc.rs @@ -36,7 +36,7 @@ use solar_data_structures::{bit_set::DenseBitSet, map::FxHashMap}; pub(crate) struct StaticAllocPass; impl ModulePass for StaticAllocPass { - fn run(&mut self, module: &mut Module) -> bool { + fn run(&mut self, _gcx: solar_sema::Gcx<'_>, module: &mut Module) -> bool { // Every entry's locals share the same low-memory region — only one // entry runs per call — so the tallest entry's frame top is a shadow // the others can grow into without moving the shared static-frame diff --git a/crates/config/src/opts.rs b/crates/config/src/opts.rs index 9cbd0f82a..6bdac79f0 100644 --- a/crates/config/src/opts.rs +++ b/crates/config/src/opts.rs @@ -363,9 +363,9 @@ pub struct UnstableOpts { #[cfg_attr(feature = "clap", arg(long))] pub print_natspec: bool, - /// Print MIR after every MIR optimization pass during codegen. + /// Print MIR or EVM IR after every optimization pass. #[cfg_attr(feature = "clap", arg(long))] - pub mir_print_after_each: bool, + pub print_after_each: bool, /// Print a before-and-after diff for each pass explicitly selected by `mir-opt` or `evm-opt`. #[cfg_attr(feature = "clap", arg(long))] diff --git a/tests/ui/cli/Zhelp.stdout b/tests/ui/cli/Zhelp.stdout index 3f9d2122d..d73feb3f8 100644 --- a/tests/ui/cli/Zhelp.stdout +++ b/tests/ui/cli/Zhelp.stdout @@ -42,8 +42,8 @@ Options: -Zprint-natspec Print resolved NatSpec docs as diagnostics for UI tests - -Zmir-print-after-each - Print MIR after every MIR optimization pass during codegen + -Zprint-after-each + Print MIR or EVM IR after every optimization pass -Zpass-diff Print a before-and-after diff for each pass explicitly selected by `mir-opt` or `evm-opt`