Path: blob/main/cranelift/codegen/src/write.rs
1693 views
//! Converting Cranelift IR to text.1//!2//! The `write` module provides the `write_function` function which converts an IR `Function` to an3//! equivalent textual form. This textual form can be read back by the `cranelift-reader` crate.45use crate::entity::SecondaryMap;6use crate::ir::entities::AnyEntity;7use crate::ir::immediates::Ieee128;8use crate::ir::pcc::Fact;9use crate::ir::{Block, DataFlowGraph, Function, Inst, Opcode, SigRef, Type, Value, ValueDef};10use crate::packed_option::ReservedValue;11use alloc::string::{String, ToString};12use alloc::vec::Vec;13use core::fmt::{self, Write};1415/// A `FuncWriter` used to decorate functions during printing.16pub trait FuncWriter {17/// Write the basic block header for the current function.18fn write_block_header(19&mut self,20w: &mut dyn Write,21func: &Function,22block: Block,23indent: usize,24) -> fmt::Result;2526/// Write the given `inst` to `w`.27fn write_instruction(28&mut self,29w: &mut dyn Write,30func: &Function,31aliases: &SecondaryMap<Value, Vec<Value>>,32inst: Inst,33indent: usize,34) -> fmt::Result;3536/// Write the preamble to `w`. By default, this uses `write_entity_definition`.37fn write_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> {38self.super_preamble(w, func)39}4041/// Default impl of `write_preamble`42fn super_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> {43let mut any = false;4445for (ss, slot) in func.dynamic_stack_slots.iter() {46any = true;47self.write_entity_definition(w, func, ss.into(), slot, None)?;48}4950for (ss, slot) in func.sized_stack_slots.iter() {51any = true;52self.write_entity_definition(w, func, ss.into(), slot, None)?;53}5455for (gv, gv_data) in &func.global_values {56any = true;57let maybe_fact = func.global_value_facts[gv].as_ref();58self.write_entity_definition(w, func, gv.into(), gv_data, maybe_fact)?;59}6061for (mt, mt_data) in &func.memory_types {62any = true;63self.write_entity_definition(w, func, mt.into(), mt_data, None)?;64}6566// Write out all signatures before functions since function declarations can refer to67// signatures.68for (sig, sig_data) in &func.dfg.signatures {69any = true;70self.write_entity_definition(w, func, sig.into(), &sig_data, None)?;71}7273for (fnref, ext_func) in &func.dfg.ext_funcs {74if ext_func.signature != SigRef::reserved_value() {75any = true;76self.write_entity_definition(77w,78func,79fnref.into(),80&ext_func.display(Some(&func.params)),81None,82)?;83}84}8586for (&cref, cval) in func.dfg.constants.iter() {87any = true;88self.write_entity_definition(w, func, cref.into(), cval, None)?;89}9091if let Some(limit) = func.stack_limit {92any = true;93self.write_entity_definition(w, func, AnyEntity::StackLimit, &limit, None)?;94}9596Ok(any)97}9899/// Write an entity definition defined in the preamble to `w`.100fn write_entity_definition(101&mut self,102w: &mut dyn Write,103func: &Function,104entity: AnyEntity,105value: &dyn fmt::Display,106maybe_fact: Option<&Fact>,107) -> fmt::Result {108self.super_entity_definition(w, func, entity, value, maybe_fact)109}110111/// Default impl of `write_entity_definition`112fn super_entity_definition(113&mut self,114w: &mut dyn Write,115_func: &Function,116entity: AnyEntity,117value: &dyn fmt::Display,118maybe_fact: Option<&Fact>,119) -> fmt::Result {120if let Some(fact) = maybe_fact {121writeln!(w, " {entity} ! {fact} = {value}")122} else {123writeln!(w, " {entity} = {value}")124}125}126}127128/// A `PlainWriter` that doesn't decorate the function.129pub struct PlainWriter;130131impl FuncWriter for PlainWriter {132fn write_instruction(133&mut self,134w: &mut dyn Write,135func: &Function,136aliases: &SecondaryMap<Value, Vec<Value>>,137inst: Inst,138indent: usize,139) -> fmt::Result {140write_instruction(w, func, aliases, inst, indent)141}142143fn write_block_header(144&mut self,145w: &mut dyn Write,146func: &Function,147block: Block,148indent: usize,149) -> fmt::Result {150write_block_header(w, func, block, indent)151}152}153154/// Write `func` to `w` as equivalent text.155/// Use `isa` to emit ISA-dependent annotations.156pub fn write_function(w: &mut dyn Write, func: &Function) -> fmt::Result {157decorate_function(&mut PlainWriter, w, func)158}159160/// Create a reverse-alias map from a value to all aliases having that value as a direct target161fn alias_map(func: &Function) -> SecondaryMap<Value, Vec<Value>> {162let mut aliases = SecondaryMap::<_, Vec<_>>::new();163for v in func.dfg.values() {164// VADFS returns the immediate target of an alias165if let Some(k) = func.dfg.value_alias_dest_for_serialization(v) {166aliases[k].push(v);167}168}169aliases170}171172/// Writes `func` to `w` as text.173/// write_function_plain is passed as 'closure' to print instructions as text.174/// pretty_function_error is passed as 'closure' to add error decoration.175pub fn decorate_function<FW: FuncWriter>(176func_w: &mut FW,177w: &mut dyn Write,178func: &Function,179) -> fmt::Result {180write!(w, "function ")?;181write_function_spec(w, func)?;182writeln!(w, " {{")?;183let aliases = alias_map(func);184let mut any = func_w.write_preamble(w, func)?;185for block in &func.layout {186if any {187writeln!(w)?;188}189decorate_block(func_w, w, func, &aliases, block)?;190any = true;191}192writeln!(w, "}}")193}194195//----------------------------------------------------------------------196//197// Function spec.198199/// Writes the spec (name and signature) of 'func' to 'w' as text.200pub fn write_function_spec(w: &mut dyn Write, func: &Function) -> fmt::Result {201write!(w, "{}{}", func.name, func.signature)202}203204//----------------------------------------------------------------------205//206// Basic blocks207208fn write_arg(w: &mut dyn Write, func: &Function, arg: Value) -> fmt::Result {209let ty = func.dfg.value_type(arg);210if let Some(f) = &func.dfg.facts[arg] {211write!(w, "{arg} ! {f}: {ty}")212} else {213write!(w, "{arg}: {ty}")214}215}216217/// Write out the basic block header, outdented:218///219/// block1:220/// block1(v1: i32):221/// block10(v4: f64, v5: i8):222///223pub fn write_block_header(224w: &mut dyn Write,225func: &Function,226block: Block,227indent: usize,228) -> fmt::Result {229let cold = if func.layout.is_cold(block) {230" cold"231} else {232""233};234235// The `indent` is the instruction indentation. block headers are 4 spaces out from that.236write!(w, "{1:0$}{2}", indent - 4, "", block)?;237238let mut args = func.dfg.block_params(block).iter().cloned();239match args.next() {240None => return writeln!(w, "{cold}:"),241Some(arg) => {242write!(w, "(")?;243write_arg(w, func, arg)?;244}245}246// Remaining arguments.247for arg in args {248write!(w, ", ")?;249write_arg(w, func, arg)?;250}251writeln!(w, "){cold}:")252}253254fn decorate_block<FW: FuncWriter>(255func_w: &mut FW,256w: &mut dyn Write,257func: &Function,258aliases: &SecondaryMap<Value, Vec<Value>>,259block: Block,260) -> fmt::Result {261// Indent all instructions if any srclocs are present.262let indent = if func.rel_srclocs().is_empty() { 4 } else { 36 };263264func_w.write_block_header(w, func, block, indent)?;265for a in func.dfg.block_params(block).iter().cloned() {266write_value_aliases(w, aliases, a, indent)?;267}268269for inst in func.layout.block_insts(block) {270func_w.write_instruction(w, func, aliases, inst, indent)?;271}272273Ok(())274}275276//----------------------------------------------------------------------277//278// Instructions279280// Should `inst` be printed with a type suffix?281//282// Polymorphic instructions may need a suffix indicating the value of the controlling type variable283// if it can't be trivially inferred.284//285fn type_suffix(func: &Function, inst: Inst) -> Option<Type> {286let inst_data = &func.dfg.insts[inst];287let constraints = inst_data.opcode().constraints();288289if !constraints.is_polymorphic() {290return None;291}292293// If the controlling type variable can be inferred from the type of the designated value input294// operand, we don't need the type suffix.295if constraints.use_typevar_operand() {296let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap();297let def_block = match func.dfg.value_def(ctrl_var) {298ValueDef::Result(instr, _) => func.layout.inst_block(instr),299ValueDef::Param(block, _) => Some(block),300ValueDef::Union(..) => None,301};302if def_block.is_some() && def_block == func.layout.inst_block(inst) {303return None;304}305}306307let rtype = func.dfg.ctrl_typevar(inst);308assert!(309!rtype.is_invalid(),310"Polymorphic instruction must produce a result"311);312Some(rtype)313}314315/// Write out any aliases to the given target, including indirect aliases316fn write_value_aliases(317w: &mut dyn Write,318aliases: &SecondaryMap<Value, Vec<Value>>,319target: Value,320indent: usize,321) -> fmt::Result {322let mut todo_stack = vec![target];323while let Some(target) = todo_stack.pop() {324for &a in &aliases[target] {325writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?;326todo_stack.push(a);327}328}329330Ok(())331}332333fn write_instruction(334w: &mut dyn Write,335func: &Function,336aliases: &SecondaryMap<Value, Vec<Value>>,337inst: Inst,338indent: usize,339) -> fmt::Result {340// Prefix containing source location, encoding, and value locations.341let mut s = String::with_capacity(16);342343// Source location goes first.344let srcloc = func.srcloc(inst);345if !srcloc.is_default() {346write!(s, "{srcloc} ")?;347}348349// Write out prefix and indent the instruction.350write!(w, "{s:indent$}")?;351352// Write out the result values, if any.353let mut has_results = false;354for r in func.dfg.inst_results(inst) {355if !has_results {356has_results = true;357write!(w, "{r}")?;358} else {359write!(w, ", {r}")?;360}361if let Some(f) = &func.dfg.facts[*r] {362write!(w, " ! {f}")?;363}364}365if has_results {366write!(w, " = ")?;367}368369// Then the opcode, possibly with a '.type' suffix.370let opcode = func.dfg.insts[inst].opcode();371372match type_suffix(func, inst) {373Some(suf) => write!(w, "{opcode}.{suf}")?,374None => write!(w, "{opcode}")?,375}376377write_operands(w, &func.dfg, inst)?;378writeln!(w)?;379380// Value aliases come out on lines after the instruction defining the referent.381for r in func.dfg.inst_results(inst) {382write_value_aliases(w, aliases, *r, indent)?;383}384Ok(())385}386387/// Write the operands of `inst` to `w` with a prepended space.388pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {389let pool = &dfg.value_lists;390let jump_tables = &dfg.jump_tables;391let exception_tables = &dfg.exception_tables;392use crate::ir::instructions::InstructionData::*;393let ctrl_ty = dfg.ctrl_typevar(inst);394match dfg.insts[inst] {395AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]),396AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),397LoadNoOffset { flags, arg, .. } => write!(w, "{flags} {arg}"),398StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]),399Unary { arg, .. } => write!(w, " {arg}"),400UnaryImm { imm, .. } => write!(w, " {}", {401let mut imm = imm;402if ctrl_ty.bits() != 0 {403imm = imm.sign_extend_from_width(ctrl_ty.bits());404}405imm406}),407UnaryIeee16 { imm, .. } => write!(w, " {imm}"),408UnaryIeee32 { imm, .. } => write!(w, " {imm}"),409UnaryIeee64 { imm, .. } => write!(w, " {imm}"),410UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"),411UnaryConst {412constant_handle, ..413} => write!(w, " {constant_handle}"),414Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),415BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"),416BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, {417let mut imm = imm;418if ctrl_ty.bits() != 0 {419imm = imm.sign_extend_from_width(ctrl_ty.bits());420}421imm422}),423Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),424MultiAry { ref args, .. } => {425if args.is_empty() {426write!(w, "")427} else {428write!(w, " {}", DisplayValues(args.as_slice(pool)))429}430}431NullAry { .. } => write!(w, " "),432TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm),433Shuffle { imm, args, .. } => {434let data = dfg.immediates.get(imm).expect(435"Expected the shuffle mask to already be inserted into the immediates table",436);437write!(w, " {}, {}, {}", args[0], args[1], data)438}439IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),440IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, {441let mut imm = imm;442if ctrl_ty.bits() != 0 {443imm = imm.sign_extend_from_width(ctrl_ty.bits());444}445imm446}),447IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code),448FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),449Jump { destination, .. } => {450write!(w, " {}", destination.display(pool))451}452Brif {453arg,454blocks: [block_then, block_else],455..456} => {457write!(w, " {}, {}", arg, block_then.display(pool))?;458write!(w, ", {}", block_else.display(pool))459}460BranchTable { arg, table, .. } => {461write!(w, " {}, {}", arg, jump_tables[table].display(pool))462}463Call {464func_ref, ref args, ..465} => {466write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?;467write_user_stack_map_entries(w, dfg, inst)468}469CallIndirect {470sig_ref, ref args, ..471} => {472let args = args.as_slice(pool);473write!(474w,475" {}, {}({})",476sig_ref,477args[0],478DisplayValues(&args[1..])479)?;480write_user_stack_map_entries(w, dfg, inst)481}482TryCall {483func_ref,484ref args,485exception,486..487} => {488write!(489w,490" {}({}), {}",491func_ref,492DisplayValues(args.as_slice(pool)),493exception_tables[exception].display(pool),494)495}496TryCallIndirect {497ref args,498exception,499..500} => {501let args = args.as_slice(pool);502write!(503w,504" {}({}), {}",505args[0],506DisplayValues(&args[1..]),507exception_tables[exception].display(pool),508)509}510FuncAddr { func_ref, .. } => write!(w, " {func_ref}"),511StackLoad {512stack_slot, offset, ..513} => write!(w, " {stack_slot}{offset}"),514StackStore {515arg,516stack_slot,517offset,518..519} => write!(w, " {arg}, {stack_slot}{offset}"),520DynamicStackLoad {521dynamic_stack_slot, ..522} => write!(w, " {dynamic_stack_slot}"),523DynamicStackStore {524arg,525dynamic_stack_slot,526..527} => write!(w, " {arg}, {dynamic_stack_slot}"),528Load {529flags, arg, offset, ..530} => write!(w, "{flags} {arg}{offset}"),531Store {532flags,533args,534offset,535..536} => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset),537Trap { code, .. } => write!(w, " {code}"),538CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"),539ExceptionHandlerAddress { block, imm, .. } => write!(w, " {block}, {imm}"),540}?;541542let mut sep = " ; ";543for arg in dfg.inst_values(inst) {544if let ValueDef::Result(src, _) = dfg.value_def(arg) {545let imm = match dfg.insts[src] {546UnaryImm { imm, .. } => {547let mut imm = imm;548if dfg.ctrl_typevar(src).bits() != 0 {549imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits());550}551imm.to_string()552}553UnaryIeee16 { imm, .. } => imm.to_string(),554UnaryIeee32 { imm, .. } => imm.to_string(),555UnaryIeee64 { imm, .. } => imm.to_string(),556UnaryConst {557constant_handle,558opcode: Opcode::F128const,559} => Ieee128::try_from(dfg.constants.get(constant_handle))560.expect("16-byte f128 constant")561.to_string(),562UnaryConst {563constant_handle, ..564} => constant_handle.to_string(),565_ => continue,566};567write!(w, "{sep}{arg} = {imm}")?;568sep = ", ";569}570}571Ok(())572}573574fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {575let entries = match dfg.user_stack_map_entries(inst) {576None => return Ok(()),577Some(es) => es,578};579write!(w, ", stack_map=[")?;580let mut need_comma = false;581for entry in entries {582if need_comma {583write!(w, ", ")?;584}585write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?;586need_comma = true;587}588write!(w, "]")?;589Ok(())590}591592/// Displayable slice of values.593struct DisplayValues<'a>(&'a [Value]);594595impl<'a> fmt::Display for DisplayValues<'a> {596fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {597for (i, val) in self.0.iter().enumerate() {598if i == 0 {599write!(f, "{val}")?;600} else {601write!(f, ", {val}")?;602}603}604Ok(())605}606}607608#[cfg(test)]609mod tests {610use crate::cursor::{Cursor, CursorPosition, FuncCursor};611use crate::ir::types;612use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName};613use alloc::string::ToString;614615#[test]616fn basic() {617let mut f = Function::new();618assert_eq!(f.to_string(), "function u0:0() fast {\n}\n");619620f.name = UserFuncName::testcase("foo");621assert_eq!(f.to_string(), "function %foo() fast {\n}\n");622623f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0));624assert_eq!(625f.to_string(),626"function %foo() fast {\n ss0 = explicit_slot 4\n}\n"627);628629let block = f.dfg.make_block();630f.layout.append_block(block);631assert_eq!(632f.to_string(),633"function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n"634);635636f.dfg.append_block_param(block, types::I8);637assert_eq!(638f.to_string(),639"function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n"640);641642f.dfg.append_block_param(block, types::F32.by(4).unwrap());643assert_eq!(644f.to_string(),645"function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n"646);647648{649let mut cursor = FuncCursor::new(&mut f);650cursor.set_position(CursorPosition::After(block));651cursor.ins().return_(&[])652};653assert_eq!(654f.to_string(),655"function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n"656);657658let mut f = Function::new();659f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2));660assert_eq!(661f.to_string(),662"function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n"663);664}665666#[test]667fn aliases() {668use crate::ir::InstBuilder;669670let mut func = Function::new();671{672let block0 = func.dfg.make_block();673let mut pos = FuncCursor::new(&mut func);674pos.insert_block(block0);675676// make some detached values for change_to_alias677let v0 = pos.func.dfg.append_block_param(block0, types::I32);678let v1 = pos.func.dfg.append_block_param(block0, types::I32);679let v2 = pos.func.dfg.append_block_param(block0, types::I32);680pos.func.dfg.detach_block_params(block0);681682// alias to a param--will be printed at beginning of block defining param683let v3 = pos.func.dfg.append_block_param(block0, types::I32);684pos.func.dfg.change_to_alias(v0, v3);685686// alias to an alias--should print attached to alias, not ultimate target687pos.func.dfg.make_value_alias_for_serialization(v0, v2); // v0 <- v2688689// alias to a result--will be printed after instruction producing result690let _dummy0 = pos.ins().iconst(types::I32, 42);691let v4 = pos.ins().iadd(v0, v0);692pos.func.dfg.change_to_alias(v1, v4);693let _dummy1 = pos.ins().iconst(types::I32, 23);694let _v7 = pos.ins().iadd(v1, v1);695}696assert_eq!(697func.to_string(),698"function u0:0() fast {\nblock0(v3: i32):\n v0 -> v3\n v2 -> v0\n v4 = iconst.i32 42\n v5 = iadd v0, v0\n v1 -> v5\n v6 = iconst.i32 23\n v7 = iadd v1, v1\n}\n"699);700}701702#[test]703fn cold_blocks() {704let mut func = Function::new();705{706let mut pos = FuncCursor::new(&mut func);707708let block0 = pos.func.dfg.make_block();709pos.insert_block(block0);710pos.func.layout.set_cold(block0);711712let block1 = pos.func.dfg.make_block();713pos.insert_block(block1);714pos.func.dfg.append_block_param(block1, types::I32);715pos.func.layout.set_cold(block1);716}717718assert_eq!(719func.to_string(),720"function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n"721);722}723}724725726