Path: blob/main/cranelift/codegen/src/souper_harvest.rs
1693 views
//! Harvest left-hand side superoptimization candidates.1//!2//! Given a clif function, harvest all its integer subexpressions, so that they3//! can be fed into [Souper](https://github.com/google/souper) as candidates for4//! superoptimization. For some of these candidates, Souper will successfully5//! synthesize a right-hand side that is equivalent but has lower cost than the6//! left-hand side. Then, we can combine these left- and right-hand sides into a7//! complete optimization, and add it to our peephole passes.8//!9//! To harvest the expression that produced a given value `x`, we do a10//! post-order traversal of the dataflow graph starting from `x`. As we do this11//! traversal, we maintain a map from clif values to their translated Souper12//! values. We stop traversing when we reach anything that can't be translated13//! into Souper IR: a memory load, a float-to-int conversion, a block parameter,14//! etc. For values produced by these instructions, we create a Souper `var`,15//! which is an input variable to the optimization. For instructions that have a16//! direct mapping into Souper IR, we get the Souper version of each of its17//! operands and then create the Souper version of the instruction itself. It18//! should now be clear why we do a post-order traversal: we need an19//! instruction's translated operands in order to translate the instruction20//! itself. Once this instruction is translated, we update the clif-to-souper21//! map with this new translation so that any other instruction that uses this22//! result as an operand has access to the translated value. When the traversal23//! is complete we return the translation of `x` as the root of left-hand side24//! candidate.2526use crate::ir;27use souper_ir::ast;28use std::collections::{HashMap, HashSet};29use std::string::String;30use std::sync::mpsc;31use std::vec::Vec;3233/// Harvest Souper left-hand side candidates from the given function.34///35/// Candidates are reported through the given MPSC sender.36pub fn do_souper_harvest(func: &ir::Function, out: &mut mpsc::Sender<String>) {37let mut allocs = Allocs::default();3839// Iterate over each instruction in each block and try and harvest a40// left-hand side from its result.41for block in func.layout.blocks() {42let mut option_inst = func.layout.first_inst(block);43while let Some(inst) = option_inst {44let results = func.dfg.inst_results(inst);45if results.len() == 1 {46let val = results[0];47let ty = func.dfg.value_type(val);48if ty.is_int() && ty.lane_count() == 1 {49harvest_candidate_lhs(&mut allocs, func, val, out);50}51}52option_inst = func.layout.next_inst(inst);53}54}55}5657/// Allocations that we reuse across many LHS candidate harvests.58#[derive(Default)]59struct Allocs {60/// A map from cranelift IR to souper IR for values that we've already61/// translated into souper IR.62ir_to_souper_val: HashMap<ir::Value, ast::ValueId>,6364/// Stack of to-visit and to-trace values for the post-order DFS.65dfs_stack: Vec<StackEntry>,6667/// Set of values we've already seen in our post-order DFS.68dfs_seen: HashSet<ir::Value>,69}7071impl Allocs {72/// Reset the collections to their empty state (without deallocating their73/// backing data).74fn reset(&mut self) {75self.ir_to_souper_val.clear();76self.dfs_stack.clear();77self.dfs_seen.clear();78}79}8081/// Harvest a candidate LHS for `val` from the dataflow graph.82fn harvest_candidate_lhs(83allocs: &mut Allocs,84func: &ir::Function,85val: ir::Value,86out: &mut mpsc::Sender<String>,87) {88allocs.reset();89let mut lhs = ast::LeftHandSideBuilder::default();90let mut non_var_count = 0;9192// Should we keep tracing through the given `val`? Only if it is defined93// by an instruction that we can translate to Souper IR.94let should_trace = |val| match func.dfg.value_def(val) {95ir::ValueDef::Result(inst, 0) => match func.dfg.insts[inst].opcode() {96ir::Opcode::Iadd97| ir::Opcode::IaddImm98| ir::Opcode::IrsubImm99| ir::Opcode::Imul100| ir::Opcode::ImulImm101| ir::Opcode::Udiv102| ir::Opcode::UdivImm103| ir::Opcode::Sdiv104| ir::Opcode::SdivImm105| ir::Opcode::Urem106| ir::Opcode::UremImm107| ir::Opcode::Srem108| ir::Opcode::SremImm109| ir::Opcode::Band110| ir::Opcode::BandImm111| ir::Opcode::Bor112| ir::Opcode::BorImm113| ir::Opcode::Bxor114| ir::Opcode::BxorImm115| ir::Opcode::Ishl116| ir::Opcode::IshlImm117| ir::Opcode::Sshr118| ir::Opcode::SshrImm119| ir::Opcode::Ushr120| ir::Opcode::UshrImm121| ir::Opcode::Select122| ir::Opcode::Uextend123| ir::Opcode::Sextend124| ir::Opcode::Trunc125| ir::Opcode::Icmp126| ir::Opcode::Popcnt127| ir::Opcode::Bitrev128| ir::Opcode::Clz129| ir::Opcode::Ctz130// TODO: ir::Opcode::IaddCarry131| ir::Opcode::SaddSat132| ir::Opcode::SsubSat133| ir::Opcode::UsubSat => true,134_ => false,135},136_ => false,137};138139post_order_dfs(allocs, &func.dfg, val, should_trace, |allocs, val| {140let souper_assignment_rhs = match func.dfg.value_def(val) {141ir::ValueDef::Result(inst, 0) => {142let args = func.dfg.inst_args(inst);143144// Get the n^th argument as a souper operand.145let arg = |allocs: &mut Allocs, n| {146let arg = args[n];147if let Some(a) = allocs.ir_to_souper_val.get(&arg).copied() {148a.into()149} else {150// The only arguments we get that we haven't already151// converted into a souper instruction are `iconst`s.152// This is because souper only allows153// constants as operands, and it doesn't allow assigning154// constants to a variable name. So we lazily convert155// `iconst`s into souper operands here,156// when they are actually used.157match func.dfg.value_def(arg) {158ir::ValueDef::Result(inst, 0) => match func.dfg.insts[inst] {159ir::InstructionData::UnaryImm { opcode, imm } => {160debug_assert_eq!(opcode, ir::Opcode::Iconst);161let imm: i64 = imm.into();162ast::Operand::Constant(ast::Constant {163value: imm.into(),164r#type: souper_type_of(&func.dfg, arg),165})166}167_ => unreachable!(168"only iconst instructions \169aren't in `ir_to_souper_val`"170),171},172_ => unreachable!(173"only iconst instructions \174aren't in `ir_to_souper_val`"175),176}177}178};179180match (func.dfg.insts[inst].opcode(), &func.dfg.insts[inst]) {181(ir::Opcode::Iadd, _) => {182let a = arg(allocs, 0);183let b = arg(allocs, 1);184ast::Instruction::Add { a, b }.into()185}186(ir::Opcode::IaddImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {187let a = arg(allocs, 0);188let value: i64 = (*imm).into();189let value: i128 = value.into();190let b = ast::Constant {191value,192r#type: souper_type_of(&func.dfg, val),193}194.into();195ast::Instruction::Add { a, b }.into()196}197(ir::Opcode::IrsubImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {198let b = arg(allocs, 0);199let value: i64 = (*imm).into();200let value: i128 = value.into();201let a = ast::Constant {202value,203r#type: souper_type_of(&func.dfg, val),204}205.into();206ast::Instruction::Sub { a, b }.into()207}208(ir::Opcode::Imul, _) => {209let a = arg(allocs, 0);210let b = arg(allocs, 1);211ast::Instruction::Mul { a, b }.into()212}213(ir::Opcode::ImulImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {214let a = arg(allocs, 0);215let value: i64 = (*imm).into();216let value: i128 = value.into();217let b = ast::Constant {218value,219r#type: souper_type_of(&func.dfg, val),220}221.into();222ast::Instruction::Mul { a, b }.into()223}224(ir::Opcode::Udiv, _) => {225let a = arg(allocs, 0);226let b = arg(allocs, 1);227ast::Instruction::Udiv { a, b }.into()228}229(ir::Opcode::UdivImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {230let a = arg(allocs, 0);231let value: i64 = (*imm).into();232let value: i128 = value.into();233let b = ast::Constant {234value,235r#type: souper_type_of(&func.dfg, val),236}237.into();238ast::Instruction::Udiv { a, b }.into()239}240(ir::Opcode::Sdiv, _) => {241let a = arg(allocs, 0);242let b = arg(allocs, 1);243ast::Instruction::Sdiv { a, b }.into()244}245(ir::Opcode::SdivImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {246let a = arg(allocs, 0);247let value: i64 = (*imm).into();248let value: i128 = value.into();249let b = ast::Constant {250value,251r#type: souper_type_of(&func.dfg, val),252}253.into();254ast::Instruction::Sdiv { a, b }.into()255}256(ir::Opcode::Urem, _) => {257let a = arg(allocs, 0);258let b = arg(allocs, 1);259ast::Instruction::Urem { a, b }.into()260}261(ir::Opcode::UremImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {262let a = arg(allocs, 0);263let value: i64 = (*imm).into();264let value: i128 = value.into();265let b = ast::Constant {266value,267r#type: souper_type_of(&func.dfg, val),268}269.into();270ast::Instruction::Urem { a, b }.into()271}272(ir::Opcode::Srem, _) => {273let a = arg(allocs, 0);274let b = arg(allocs, 1);275ast::Instruction::Srem { a, b }.into()276}277(ir::Opcode::SremImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {278let a = arg(allocs, 0);279let value: i64 = (*imm).into();280let value: i128 = value.into();281let b = ast::Constant {282value,283r#type: souper_type_of(&func.dfg, val),284}285.into();286ast::Instruction::Srem { a, b }.into()287}288(ir::Opcode::Band, _) => {289let a = arg(allocs, 0);290let b = arg(allocs, 1);291ast::Instruction::And { a, b }.into()292}293(ir::Opcode::BandImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {294let a = arg(allocs, 0);295let value: i64 = (*imm).into();296let value: i128 = value.into();297let b = ast::Constant {298value,299r#type: souper_type_of(&func.dfg, val),300}301.into();302ast::Instruction::And { a, b }.into()303}304(ir::Opcode::Bor, _) => {305let a = arg(allocs, 0);306let b = arg(allocs, 1);307ast::Instruction::Or { a, b }.into()308}309(ir::Opcode::BorImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {310let a = arg(allocs, 0);311let value: i64 = (*imm).into();312let value: i128 = value.into();313let b = ast::Constant {314value,315r#type: souper_type_of(&func.dfg, val),316}317.into();318ast::Instruction::Or { a, b }.into()319}320(ir::Opcode::Bxor, _) => {321let a = arg(allocs, 0);322let b = arg(allocs, 1);323ast::Instruction::Xor { a, b }.into()324}325(ir::Opcode::BxorImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {326let a = arg(allocs, 0);327let value: i64 = (*imm).into();328let value: i128 = value.into();329let b = ast::Constant {330value,331r#type: souper_type_of(&func.dfg, val),332}333.into();334ast::Instruction::Xor { a, b }.into()335}336(ir::Opcode::Ishl, _) => {337let a = arg(allocs, 0);338let b = arg(allocs, 1);339ast::Instruction::Shl { a, b }.into()340}341(ir::Opcode::IshlImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {342let a = arg(allocs, 0);343let value: i64 = (*imm).into();344let value: i128 = value.into();345let b = ast::Constant {346value,347r#type: souper_type_of(&func.dfg, val),348}349.into();350ast::Instruction::Shl { a, b }.into()351}352(ir::Opcode::Sshr, _) => {353let a = arg(allocs, 0);354let b = arg(allocs, 1);355ast::Instruction::Ashr { a, b }.into()356}357(ir::Opcode::SshrImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {358let a = arg(allocs, 0);359let value: i64 = (*imm).into();360let value: i128 = value.into();361let b = ast::Constant {362value,363r#type: souper_type_of(&func.dfg, val),364}365.into();366ast::Instruction::Ashr { a, b }.into()367}368(ir::Opcode::Ushr, _) => {369let a = arg(allocs, 0);370let b = arg(allocs, 1);371ast::Instruction::Lshr { a, b }.into()372}373(ir::Opcode::UshrImm, ir::InstructionData::BinaryImm64 { imm, .. }) => {374let a = arg(allocs, 0);375let value: i64 = (*imm).into();376let value: i128 = value.into();377let b = ast::Constant {378value,379r#type: souper_type_of(&func.dfg, val),380}381.into();382ast::Instruction::Lshr { a, b }.into()383}384(ir::Opcode::Select, _) => {385let a = arg(allocs, 0);386387// While Cranelift allows any width condition for388// `select` and checks it against `0`, Souper requires389// an `i1`. So insert a `ne %x, 0` as needed.390let a = match a {391ast::Operand::Value(id) => match lhs.get_value(id).r#type {392Some(ast::Type { width: 1 }) => a,393_ => lhs394.assignment(395None,396Some(ast::Type { width: 1 }),397ast::Instruction::Ne {398a,399b: ast::Constant {400value: 0,401r#type: None,402}403.into(),404},405vec![],406)407.into(),408},409ast::Operand::Constant(ast::Constant { value, .. }) => ast::Constant {410value: (value != 0) as _,411r#type: Some(ast::Type { width: 1 }),412}413.into(),414};415416let b = arg(allocs, 1);417let c = arg(allocs, 2);418ast::Instruction::Select { a, b, c }.into()419}420(ir::Opcode::Uextend, _) => {421let a = arg(allocs, 0);422ast::Instruction::Zext { a }.into()423}424(ir::Opcode::Sextend, _) => {425let a = arg(allocs, 0);426ast::Instruction::Sext { a }.into()427}428(ir::Opcode::Trunc, _) => {429let a = arg(allocs, 0);430ast::Instruction::Trunc { a }.into()431}432(ir::Opcode::Icmp, ir::InstructionData::IntCompare { cond, .. })433| (ir::Opcode::IcmpImm, ir::InstructionData::IntCompare { cond, .. }) => {434let a = arg(allocs, 0);435let b = arg(allocs, 1);436match cond {437ir::condcodes::IntCC::Equal => ast::Instruction::Eq { a, b }.into(),438ir::condcodes::IntCC::NotEqual => ast::Instruction::Ne { a, b }.into(),439ir::condcodes::IntCC::UnsignedLessThan => {440ast::Instruction::Ult { a, b }.into()441}442ir::condcodes::IntCC::SignedLessThan => {443ast::Instruction::Slt { a, b }.into()444}445ir::condcodes::IntCC::UnsignedLessThanOrEqual => {446ast::Instruction::Sle { a, b }.into()447}448ir::condcodes::IntCC::SignedLessThanOrEqual => {449ast::Instruction::Sle { a, b }.into()450}451_ => ast::AssignmentRhs::Var,452}453}454(ir::Opcode::Popcnt, _) => {455let a = arg(allocs, 0);456ast::Instruction::Ctpop { a }.into()457}458(ir::Opcode::Bitrev, _) => {459let a = arg(allocs, 0);460ast::Instruction::BitReverse { a }.into()461}462(ir::Opcode::Clz, _) => {463let a = arg(allocs, 0);464ast::Instruction::Ctlz { a }.into()465}466(ir::Opcode::Ctz, _) => {467let a = arg(allocs, 0);468ast::Instruction::Cttz { a }.into()469}470// TODO: ir::Opcode::IaddCarry471(ir::Opcode::SaddSat, _) => {472let a = arg(allocs, 0);473let b = arg(allocs, 1);474ast::Instruction::SaddSat { a, b }.into()475}476(ir::Opcode::SsubSat, _) => {477let a = arg(allocs, 0);478let b = arg(allocs, 1);479ast::Instruction::SsubSat { a, b }.into()480}481(ir::Opcode::UsubSat, _) => {482let a = arg(allocs, 0);483let b = arg(allocs, 1);484ast::Instruction::UsubSat { a, b }.into()485}486// Because Souper doesn't allow constants to be on the right487// hand side of an assignment (i.e. `%0:i32 = 1234` is488// disallowed) we have to ignore `iconst`489// instructions until we process them as operands for some490// other instruction. See the `arg` closure above for491// details.492(ir::Opcode::Iconst, _) => return,493_ => ast::AssignmentRhs::Var,494}495}496_ => ast::AssignmentRhs::Var,497};498499non_var_count += match souper_assignment_rhs {500ast::AssignmentRhs::Var => 0,501_ => 1,502};503let souper_ty = souper_type_of(&func.dfg, val);504let souper_val = lhs.assignment(None, souper_ty, souper_assignment_rhs, vec![]);505let old_value = allocs.ir_to_souper_val.insert(val, souper_val);506assert!(old_value.is_none());507});508509// We end up harvesting a lot of candidates like:510//511// %0:i32 = var512// infer %0513//514// and515//516// %0:i32 = var517// %1:i32 = var518// %2:i32 = add %0, %1519//520// Both of these are useless. Only actually harvest the candidate if there521// are at least two actual operations.522if non_var_count >= 2 {523let lhs = lhs.finish(allocs.ir_to_souper_val[&val], None);524out.send(format!(525";; Harvested from `{}` in `{}`\n{}\n",526val, func.name, lhs527))528.unwrap();529}530}531532fn souper_type_of(dfg: &ir::DataFlowGraph, val: ir::Value) -> Option<ast::Type> {533let ty = dfg.value_type(val);534assert!(ty.is_int());535assert_eq!(ty.lane_count(), 1);536let width = match dfg.value_def(val).inst() {537Some(inst)538if dfg.insts[inst].opcode() == ir::Opcode::IcmpImm539|| dfg.insts[inst].opcode() == ir::Opcode::Icmp =>540{5411542}543_ => ty.bits().try_into().unwrap(),544};545Some(ast::Type { width })546}547548#[derive(Debug)]549enum StackEntry {550Visit(ir::Value),551Trace(ir::Value),552}553554fn post_order_dfs(555allocs: &mut Allocs,556dfg: &ir::DataFlowGraph,557val: ir::Value,558should_trace: impl Fn(ir::Value) -> bool,559mut visit: impl FnMut(&mut Allocs, ir::Value),560) {561allocs.dfs_stack.push(StackEntry::Trace(val));562563while let Some(entry) = allocs.dfs_stack.pop() {564match entry {565StackEntry::Visit(val) => {566let is_new = allocs.dfs_seen.insert(val);567if is_new {568visit(allocs, val);569}570}571StackEntry::Trace(val) => {572if allocs.dfs_seen.contains(&val) {573continue;574}575576allocs.dfs_stack.push(StackEntry::Visit(val));577if should_trace(val) {578if let ir::ValueDef::Result(inst, 0) = dfg.value_def(val) {579let args = dfg.inst_args(inst);580for v in args.iter().rev().copied() {581allocs.dfs_stack.push(StackEntry::Trace(v));582}583}584}585}586}587}588}589590591