Path: blob/main/cranelift/codegen/src/write.rs
3052 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 or debug tags are present.262let indent = if func.rel_srclocs().is_empty() && func.debug_tags.is_empty() {2634264} else {26536266};267268func_w.write_block_header(w, func, block, indent)?;269for a in func.dfg.block_params(block).iter().cloned() {270write_value_aliases(w, aliases, a, indent)?;271}272273for inst in func.layout.block_insts(block) {274func_w.write_instruction(w, func, aliases, inst, indent)?;275}276277Ok(())278}279280//----------------------------------------------------------------------281//282// Instructions283284// Should `inst` be printed with a type suffix?285//286// Polymorphic instructions may need a suffix indicating the value of the controlling type variable287// if it can't be trivially inferred.288//289fn type_suffix(func: &Function, inst: Inst) -> Option<Type> {290let inst_data = &func.dfg.insts[inst];291let constraints = inst_data.opcode().constraints();292293if !constraints.is_polymorphic() {294return None;295}296297// If the controlling type variable can be inferred from the type of the designated value input298// operand, we don't need the type suffix.299if constraints.use_typevar_operand() {300let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap();301let def_block = match func.dfg.value_def(ctrl_var) {302ValueDef::Result(instr, _) => func.layout.inst_block(instr),303ValueDef::Param(block, _) => Some(block),304ValueDef::Union(..) => None,305};306if def_block.is_some() && def_block == func.layout.inst_block(inst) {307return None;308}309}310311let rtype = func.dfg.ctrl_typevar(inst);312assert!(313!rtype.is_invalid(),314"Polymorphic instruction must produce a result"315);316Some(rtype)317}318319/// Write out any aliases to the given target, including indirect aliases320fn write_value_aliases(321w: &mut dyn Write,322aliases: &SecondaryMap<Value, Vec<Value>>,323target: Value,324indent: usize,325) -> fmt::Result {326let mut todo_stack = vec![target];327while let Some(target) = todo_stack.pop() {328for &a in &aliases[target] {329writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?;330todo_stack.push(a);331}332}333334Ok(())335}336337fn write_instruction(338w: &mut dyn Write,339func: &Function,340aliases: &SecondaryMap<Value, Vec<Value>>,341inst: Inst,342mut indent: usize,343) -> fmt::Result {344// Prefix containing source location, encoding, and value locations.345let mut s = String::with_capacity(16);346347// Source location goes first.348let srcloc = func.srcloc(inst);349if !srcloc.is_default() {350write!(s, "{srcloc} ")?;351}352353// Write out any debug tags.354write_debug_tags(w, &func, inst, &mut indent)?;355356// Write out prefix and indent the instruction.357write!(w, "{s:indent$}")?;358359// Write out the result values, if any.360let mut has_results = false;361for r in func.dfg.inst_results(inst) {362if !has_results {363has_results = true;364write!(w, "{r}")?;365} else {366write!(w, ", {r}")?;367}368if let Some(f) = &func.dfg.facts[*r] {369write!(w, " ! {f}")?;370}371}372if has_results {373write!(w, " = ")?;374}375376// Then the opcode, possibly with a '.type' suffix.377let opcode = func.dfg.insts[inst].opcode();378379match type_suffix(func, inst) {380Some(suf) => write!(w, "{opcode}.{suf}")?,381None => write!(w, "{opcode}")?,382}383384write_operands(w, &func.dfg, inst)?;385writeln!(w)?;386387// Value aliases come out on lines after the instruction defining the referent.388for r in func.dfg.inst_results(inst) {389write_value_aliases(w, aliases, *r, indent)?;390}391Ok(())392}393394/// Write the operands of `inst` to `w` with a prepended space.395pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {396let pool = &dfg.value_lists;397let jump_tables = &dfg.jump_tables;398let exception_tables = &dfg.exception_tables;399use crate::ir::instructions::InstructionData::*;400let ctrl_ty = dfg.ctrl_typevar(inst);401match dfg.insts[inst] {402AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]),403AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),404LoadNoOffset { flags, arg, .. } => write!(w, "{flags} {arg}"),405StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]),406Unary { arg, .. } => write!(w, " {arg}"),407UnaryImm { imm, .. } => write!(w, " {}", {408let mut imm = imm;409if ctrl_ty.bits() != 0 {410imm = imm.sign_extend_from_width(ctrl_ty.bits());411}412imm413}),414UnaryIeee16 { imm, .. } => write!(w, " {imm}"),415UnaryIeee32 { imm, .. } => write!(w, " {imm}"),416UnaryIeee64 { imm, .. } => write!(w, " {imm}"),417UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"),418UnaryConst {419constant_handle, ..420} => write!(w, " {constant_handle}"),421Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),422BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"),423BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, {424let mut imm = imm;425if ctrl_ty.bits() != 0 {426imm = imm.sign_extend_from_width(ctrl_ty.bits());427}428imm429}),430Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),431MultiAry { ref args, .. } => {432if args.is_empty() {433write!(w, "")434} else {435write!(w, " {}", DisplayValues(args.as_slice(pool)))436}437}438NullAry { .. } => write!(w, " "),439TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm),440Shuffle { imm, args, .. } => {441let data = dfg.immediates.get(imm).expect(442"Expected the shuffle mask to already be inserted into the immediates table",443);444write!(w, " {}, {}, {}", args[0], args[1], data)445}446IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),447IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, {448let mut imm = imm;449if ctrl_ty.bits() != 0 {450imm = imm.sign_extend_from_width(ctrl_ty.bits());451}452imm453}),454IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code),455FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),456Jump { destination, .. } => {457write!(w, " {}", destination.display(pool))458}459Brif {460arg,461blocks: [block_then, block_else],462..463} => {464write!(w, " {}, {}", arg, block_then.display(pool))?;465write!(w, ", {}", block_else.display(pool))466}467BranchTable { arg, table, .. } => {468write!(w, " {}, {}", arg, jump_tables[table].display(pool))469}470Call {471func_ref, ref args, ..472} => {473write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?;474write_user_stack_map_entries(w, dfg, inst)475}476CallIndirect {477sig_ref, ref args, ..478} => {479let args = args.as_slice(pool);480write!(481w,482" {}, {}({})",483sig_ref,484args[0],485DisplayValues(&args[1..])486)?;487write_user_stack_map_entries(w, dfg, inst)488}489TryCall {490func_ref,491ref args,492exception,493..494} => {495write!(496w,497" {}({}), {}",498func_ref,499DisplayValues(args.as_slice(pool)),500exception_tables[exception].display(pool),501)502}503TryCallIndirect {504ref args,505exception,506..507} => {508let args = args.as_slice(pool);509write!(510w,511" {}({}), {}",512args[0],513DisplayValues(&args[1..]),514exception_tables[exception].display(pool),515)516}517FuncAddr { func_ref, .. } => write!(w, " {func_ref}"),518StackLoad {519stack_slot, offset, ..520} => write!(w, " {stack_slot}{offset}"),521StackStore {522arg,523stack_slot,524offset,525..526} => write!(w, " {arg}, {stack_slot}{offset}"),527DynamicStackLoad {528dynamic_stack_slot, ..529} => write!(w, " {dynamic_stack_slot}"),530DynamicStackStore {531arg,532dynamic_stack_slot,533..534} => write!(w, " {arg}, {dynamic_stack_slot}"),535Load {536flags, arg, offset, ..537} => write!(w, "{flags} {arg}{offset}"),538Store {539flags,540args,541offset,542..543} => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset),544Trap { code, .. } => write!(w, " {code}"),545CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"),546ExceptionHandlerAddress { block, imm, .. } => write!(w, " {block}, {imm}"),547}?;548549let mut sep = " ; ";550for arg in dfg.inst_values(inst) {551if let ValueDef::Result(src, _) = dfg.value_def(arg) {552let imm = match dfg.insts[src] {553UnaryImm { imm, .. } => {554let mut imm = imm;555if dfg.ctrl_typevar(src).bits() != 0 {556imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits());557}558imm.to_string()559}560UnaryIeee16 { imm, .. } => imm.to_string(),561UnaryIeee32 { imm, .. } => imm.to_string(),562UnaryIeee64 { imm, .. } => imm.to_string(),563UnaryConst {564constant_handle,565opcode: Opcode::F128const,566} => Ieee128::try_from(dfg.constants.get(constant_handle))567.expect("16-byte f128 constant")568.to_string(),569UnaryConst {570constant_handle, ..571} => constant_handle.to_string(),572_ => continue,573};574write!(w, "{sep}{arg} = {imm}")?;575sep = ", ";576}577}578Ok(())579}580581fn write_debug_tags(582w: &mut dyn Write,583func: &Function,584inst: Inst,585indent: &mut usize,586) -> fmt::Result {587let tags = func.debug_tags.get(inst);588if !tags.is_empty() {589let tags = tags590.iter()591.map(|tag| format!("{tag}"))592.collect::<Vec<_>>()593.join(", ");594let s = format!("<{tags}> ");595write!(w, "{s}")?;596*indent = indent.saturating_sub(s.len());597}598Ok(())599}600601fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {602let entries = match dfg.user_stack_map_entries(inst) {603None => return Ok(()),604Some(es) => es,605};606write!(w, ", stack_map=[")?;607let mut need_comma = false;608for entry in entries {609if need_comma {610write!(w, ", ")?;611}612write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?;613need_comma = true;614}615write!(w, "]")?;616Ok(())617}618619/// Displayable slice of values.620struct DisplayValues<'a>(&'a [Value]);621622impl<'a> fmt::Display for DisplayValues<'a> {623fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {624for (i, val) in self.0.iter().enumerate() {625if i == 0 {626write!(f, "{val}")?;627} else {628write!(f, ", {val}")?;629}630}631Ok(())632}633}634635#[cfg(test)]636mod tests {637use crate::cursor::{Cursor, CursorPosition, FuncCursor};638use crate::ir::types;639use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName};640use alloc::string::ToString;641642#[test]643fn basic() {644let mut f = Function::new();645assert_eq!(f.to_string(), "function u0:0() fast {\n}\n");646647f.name = UserFuncName::testcase("foo");648assert_eq!(f.to_string(), "function %foo() fast {\n}\n");649650f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0));651assert_eq!(652f.to_string(),653"function %foo() fast {\n ss0 = explicit_slot 4\n}\n"654);655656let block = f.dfg.make_block();657f.layout.append_block(block);658assert_eq!(659f.to_string(),660"function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n"661);662663f.dfg.append_block_param(block, types::I8);664assert_eq!(665f.to_string(),666"function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n"667);668669f.dfg.append_block_param(block, types::F32.by(4).unwrap());670assert_eq!(671f.to_string(),672"function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n"673);674675{676let mut cursor = FuncCursor::new(&mut f);677cursor.set_position(CursorPosition::After(block));678cursor.ins().return_(&[])679};680assert_eq!(681f.to_string(),682"function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n"683);684685let mut f = Function::new();686f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2));687assert_eq!(688f.to_string(),689"function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n"690);691}692693#[test]694fn aliases() {695use crate::ir::InstBuilder;696697let mut func = Function::new();698{699let block0 = func.dfg.make_block();700let mut pos = FuncCursor::new(&mut func);701pos.insert_block(block0);702703// make some detached values for change_to_alias704let v0 = pos.func.dfg.append_block_param(block0, types::I32);705let v1 = pos.func.dfg.append_block_param(block0, types::I32);706let v2 = pos.func.dfg.append_block_param(block0, types::I32);707pos.func.dfg.detach_block_params(block0);708709// alias to a param--will be printed at beginning of block defining param710let v3 = pos.func.dfg.append_block_param(block0, types::I32);711pos.func.dfg.change_to_alias(v0, v3);712713// alias to an alias--should print attached to alias, not ultimate target714pos.func.dfg.make_value_alias_for_serialization(v0, v2); // v0 <- v2715716// alias to a result--will be printed after instruction producing result717let _dummy0 = pos.ins().iconst(types::I32, 42);718let v4 = pos.ins().iadd(v0, v0);719pos.func.dfg.change_to_alias(v1, v4);720let _dummy1 = pos.ins().iconst(types::I32, 23);721let _v7 = pos.ins().iadd(v1, v1);722}723assert_eq!(724func.to_string(),725"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"726);727}728729#[test]730fn cold_blocks() {731let mut func = Function::new();732{733let mut pos = FuncCursor::new(&mut func);734735let block0 = pos.func.dfg.make_block();736pos.insert_block(block0);737pos.func.layout.set_cold(block0);738739let block1 = pos.func.dfg.make_block();740pos.insert_block(block1);741pos.func.dfg.append_block_param(block1, types::I32);742pos.func.layout.set_cold(block1);743}744745assert_eq!(746func.to_string(),747"function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n"748);749}750}751752753