diff --git a/sway-ir/src/optimize.rs b/sway-ir/src/optimize.rs index 21cb0162fc3..ba4c7c8c728 100644 --- a/sway-ir/src/optimize.rs +++ b/sway-ir/src/optimize.rs @@ -35,3 +35,89 @@ pub mod simplify_cfg; pub use simplify_cfg::*; mod target_fuel; + +#[cfg(test)] +pub mod tests { + use crate::{PassGroup, PassManager}; + use sway_types::SourceEngine; + + /// This function parses the IR text representation and run the specified optimizers passes. + /// Then, depending on the `expected` parameter it checks if the IR was optimized or not. + /// + /// This comparison is done by capturing all instructions with metadata "!0". + /// + /// For example: + /// + /// ```rust, ignore + /// assert_optimization( + /// &["constcombine"], + /// "entry fn main() -> u64 { + /// entry(): + /// l = const u64 1 + /// r = const u64 2 + /// result = add l, r, !0 + /// ret u64 result + /// }", + /// ["const u64 3"], + /// ); + /// ``` + pub(crate) fn assert_optimization<'a>( + passes: &[&'static str], + body: &str, + expected: Option>, + ) { + let source_engine = SourceEngine::default(); + let mut context = crate::parse( + &format!( + "script {{ + {body} + }} + + !0 = \"a.sw\"" + ), + &source_engine, + ) + .unwrap(); + + let mut pass_manager = PassManager::default(); + crate::register_known_passes(&mut pass_manager); + + let mut group = PassGroup::default(); + for pass in passes { + group.append_pass(pass); + } + + let modified = pass_manager.run(&mut context, &group).unwrap(); + assert_eq!(expected.is_some(), modified); + + let Some(expected) = expected else { + return; + }; + + let actual = context + .to_string() + .lines() + .filter_map(|x| { + if x.contains(", !0") { + Some(format!("{}\n", x.trim())) + } else { + None + } + }) + .collect::>(); + + assert!(!actual.is_empty()); + + let mut expected_matches = actual.len(); + + for (actual, expected) in actual.iter().zip(expected) { + if !actual.contains(expected) { + panic!("error: {actual:?} {expected:?}"); + } else { + expected_matches -= 1; + } + } + + assert_eq!(expected_matches, 0); + } +} diff --git a/sway-ir/src/optimize/constants.rs b/sway-ir/src/optimize/constants.rs index 19d6cad4584..a3e2d2f1dbf 100644 --- a/sway-ir/src/optimize/constants.rs +++ b/sway-ir/src/optimize/constants.rs @@ -43,6 +43,16 @@ pub fn combine_constants( continue; } + if combine_binary_op(context, &function) { + modified = true; + continue; + } + + if combine_unary_op(context, &function) { + modified = true; + continue; + } + // Other passes here... always continue to the top if pass returns true. break; } @@ -143,3 +153,239 @@ fn combine_cmp(context: &mut Context, function: &Function) -> bool { true }) } + +fn combine_binary_op(context: &mut Context, function: &Function) -> bool { + let candidate = function + .instruction_iter(context) + .find_map( + |(block, inst_val)| match &context.values[inst_val.0].value { + ValueDatum::Instruction(Instruction::BinaryOp { op, arg1, arg2 }) + if arg1.is_constant(context) && arg2.is_constant(context) => + { + let val1 = arg1.get_constant(context).unwrap(); + let val2 = arg2.get_constant(context).unwrap(); + let v = match op { + crate::BinaryOpKind::Add => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_add(*r), + _ => None, + }, + crate::BinaryOpKind::Sub => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_sub(*r), + _ => None, + }, + crate::BinaryOpKind::Mul => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_mul(*r), + _ => None, + }, + crate::BinaryOpKind::Div => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_div(*r), + _ => None, + }, + crate::BinaryOpKind::And => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => Some(l & r), + _ => None, + }, + crate::BinaryOpKind::Or => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => Some(l | r), + _ => None, + }, + crate::BinaryOpKind::Xor => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => Some(l ^ r), + _ => None, + }, + crate::BinaryOpKind::Mod => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => Some(l % r), + _ => None, + }, + crate::BinaryOpKind::Rsh => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => { + u32::try_from(*r).ok().and_then(|r| l.checked_shr(r)) + } + _ => None, + }, + crate::BinaryOpKind::Lsh => match (&val1.value, &val2.value) { + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => { + u32::try_from(*r).ok().and_then(|r| l.checked_shl(r)) + } + _ => None, + }, + }; + + v.map(|v| { + ( + inst_val, + block, + Constant { + ty: val1.ty, + value: ConstantValue::Uint(v), + }, + ) + }) + } + _ => None, + }, + ); + + // Replace this binary op instruction with a constant. + candidate.map_or(false, |(inst_val, block, new_value)| { + inst_val.replace(context, ValueDatum::Constant(new_value)); + block.remove_instruction(context, inst_val); + true + }) +} + +fn combine_unary_op(context: &mut Context, function: &Function) -> bool { + let candidate = function + .instruction_iter(context) + .find_map( + |(block, inst_val)| match &context.values[inst_val.0].value { + ValueDatum::Instruction(Instruction::UnaryOp { op, arg }) + if arg.is_constant(context) => + { + let val = arg.get_constant(context).unwrap(); + match op { + crate::UnaryOpKind::Not => match &val.value { + ConstantValue::Uint(v) => { + val.ty.get_uint_width(context).and_then(|width| { + let max = match width { + 8 => u8::MAX as u64, + 16 => u16::MAX as u64, + 32 => u32::MAX as u64, + 64 => u64::MAX, + _ => return None, + }; + Some(( + inst_val, + block, + Constant { + ty: val.ty, + value: ConstantValue::Uint((!v) & max), + }, + )) + }) + } + _ => None, + }, + } + } + _ => None, + }, + ); + + // Replace this unary op instruction with a constant. + candidate.map_or(false, |(inst_val, block, new_value)| { + inst_val.replace(context, ValueDatum::Constant(new_value)); + block.remove_instruction(context, inst_val); + true + }) +} + +#[cfg(test)] +mod tests { + use crate::optimize::tests::*; + + fn assert_operator(opcode: &str, l: &str, r: Option<&str>, result: Option<&str>) { + let expected = result.map(|result| format!("v0 = const u64 {result}")); + let expected = expected.as_ref().map(|x| vec![x.as_str()]); + let body = format!( + " + entry fn main() -> u64 {{ + entry(): + l = const u64 {l} + {r_inst} + result = {opcode} l, {result_inst} !0 + ret u64 result + }} +", + r_inst = r.map_or("".into(), |r| format!("r = const u64 {r}")), + result_inst = r.map_or("", |_| " r,") + ); + assert_optimization(&["constcombine"], &body, expected); + } + + #[test] + fn unary_op_are_optimized() { + assert_operator("not", &u64::MAX.to_string(), None, Some("0")); + } + + #[test] + fn binary_op_are_optimized() { + assert_operator("add", "1", Some("1"), Some("2")); + assert_operator("sub", "1", Some("1"), Some("0")); + assert_operator("mul", "2", Some("2"), Some("4")); + assert_operator("div", "10", Some("5"), Some("2")); + assert_operator("mod", "12", Some("5"), Some("2")); + assert_operator("rsh", "16", Some("1"), Some("8")); + assert_operator("lsh", "16", Some("1"), Some("32")); + + assert_operator( + "and", + &0x00FFF.to_string(), + Some(&0xFFF00.to_string()), + Some(&0xF00.to_string()), + ); + assert_operator( + "or", + &0x00FFF.to_string(), + Some(&0xFFF00.to_string()), + Some(&0xFFFFF.to_string()), + ); + + assert_operator( + "xor", + &0x00FFF.to_string(), + Some(&0xFFF00.to_string()), + Some(&0xFF0FF.to_string()), + ); + } + + #[test] + fn binary_op_are_not_optimized() { + assert_operator("add", &u64::MAX.to_string(), Some("1"), None); + assert_operator("sub", "0", Some("1"), None); + assert_operator("mul", &u64::MAX.to_string(), Some("2"), None); + assert_operator("div", "1", Some("0"), None); + + assert_operator("rsh", "1", Some("64"), None); + assert_operator("lsh", "1", Some("64"), None); + } + + #[test] + fn ok_chain_optimization() { + // Unary operator + + // `sub 1` is used to guarantee that the assert string is unique + assert_optimization( + &["constcombine"], + " + entry fn main() -> u64 { + entry(): + a = const u64 18446744073709551615 + b = not a, !0 + c = not b, !0 + d = const u64 1 + result = sub c, d, !0 + ret u64 result + } + ", + Some(["const u64 18446744073709551614"]), + ); + + // Binary Operators + assert_optimization( + &["constcombine"], + " + entry fn main() -> u64 { + entry(): + l0 = const u64 1 + r0 = const u64 2 + l1 = add l0, r0, !0 + r1 = const u64 3 + result = add l1, r1, !0 + ret u64 result + } + ", + Some(["const u64 6"]), + ); + } +} diff --git a/sway-ir/src/parser.rs b/sway-ir/src/parser.rs index 38a3ace0cf2..17071441d47 100644 --- a/sway-ir/src/parser.rs +++ b/sway-ir/src/parser.rs @@ -897,7 +897,7 @@ mod ir_builder { fn_decl: IrAstFnDecl, ) -> Result<(), IrError> { let convert_md_idx = |opt_md_idx: &Option| { - opt_md_idx.map(|mdi| self.md_map.get(&mdi).copied().unwrap()) + opt_md_idx.and_then(|mdi| self.md_map.get(&mdi).copied()) }; let args: Vec<(String, Type, Option)> = fn_decl .args diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw index 65111d9149c..12af25ab538 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw @@ -2,7 +2,7 @@ script; use basic_storage_abi::{BasicStorage, Quad}; fn main() -> u64 { - let addr = abi(BasicStorage, 0x6a550eadf838642881db8c66f40c1fc8442ee625212f3f2943850b2c12d12275); + let addr = abi(BasicStorage, 0xf2cabd05603d8f2b7eb273e2e763e60e3b88dd6405de5cb732bc7c498f13f213); let key = 0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; let value = 4242; diff --git a/test/src/ir_generation/mod.rs b/test/src/ir_generation/mod.rs index 63ef3285f67..dabd3835c70 100644 --- a/test/src/ir_generation/mod.rs +++ b/test/src/ir_generation/mod.rs @@ -1,5 +1,6 @@ use std::{ fs, + ops::Not, path::{Path, PathBuf}, sync::Arc, }; @@ -16,7 +17,151 @@ use sway_ir::{ }; use sway_utils::PerformanceData; -pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { +enum Checker { + Ir, + Asm, + OptimizedIr { passes: Vec }, +} + +impl Checker { + /// Builds and configures checkers based on file comments. Every check between checkers directive + /// are collected into the last started checker, "::check-ir::" being the default at the start + /// of the file. + /// Example: + /// + /// ``` + /// // ::check-ir:: + /// // ::check-ir-optimized:: + /// // ::check-ir-asm:: + /// ``` + /// + /// # ::check-ir-optimized:: + /// + /// Optimized IR chekcer can be configured with `pass: `. When + /// `o1` is chosen, all the configured passes are chosen automatically. + /// + /// ``` + /// // ::check-ir-optimized:: + /// // pass: o1 + /// ``` + pub fn new(input: impl AsRef) -> Vec<(Checker, Option)> { + let input = input.as_ref(); + + let mut checkers: Vec<(Checker, String)> = vec![(Checker::Ir, "".to_string())]; + + for line in input.lines() { + if line.contains("::check-ir::") && !matches!(checkers.last(), Some((Checker::Ir, _))) { + checkers.push((Checker::Ir, "".to_string())); + } + + if line.contains("::check-asm::") { + checkers.push((Checker::Asm, "".to_string())); + } + + if line.contains("::check-ir-optimized::") { + checkers.push((Checker::OptimizedIr { passes: vec![] }, "".to_string())); + } + + if let Some(pass) = line.strip_prefix("// pass: ") { + if let Some((Checker::OptimizedIr { passes }, _)) = checkers.last_mut() { + passes.push(pass.trim().to_string()); + } + } + + if line.starts_with("//") { + let s = checkers.last_mut().unwrap(); + s.1.push_str(line); + s.1.push('\n'); + } + } + + let mut new_checkers = vec![]; + + for (k, v) in checkers { + let ir_checker = filecheck::CheckerBuilder::new() + .text(&v) + .unwrap() + .finish() + .is_empty() + .not() + .then(|| { + filecheck::CheckerBuilder::new() + .text( + "regex: VAL=\\bv\\d+\\b\n\ + regex: ID=[_[:alpha:]][_0-9[:alpha:]]*\n\ + regex: MD=!\\d+\n", + ) + .unwrap() + .text(&v) + .unwrap() + .finish() + }); + new_checkers.push((k, ir_checker)); + } + + new_checkers + } +} + +/// Will print `filecheck` report using colors: normal lines will be dimmed, +/// matches will be green and misses will be red. +fn pretty_print_error_report(error: &str) { + let mut stash = vec![]; + + let mut lines = error.lines().peekable(); + while let Some(current) = lines.next() { + if current.starts_with("> ") { + match lines.peek() { + Some(next) if next.contains("^~") => { + stash.push(current); + } + _ => println!("{}", current.bright_black()), + } + } else if current.starts_with("Matched") && current.contains("not: ") { + for line in stash.drain(..) { + if line.contains("^~") { + println!("{}", line.red()) + } else { + println!("{}", line.bold()) + } + } + println!("{}", current.red()) + } else if current.starts_with("Matched") { + for line in stash.drain(..) { + if line.contains("^~") { + println!("{}", line.green()) + } else { + println!("{}", line.bold()) + } + } + println!("{}", current) + } else if current.starts_with("Define") { + println!("{}", current) + } else if current.starts_with("Missed") && current.contains("check: ") { + for line in stash.drain(..) { + if line.contains("^~") { + println!("{}", line.red()) + } else { + println!("{}", line.bold()) + } + } + println!("{}", current.red()) + } else if current.starts_with("Missed") && current.contains("not: ") { + for line in stash.drain(..) { + if line.contains("^~") { + println!("{}", line.green()) + } else { + println!("{}", line.bold()) + } + } + println!("{}", current) + } else { + stash.push(current); + } + } +} + +pub(super) async fn run(filter_regex: Option<®ex::Regex>, verbose: bool) -> Result<()> { // Compile core library and reuse it when compiling tests. let engines = Engines::default(); let build_target = BuildTarget::default(); @@ -39,14 +184,7 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { let input_bytes = fs::read(&path).expect("Read entire Sway source."); let input = String::from_utf8_lossy(&input_bytes); - // Split into Sway, FileCheck of IR, FileCheck of ASM. - // - // - Search for the optional boundaries. If they exist, delimited by special tags, - // then they mark the boundaries for their checks. If the IR delimiter is missing then - // it's assumed to be from the start of the file. The ASM checks themselves are - // entirely optional. - let ir_checks_begin_offs = input.find("::check-ir::").unwrap_or(0); - let asm_checks_begin_offs = input.find("::check-asm::"); + let checkers = Checker::new(&input); let mut optimisation_inline = false; let mut target_fuelvm = false; @@ -56,64 +194,16 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { target_fuelvm = first_line.contains("target-fuelvm"); } - let ir_checks_end_offs = match asm_checks_begin_offs { - Some(asm_offs) if asm_offs > ir_checks_begin_offs => asm_offs, - _otherwise => input.len(), - }; - - // This is slightly convoluted. We want to build the checker from the text, but also - // provide some builtin regexes for VAL, ID and MD. If the checker is empty after - // parsing the test source then it has no checks which is invalid (and below we - // helpfully print out the IR so some checks can be authored). But if we add the - // regexes first then it can't be empty and there's no other simple way to tell. - // Ideally we'd be able get it from the result of `CheckerBuilder::text()` or to get a - // count of the found directives (and check they're greater than 3). - // - // So instead it builds a temporary checker, tests if it's empty and sets it to None if - // so. Otherwise it's discarded and we build another one with the regexes provided. - use std::ops::Not; - let ir_checker = filecheck::CheckerBuilder::new() - .text(&input[ir_checks_begin_offs..ir_checks_end_offs]) - .unwrap() - .finish() - .is_empty() - .not() - .then(|| { - filecheck::CheckerBuilder::new() - .text( - "regex: VAL=\\bv\\d+\\b\n\ - regex: ID=[_[:alpha:]][_0-9[:alpha:]]*\n\ - regex: MD=!\\d+\n", - ) - .unwrap() - .text(&input[ir_checks_begin_offs..ir_checks_end_offs]) - .unwrap() - .finish() - }); - - let asm_checker = asm_checks_begin_offs.map(|begin_offs| { - let end_offs = if ir_checks_begin_offs > begin_offs { - ir_checks_begin_offs - } else { - input.len() - }; - filecheck::CheckerBuilder::new() - .text(&input[begin_offs..end_offs]) - .unwrap() - .finish() - }); - ( path, input_bytes, - ir_checker, - asm_checker, + checkers, optimisation_inline, target_fuelvm, ) }) .for_each( - |(path, sway_str, ir_checker, opt_asm_checker, optimisation_inline, target_fuelvm)| { + |(path, sway_str, checkers, optimisation_inline, target_fuelvm)| { let test_file_name = path.file_name().unwrap().to_string_lossy().to_string(); tracing::info!("Testing {} ...", test_file_name.bold()); @@ -205,84 +295,146 @@ pub(super) async fn run(filter_regex: Option<®ex::Regex>) -> Result<()> { let ir_output = sway_ir::printer::to_string(&ir); - if ir_checker.is_none() { - panic!( - "IR test for {test_file_name} is missing mandatory FileCheck directives.\n\n\ - Here's the IR output:\n{ir_output}", - ); - } - - // Do IR checks. - match ir_checker - .unwrap() - .explain(&ir_output, filecheck::NO_VARIABLES) - { - Ok((success, report)) if !success => { - panic!("IR filecheck failed:\n{report}"); - } - Err(e) => { - panic!("IR filecheck directive error: {e}"); - } - _ => (), - }; - - if optimisation_inline { - let mut pass_mgr = PassManager::default(); - let mut pmgr_config = PassGroup::default(); - let inline = pass_mgr.register(create_inline_in_module_pass()); - pmgr_config.append_pass(inline); - let inline_res = pass_mgr.run(&mut ir, &pmgr_config); - if inline_res.is_err() { - panic!( - "Failed to compile test {}:\n{}", - path.display(), - compile_res - .errors - .iter() - .map(|err| err.to_string()) - .collect::>() - .as_slice() - .join("\n") - ); - } - } - - if let Some(asm_checker) = opt_asm_checker { - // Compile to ASM. - let asm_result = compile_ir_to_asm(&ir, None); - if !asm_result.is_ok() { - println!("Errors when compiling {test_file_name} IR to ASM:\n"); - for e in asm_result.errors { - println!("{e}\n"); + for (k, checker) in checkers { + match (k, checker) { + (Checker::Ir, Some(checker)) => { + match checker.explain(&ir_output, filecheck::NO_VARIABLES) + { + Ok((success, error)) if !success || verbose => { + if !success || verbose { + println!("{}", "::check-ir::".bold()); + pretty_print_error_report(&error); + } + if !success { + panic!("check-ir filecheck failed. See above."); + } + } + Err(e) => { + panic!("check-ir filecheck directive error: {e}"); + } + _ => (), + }; } - panic!(); - }; - - let asm_output = asm_result - .value - .map(|asm| format!("{asm}")) - .expect("Failed to stringify ASM for {test_file_name}."); - - if asm_checker.is_empty() { - panic!( - "ASM test for {} has the '::check-asm::' marker \ - but is missing directives.\n\ - Please either remove the marker or add some.\n\n\ - Here's the ASM output:\n{asm_output}", - path.file_name().unwrap().to_string_lossy() - ); - } - - // Do ASM checks. - match asm_checker.explain(&asm_output, filecheck::NO_VARIABLES) { - Ok((success, report)) if !success => { - panic!("ASM filecheck for {test_file_name}failed:\n{report}"); + (Checker::Ir, None) => { + panic!( + "IR test for {test_file_name} is missing mandatory FileCheck directives.\n\n\ + Here's the IR output:\n{ir_output}", + ); } - Err(e) => { - panic!("ASM filecheck directive errors for {test_file_name}: {e}"); + (Checker::OptimizedIr { passes }, Some(checker)) => { + if passes.is_empty() { + panic!("No optimization passes were specified for ::check-ir-optimized::. Use `// pass: ` in the very next line."); + } + + let mut group = PassGroup::default(); + for pass in passes { + if pass == "o1" { + group = sway_ir::create_o1_pass_group(); + } else { + // pass needs a 'static str + let pass = Box::leak(Box::new(pass)); + group.append_pass(pass.as_str()); + } + } + + let mut pass_mgr = PassManager::default(); + register_known_passes(&mut pass_mgr); + + // Parse the IR again avoiding mutating the original ir + let mut ir = sway_ir::parser::parse( + &ir_output, + engines.se() + ) + .unwrap_or_else(|e| panic!("{}: {e}\n{ir_output}", path.display())); + + let _ = pass_mgr.run(&mut ir, &group); + let ir_output = sway_ir::printer::to_string(&ir); + + match checker.explain(&ir_output, filecheck::NO_VARIABLES) + { + Ok((success, error)) if !success || verbose => { + if !success || verbose { + println!("{}", "::check-ir-optimized::".bold()); + pretty_print_error_report(&error); + } + if !success { + panic!("check-ir-optimized filecheck failed. See above."); + } + } + Err(e) => { + panic!("check-ir-optimized filecheck directive error: {e}"); + } + _ => (), + }; } - _ => (), - }; + (Checker::Asm, Some(checker)) => { + if optimisation_inline { + let mut pass_mgr = PassManager::default(); + let mut pmgr_config = PassGroup::default(); + let inline = pass_mgr.register(create_inline_in_module_pass()); + pmgr_config.append_pass(inline); + let inline_res = pass_mgr.run(&mut ir, &pmgr_config); + if inline_res.is_err() { + panic!( + "Failed to compile test {}:\n{}", + path.display(), + compile_res + .errors + .iter() + .map(|err| err.to_string()) + .collect::>() + .as_slice() + .join("\n") + ); + } + } + + // Compile to ASM. + let asm_result = compile_ir_to_asm(&ir, None); + + if !asm_result.is_ok() { + println!("Errors when compiling {test_file_name} IR to ASM:\n"); + for e in asm_result.errors { + println!("{e}\n"); + } + panic!(); + }; + + let asm_output = asm_result + .value + .map(|asm| format!("{asm}")) + .expect("Failed to stringify ASM for {test_file_name}."); + + if checker.is_empty() { + panic!( + "ASM test for {} has the '::check-asm::' marker \ + but is missing directives.\n\ + Please either remove the marker or add some.\n\n\ + Here's the ASM output:\n{asm_output}", + path.file_name().unwrap().to_string_lossy() + ); + } + + // Do ASM checks. + match checker.explain(&asm_output, filecheck::NO_VARIABLES) { + Ok((success, error)) => { + if !success || verbose { + println!("{}", "::check-asm::".bold()); + pretty_print_error_report(&error); + } + if !success { + panic!("check-asm filecheck for {test_file_name} failed. See above."); + } + } + Err(e) => { + panic!("check-asm filecheck directive errors for {test_file_name}: {e}"); + } + }; + } + (_, _) => { + todo!("Unknown checker"); + } + } } // Parse the IR again, and print it yet again to make sure that IR de/serialisation works. diff --git a/test/src/ir_generation/tests/let_const_init.sw b/test/src/ir_generation/tests/let_const_init.sw new file mode 100644 index 00000000000..d5b4c2efe0a --- /dev/null +++ b/test/src/ir_generation/tests/let_const_init.sw @@ -0,0 +1,25 @@ +script; + +fn main() -> u64 { + let a = 1 + 2 + 3 + 4; + a +} + +// check: local u64 a +// check: $(v0=$VAL) = const u64 1 +// check: $(v1=$VAL) = const u64 2 +// check: $(v2=$VAL) = call add_0($v0, $v1) +// check: $(v3=$VAL) = const u64 3 +// check: $(v4=$VAL) = call add_0($v2, $v3) +// check: $(v5=$VAL) = const u64 4 +// check: $(v6=$VAL) = call add_0($v4, $v5) +// check: $(a_addr=$VAL) = get_local ptr u64, a +// check: store $v6 to $a_addr + +// ::check-ir-optimized:: +// pass: o1 + +// not: local u64 a +// check: entry(): +// check: $(a=$VAL) = const u64 10 +// check: ret u64 $a diff --git a/test/src/main.rs b/test/src/main.rs index 031c37e6727..95065b7519c 100644 --- a/test/src/main.rs +++ b/test/src/main.rs @@ -99,7 +99,7 @@ async fn main() -> Result<()> { // Run IR tests if !filter_config.first_only { println!("\n"); - ir_generation::run(filter_config.include.as_ref()) + ir_generation::run(filter_config.include.as_ref(), cli.verbose) .instrument(tracing::trace_span!("IR")) .await?; }