1 //! Test command for testing inlining. 2 //! 3 //! The `inline` test command inlines all calls, and optionally optimizes each 4 //! function before and after the optimization passes. It does not perform 5 //! lowering or regalloc. The output for filecheck purposes is the resulting 6 //! CLIF. 7 //! 8 //! Some legalization may be ISA-specific, so this requires an ISA 9 //! (for now). 10 11 use crate::subtest::{Context, SubTest, check_precise_output, run_filecheck}; 12 use anyhow::{Context as _, Result}; 13 use cranelift_codegen::{ 14 inline::{Inline, InlineCommand}, 15 ir, 16 print_errors::pretty_verifier_error, 17 }; 18 use cranelift_control::ControlPlane; 19 use cranelift_reader::{TestCommand, TestOption}; 20 use std::{ 21 borrow::Cow, 22 cell::{Ref, RefCell}, 23 collections::HashMap, 24 }; 25 26 #[derive(Default)] 27 struct TestInline { 28 /// Flag indicating that the text expectation, comments after the function, 29 /// must be a precise 100% match on the compiled output of the function. 30 /// This test assertion is also automatically-update-able to allow tweaking 31 /// the code generator and easily updating all affected tests. 32 precise_output: bool, 33 34 /// Flag indicating whether to run optimizations on the function after 35 /// inlining. 36 optimize: bool, 37 38 /// The already-defined functions we have seen, available for inlining into 39 /// future functions. 40 funcs: RefCell<HashMap<ir::UserFuncName, ir::Function>>, 41 } 42 43 pub fn subtest(parsed: &TestCommand) -> Result<Box<dyn SubTest>> { 44 assert_eq!(parsed.command, "inline"); 45 let mut test = TestInline::default(); 46 for option in parsed.options.iter() { 47 match option { 48 TestOption::Flag("precise-output") => test.precise_output = true, 49 TestOption::Flag("optimize") => test.optimize = true, 50 _ => anyhow::bail!("unknown option on {parsed}"), 51 } 52 } 53 Ok(Box::new(test)) 54 } 55 56 impl SubTest for TestInline { 57 fn name(&self) -> &'static str { 58 "inline" 59 } 60 61 fn is_mutating(&self) -> bool { 62 true 63 } 64 65 fn needs_isa(&self) -> bool { 66 true 67 } 68 69 fn run(&self, func: Cow<ir::Function>, context: &Context) -> Result<()> { 70 // Legalize this function. 71 let isa = context.isa.unwrap(); 72 let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); 73 comp_ctx 74 .legalize(isa) 75 .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, e)) 76 .context("error while legalizing")?; 77 78 // Insert this function in our map for inlining into subsequent 79 // functions. 80 let func_name = comp_ctx.func.name.clone(); 81 self.funcs 82 .borrow_mut() 83 .insert(func_name, comp_ctx.func.clone()); 84 85 // Run the inliner. 86 let inlined_any = comp_ctx.inline(Inliner(self.funcs.borrow()))?; 87 88 // Verify that the CLIF is still valid. 89 comp_ctx 90 .verify(context.flags_or_isa()) 91 .map_err(|errors| { 92 anyhow::Error::msg(pretty_verifier_error(&comp_ctx.func, None, errors)) 93 }) 94 .context("CLIF verification error after inlining")?; 95 96 // If requested, run optimizations. 97 if self.optimize { 98 comp_ctx 99 .optimize(isa, &mut ControlPlane::default()) 100 .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, e)) 101 .context("error while optimizing")?; 102 } 103 104 // Check the filecheck expectations. 105 let actual = if inlined_any { 106 format!("{:?}", comp_ctx.func) 107 } else { 108 format!("(no functions inlined into {})", comp_ctx.func.name) 109 }; 110 log::debug!("filecheck input: {actual}"); 111 if self.precise_output { 112 let actual: Vec<_> = actual.lines().collect(); 113 check_precise_output(&actual, context) 114 } else { 115 run_filecheck(&actual, context) 116 } 117 } 118 } 119 120 struct Inliner<'a>(Ref<'a, HashMap<ir::UserFuncName, ir::Function>>); 121 122 impl<'a> Inline for Inliner<'a> { 123 fn inline( 124 &mut self, 125 caller: &ir::Function, 126 _inst: ir::Inst, 127 _opcode: ir::Opcode, 128 callee: ir::FuncRef, 129 _args: &[ir::Value], 130 ) -> InlineCommand<'_> { 131 match &caller.dfg.ext_funcs[callee].name { 132 ir::ExternalName::User(name) => match caller 133 .params 134 .user_named_funcs() 135 .get(*name) 136 .and_then(|name| self.0.get(&ir::UserFuncName::User(name.clone()))) 137 { 138 None => InlineCommand::KeepCall, 139 Some(f) => InlineCommand::Inline { 140 callee: Cow::Borrowed(f), 141 visit_callee: true, 142 }, 143 }, 144 ir::ExternalName::TestCase(name) => { 145 match self.0.get(&ir::UserFuncName::Testcase(name.clone())) { 146 None => InlineCommand::KeepCall, 147 Some(f) => InlineCommand::Inline { 148 callee: Cow::Borrowed(f), 149 visit_callee: true, 150 }, 151 } 152 } 153 ir::ExternalName::LibCall(_) | ir::ExternalName::KnownSymbol(_) => { 154 InlineCommand::KeepCall 155 } 156 } 157 } 158 } 159