| //! Converting Cranelift IR to text. |
| //! |
| //! The `write` module provides the `write_function` function which converts an IR `Function` to an |
| //! equivalent textual form. This textual form can be read back by the `cranelift-reader` crate. |
| |
| use crate::entity::SecondaryMap; |
| use crate::ir::entities::AnyEntity; |
| use crate::ir::{ |
| Block, DataFlowGraph, DisplayFunctionAnnotations, Function, Inst, SigRef, Type, Value, |
| ValueDef, ValueLoc, |
| }; |
| use crate::isa::{RegInfo, TargetIsa}; |
| use crate::packed_option::ReservedValue; |
| use crate::value_label::ValueLabelsRanges; |
| use crate::HashSet; |
| use alloc::string::String; |
| use alloc::vec::Vec; |
| use core::fmt::{self, Write}; |
| |
| /// A `FuncWriter` used to decorate functions during printing. |
| pub trait FuncWriter { |
| /// Write the basic block header for the current function. |
| fn write_block_header( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| isa: Option<&dyn TargetIsa>, |
| block: Block, |
| indent: usize, |
| ) -> fmt::Result; |
| |
| /// Write the given `inst` to `w`. |
| fn write_instruction( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| isa: Option<&dyn TargetIsa>, |
| inst: Inst, |
| indent: usize, |
| ) -> fmt::Result; |
| |
| /// Write the preamble to `w`. By default, this uses `write_entity_definition`. |
| fn write_preamble( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| regs: Option<&RegInfo>, |
| ) -> Result<bool, fmt::Error> { |
| self.super_preamble(w, func, regs) |
| } |
| |
| /// Default impl of `write_preamble` |
| fn super_preamble( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| regs: Option<&RegInfo>, |
| ) -> Result<bool, fmt::Error> { |
| let mut any = false; |
| |
| for (ss, slot) in func.stack_slots.iter() { |
| any = true; |
| self.write_entity_definition(w, func, ss.into(), slot)?; |
| } |
| |
| for (gv, gv_data) in &func.global_values { |
| any = true; |
| self.write_entity_definition(w, func, gv.into(), gv_data)?; |
| } |
| |
| for (heap, heap_data) in &func.heaps { |
| if !heap_data.index_type.is_invalid() { |
| any = true; |
| self.write_entity_definition(w, func, heap.into(), heap_data)?; |
| } |
| } |
| |
| for (table, table_data) in &func.tables { |
| if !table_data.index_type.is_invalid() { |
| any = true; |
| self.write_entity_definition(w, func, table.into(), table_data)?; |
| } |
| } |
| |
| // Write out all signatures before functions since function declarations can refer to |
| // signatures. |
| for (sig, sig_data) in &func.dfg.signatures { |
| any = true; |
| self.write_entity_definition(w, func, sig.into(), &sig_data.display(regs))?; |
| } |
| |
| for (fnref, ext_func) in &func.dfg.ext_funcs { |
| if ext_func.signature != SigRef::reserved_value() { |
| any = true; |
| self.write_entity_definition(w, func, fnref.into(), ext_func)?; |
| } |
| } |
| |
| for (jt, jt_data) in &func.jump_tables { |
| any = true; |
| self.write_entity_definition(w, func, jt.into(), jt_data)?; |
| } |
| |
| for (&cref, cval) in func.dfg.constants.iter() { |
| any = true; |
| self.write_entity_definition(w, func, cref.into(), cval)?; |
| } |
| |
| if let Some(limit) = func.stack_limit { |
| any = true; |
| self.write_entity_definition(w, func, AnyEntity::StackLimit, &limit)?; |
| } |
| |
| Ok(any) |
| } |
| |
| /// Write an entity definition defined in the preamble to `w`. |
| fn write_entity_definition( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| entity: AnyEntity, |
| value: &dyn fmt::Display, |
| ) -> fmt::Result { |
| self.super_entity_definition(w, func, entity, value) |
| } |
| |
| /// Default impl of `write_entity_definition` |
| #[allow(unused_variables)] |
| fn super_entity_definition( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| entity: AnyEntity, |
| value: &dyn fmt::Display, |
| ) -> fmt::Result { |
| writeln!(w, " {} = {}", entity, value) |
| } |
| } |
| |
| /// A `PlainWriter` that doesn't decorate the function. |
| pub struct PlainWriter; |
| |
| impl FuncWriter for PlainWriter { |
| fn write_instruction( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| isa: Option<&dyn TargetIsa>, |
| inst: Inst, |
| indent: usize, |
| ) -> fmt::Result { |
| write_instruction(w, func, aliases, isa, inst, indent) |
| } |
| |
| fn write_block_header( |
| &mut self, |
| w: &mut dyn Write, |
| func: &Function, |
| isa: Option<&dyn TargetIsa>, |
| block: Block, |
| indent: usize, |
| ) -> fmt::Result { |
| write_block_header(w, func, isa, block, indent) |
| } |
| } |
| |
| /// Write `func` to `w` as equivalent text. |
| /// Use `isa` to emit ISA-dependent annotations. |
| pub fn write_function( |
| w: &mut dyn Write, |
| func: &Function, |
| annotations: &DisplayFunctionAnnotations, |
| ) -> fmt::Result { |
| decorate_function(&mut PlainWriter, w, func, annotations) |
| } |
| |
| /// Create a reverse-alias map from a value to all aliases having that value as a direct target |
| fn alias_map(func: &Function) -> SecondaryMap<Value, Vec<Value>> { |
| let mut aliases = SecondaryMap::<_, Vec<_>>::new(); |
| for v in func.dfg.values() { |
| // VADFS returns the immediate target of an alias |
| if let Some(k) = func.dfg.value_alias_dest_for_serialization(v) { |
| aliases[k].push(v); |
| } |
| } |
| aliases |
| } |
| |
| /// Writes `func` to `w` as text. |
| /// write_function_plain is passed as 'closure' to print instructions as text. |
| /// pretty_function_error is passed as 'closure' to add error decoration. |
| pub fn decorate_function<FW: FuncWriter>( |
| func_w: &mut FW, |
| w: &mut dyn Write, |
| func: &Function, |
| annotations: &DisplayFunctionAnnotations, |
| ) -> fmt::Result { |
| let regs = annotations.isa.map(TargetIsa::register_info); |
| let regs = regs.as_ref(); |
| |
| write!(w, "function ")?; |
| write_spec(w, func, regs)?; |
| writeln!(w, " {{")?; |
| let aliases = alias_map(func); |
| let mut any = func_w.write_preamble(w, func, regs)?; |
| for block in &func.layout { |
| if any { |
| writeln!(w)?; |
| } |
| decorate_block(func_w, w, func, &aliases, annotations, block)?; |
| any = true; |
| } |
| writeln!(w, "}}") |
| } |
| |
| //---------------------------------------------------------------------- |
| // |
| // Function spec. |
| |
| fn write_spec(w: &mut dyn Write, func: &Function, regs: Option<&RegInfo>) -> fmt::Result { |
| write!(w, "{}{}", func.name, func.signature.display(regs)) |
| } |
| |
| //---------------------------------------------------------------------- |
| // |
| // Basic blocks |
| |
| fn write_arg( |
| w: &mut dyn Write, |
| func: &Function, |
| regs: Option<&RegInfo>, |
| arg: Value, |
| ) -> fmt::Result { |
| write!(w, "{}: {}", arg, func.dfg.value_type(arg))?; |
| let loc = func.locations[arg]; |
| if loc.is_assigned() { |
| write!(w, " [{}]", loc.display(regs))? |
| } |
| |
| Ok(()) |
| } |
| |
| /// Write out the basic block header, outdented: |
| /// |
| /// block1: |
| /// block1(v1: i32): |
| /// block10(v4: f64, v5: b1): |
| /// |
| pub fn write_block_header( |
| w: &mut dyn Write, |
| func: &Function, |
| isa: Option<&dyn TargetIsa>, |
| block: Block, |
| indent: usize, |
| ) -> fmt::Result { |
| // The `indent` is the instruction indentation. block headers are 4 spaces out from that. |
| write!(w, "{1:0$}{2}", indent - 4, "", block)?; |
| |
| let regs = isa.map(TargetIsa::register_info); |
| let regs = regs.as_ref(); |
| |
| let mut args = func.dfg.block_params(block).iter().cloned(); |
| match args.next() { |
| None => return writeln!(w, ":"), |
| Some(arg) => { |
| write!(w, "(")?; |
| write_arg(w, func, regs, arg)?; |
| } |
| } |
| // Remaining arguments. |
| for arg in args { |
| write!(w, ", ")?; |
| write_arg(w, func, regs, arg)?; |
| } |
| writeln!(w, "):") |
| } |
| |
| fn write_valueloc(w: &mut dyn Write, loc: ValueLoc, regs: &RegInfo) -> fmt::Result { |
| match loc { |
| ValueLoc::Reg(r) => write!(w, "{}", regs.display_regunit(r)), |
| ValueLoc::Stack(ss) => write!(w, "{}", ss), |
| ValueLoc::Unassigned => write!(w, "?"), |
| } |
| } |
| |
| fn write_value_range_markers( |
| w: &mut dyn Write, |
| val_ranges: &ValueLabelsRanges, |
| regs: &RegInfo, |
| offset: u32, |
| indent: usize, |
| ) -> fmt::Result { |
| let mut result = String::new(); |
| let mut shown = HashSet::new(); |
| for (val, rng) in val_ranges { |
| for i in (0..rng.len()).rev() { |
| if rng[i].start == offset { |
| write!(&mut result, " {}@", val)?; |
| write_valueloc(&mut result, rng[i].loc, regs)?; |
| shown.insert(val); |
| break; |
| } |
| } |
| } |
| for (val, rng) in val_ranges { |
| for i in (0..rng.len()).rev() { |
| if rng[i].end == offset && !shown.contains(val) { |
| write!(&mut result, " {}\u{2620}", val)?; |
| break; |
| } |
| } |
| } |
| if !result.is_empty() { |
| writeln!(w, ";{1:0$}; {2}", indent + 24, "", result)?; |
| } |
| Ok(()) |
| } |
| |
| fn decorate_block<FW: FuncWriter>( |
| func_w: &mut FW, |
| w: &mut dyn Write, |
| func: &Function, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| annotations: &DisplayFunctionAnnotations, |
| block: Block, |
| ) -> fmt::Result { |
| // Indent all instructions if any encodings are present. |
| let indent = if func.encodings.is_empty() && func.srclocs.is_empty() { |
| 4 |
| } else { |
| 36 |
| }; |
| let isa = annotations.isa; |
| |
| func_w.write_block_header(w, func, isa, block, indent)?; |
| for a in func.dfg.block_params(block).iter().cloned() { |
| write_value_aliases(w, aliases, a, indent)?; |
| } |
| |
| if let Some(isa) = isa { |
| if !func.offsets.is_empty() { |
| let encinfo = isa.encoding_info(); |
| let regs = &isa.register_info(); |
| for (offset, inst, size) in func.inst_offsets(block, &encinfo) { |
| func_w.write_instruction(w, func, aliases, Some(isa), inst, indent)?; |
| if size > 0 { |
| if let Some(val_ranges) = annotations.value_ranges { |
| write_value_range_markers(w, val_ranges, regs, offset + size, indent)?; |
| } |
| } |
| } |
| return Ok(()); |
| } |
| } |
| |
| for inst in func.layout.block_insts(block) { |
| func_w.write_instruction(w, func, aliases, isa, inst, indent)?; |
| } |
| |
| Ok(()) |
| } |
| |
| //---------------------------------------------------------------------- |
| // |
| // Instructions |
| |
| // Should `inst` be printed with a type suffix? |
| // |
| // Polymorphic instructions may need a suffix indicating the value of the controlling type variable |
| // if it can't be trivially inferred. |
| // |
| fn type_suffix(func: &Function, inst: Inst) -> Option<Type> { |
| let inst_data = &func.dfg[inst]; |
| let constraints = inst_data.opcode().constraints(); |
| |
| if !constraints.is_polymorphic() { |
| return None; |
| } |
| |
| // If the controlling type variable can be inferred from the type of the designated value input |
| // operand, we don't need the type suffix. |
| if constraints.use_typevar_operand() { |
| let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap(); |
| let def_block = match func.dfg.value_def(ctrl_var) { |
| ValueDef::Result(instr, _) => func.layout.inst_block(instr), |
| ValueDef::Param(block, _) => Some(block), |
| }; |
| if def_block.is_some() && def_block == func.layout.inst_block(inst) { |
| return None; |
| } |
| } |
| |
| let rtype = func.dfg.ctrl_typevar(inst); |
| assert!( |
| !rtype.is_invalid(), |
| "Polymorphic instruction must produce a result" |
| ); |
| Some(rtype) |
| } |
| |
| /// Write out any aliases to the given target, including indirect aliases |
| fn write_value_aliases( |
| w: &mut dyn Write, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| target: Value, |
| indent: usize, |
| ) -> fmt::Result { |
| let mut todo_stack = vec![target]; |
| while let Some(target) = todo_stack.pop() { |
| for &a in &aliases[target] { |
| writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?; |
| todo_stack.push(a); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn write_instruction( |
| w: &mut dyn Write, |
| func: &Function, |
| aliases: &SecondaryMap<Value, Vec<Value>>, |
| isa: Option<&dyn TargetIsa>, |
| inst: Inst, |
| indent: usize, |
| ) -> fmt::Result { |
| // Prefix containing source location, encoding, and value locations. |
| let mut s = String::with_capacity(16); |
| |
| // Source location goes first. |
| let srcloc = func.srclocs[inst]; |
| if !srcloc.is_default() { |
| write!(s, "{} ", srcloc)?; |
| } |
| |
| // Write out encoding info. |
| if let Some(enc) = func.encodings.get(inst).cloned() { |
| if let Some(isa) = isa { |
| write!(s, "[{}", isa.encoding_info().display(enc))?; |
| // Write value locations, if we have them. |
| if !func.locations.is_empty() { |
| let regs = isa.register_info(); |
| for &r in func.dfg.inst_results(inst) { |
| write!(s, ",{}", func.locations[r].display(®s))? |
| } |
| } |
| write!(s, "] ")?; |
| } else { |
| write!(s, "[{}] ", enc)?; |
| } |
| } |
| |
| // Write out prefix and indent the instruction. |
| write!(w, "{1:0$}", indent, s)?; |
| |
| // Write out the result values, if any. |
| let mut has_results = false; |
| for r in func.dfg.inst_results(inst) { |
| if !has_results { |
| has_results = true; |
| write!(w, "{}", r)?; |
| } else { |
| write!(w, ", {}", r)?; |
| } |
| } |
| if has_results { |
| write!(w, " = ")?; |
| } |
| |
| // Then the opcode, possibly with a '.type' suffix. |
| let opcode = func.dfg[inst].opcode(); |
| |
| match type_suffix(func, inst) { |
| Some(suf) => write!(w, "{}.{}", opcode, suf)?, |
| None => write!(w, "{}", opcode)?, |
| } |
| |
| write_operands(w, &func.dfg, isa, inst)?; |
| writeln!(w)?; |
| |
| // Value aliases come out on lines after the instruction defining the referent. |
| for r in func.dfg.inst_results(inst) { |
| write_value_aliases(w, aliases, *r, indent)?; |
| } |
| Ok(()) |
| } |
| |
| /// Write the operands of `inst` to `w` with a prepended space. |
| pub fn write_operands( |
| w: &mut dyn Write, |
| dfg: &DataFlowGraph, |
| isa: Option<&dyn TargetIsa>, |
| inst: Inst, |
| ) -> fmt::Result { |
| let pool = &dfg.value_lists; |
| use crate::ir::instructions::InstructionData::*; |
| match dfg[inst] { |
| AtomicRmw { op, args, .. } => write!(w, " {}, {}, {}", op, args[0], args[1]), |
| AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), |
| LoadNoOffset { flags, arg, .. } => write!(w, "{} {}", flags, arg), |
| StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]), |
| Unary { arg, .. } => write!(w, " {}", arg), |
| UnaryImm { imm, .. } => write!(w, " {}", imm), |
| UnaryIeee32 { imm, .. } => write!(w, " {}", imm), |
| UnaryIeee64 { imm, .. } => write!(w, " {}", imm), |
| UnaryBool { imm, .. } => write!(w, " {}", imm), |
| UnaryGlobalValue { global_value, .. } => write!(w, " {}", global_value), |
| UnaryConst { |
| constant_handle, .. |
| } => write!(w, " {}", constant_handle), |
| Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), |
| BinaryImm8 { arg, imm, .. } => write!(w, " {}, {}", arg, imm), |
| BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, imm), |
| Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), |
| MultiAry { ref args, .. } => { |
| if args.is_empty() { |
| write!(w, "") |
| } else { |
| write!(w, " {}", DisplayValues(args.as_slice(pool))) |
| } |
| } |
| NullAry { .. } => write!(w, " "), |
| TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm), |
| Shuffle { mask, args, .. } => { |
| let data = dfg.immediates.get(mask).expect( |
| "Expected the shuffle mask to already be inserted into the immediates table", |
| ); |
| write!(w, " {}, {}, {}", args[0], args[1], data) |
| } |
| IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), |
| IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, imm), |
| IntCond { cond, arg, .. } => write!(w, " {} {}", cond, arg), |
| FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]), |
| FloatCond { cond, arg, .. } => write!(w, " {} {}", cond, arg), |
| IntSelect { cond, args, .. } => { |
| write!(w, " {} {}, {}, {}", cond, args[0], args[1], args[2]) |
| } |
| Jump { |
| destination, |
| ref args, |
| .. |
| } => { |
| write!(w, " {}", destination)?; |
| write_block_args(w, args.as_slice(pool)) |
| } |
| Branch { |
| destination, |
| ref args, |
| .. |
| } => { |
| let args = args.as_slice(pool); |
| write!(w, " {}, {}", args[0], destination)?; |
| write_block_args(w, &args[1..]) |
| } |
| BranchInt { |
| cond, |
| destination, |
| ref args, |
| .. |
| } => { |
| let args = args.as_slice(pool); |
| write!(w, " {} {}, {}", cond, args[0], destination)?; |
| write_block_args(w, &args[1..]) |
| } |
| BranchFloat { |
| cond, |
| destination, |
| ref args, |
| .. |
| } => { |
| let args = args.as_slice(pool); |
| write!(w, " {} {}, {}", cond, args[0], destination)?; |
| write_block_args(w, &args[1..]) |
| } |
| BranchIcmp { |
| cond, |
| destination, |
| ref args, |
| .. |
| } => { |
| let args = args.as_slice(pool); |
| write!(w, " {} {}, {}, {}", cond, args[0], args[1], destination)?; |
| write_block_args(w, &args[2..]) |
| } |
| BranchTable { |
| arg, |
| destination, |
| table, |
| .. |
| } => write!(w, " {}, {}, {}", arg, destination, table), |
| BranchTableBase { table, .. } => write!(w, " {}", table), |
| BranchTableEntry { |
| args, imm, table, .. |
| } => write!(w, " {}, {}, {}, {}", args[0], args[1], imm, table), |
| IndirectJump { arg, table, .. } => write!(w, " {}, {}", arg, table), |
| Call { |
| func_ref, ref args, .. |
| } => write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool))), |
| CallIndirect { |
| sig_ref, ref args, .. |
| } => { |
| let args = args.as_slice(pool); |
| write!( |
| w, |
| " {}, {}({})", |
| sig_ref, |
| args[0], |
| DisplayValues(&args[1..]) |
| ) |
| } |
| FuncAddr { func_ref, .. } => write!(w, " {}", func_ref), |
| StackLoad { |
| stack_slot, offset, .. |
| } => write!(w, " {}{}", stack_slot, offset), |
| StackStore { |
| arg, |
| stack_slot, |
| offset, |
| .. |
| } => write!(w, " {}, {}{}", arg, stack_slot, offset), |
| HeapAddr { heap, arg, imm, .. } => write!(w, " {}, {}, {}", heap, arg, imm), |
| TableAddr { table, arg, .. } => write!(w, " {}, {}", table, arg), |
| Load { |
| flags, arg, offset, .. |
| } => write!(w, "{} {}{}", flags, arg, offset), |
| LoadComplex { |
| flags, |
| ref args, |
| offset, |
| .. |
| } => { |
| let args = args.as_slice(pool); |
| write!( |
| w, |
| "{} {}{}", |
| flags, |
| DisplayValuesWithDelimiter(&args, '+'), |
| offset |
| ) |
| } |
| Store { |
| flags, |
| args, |
| offset, |
| .. |
| } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset), |
| StoreComplex { |
| flags, |
| ref args, |
| offset, |
| .. |
| } => { |
| let args = args.as_slice(pool); |
| write!( |
| w, |
| "{} {}, {}{}", |
| flags, |
| args[0], |
| DisplayValuesWithDelimiter(&args[1..], '+'), |
| offset |
| ) |
| } |
| RegMove { arg, src, dst, .. } => { |
| if let Some(isa) = isa { |
| let regs = isa.register_info(); |
| write!( |
| w, |
| " {}, {} -> {}", |
| arg, |
| regs.display_regunit(src), |
| regs.display_regunit(dst) |
| ) |
| } else { |
| write!(w, " {}, %{} -> %{}", arg, src, dst) |
| } |
| } |
| CopySpecial { src, dst, .. } => { |
| if let Some(isa) = isa { |
| let regs = isa.register_info(); |
| write!( |
| w, |
| " {} -> {}", |
| regs.display_regunit(src), |
| regs.display_regunit(dst) |
| ) |
| } else { |
| write!(w, " %{} -> %{}", src, dst) |
| } |
| } |
| CopyToSsa { src, .. } => { |
| if let Some(isa) = isa { |
| let regs = isa.register_info(); |
| write!(w, " {}", regs.display_regunit(src)) |
| } else { |
| write!(w, " %{}", src) |
| } |
| } |
| RegSpill { arg, src, dst, .. } => { |
| if let Some(isa) = isa { |
| let regs = isa.register_info(); |
| write!(w, " {}, {} -> {}", arg, regs.display_regunit(src), dst) |
| } else { |
| write!(w, " {}, %{} -> {}", arg, src, dst) |
| } |
| } |
| RegFill { arg, src, dst, .. } => { |
| if let Some(isa) = isa { |
| let regs = isa.register_info(); |
| write!(w, " {}, {} -> {}", arg, src, regs.display_regunit(dst)) |
| } else { |
| write!(w, " {}, {} -> %{}", arg, src, dst) |
| } |
| } |
| Trap { code, .. } => write!(w, " {}", code), |
| CondTrap { arg, code, .. } => write!(w, " {}, {}", arg, code), |
| IntCondTrap { |
| cond, arg, code, .. |
| } => write!(w, " {} {}, {}", cond, arg, code), |
| FloatCondTrap { |
| cond, arg, code, .. |
| } => write!(w, " {} {}, {}", cond, arg, code), |
| } |
| } |
| |
| /// Write block args using optional parantheses. |
| fn write_block_args(w: &mut dyn Write, args: &[Value]) -> fmt::Result { |
| if args.is_empty() { |
| Ok(()) |
| } else { |
| write!(w, "({})", DisplayValues(args)) |
| } |
| } |
| |
| /// Displayable slice of values. |
| struct DisplayValues<'a>(&'a [Value]); |
| |
| impl<'a> fmt::Display for DisplayValues<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| for (i, val) in self.0.iter().enumerate() { |
| if i == 0 { |
| write!(f, "{}", val)?; |
| } else { |
| write!(f, ", {}", val)?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| struct DisplayValuesWithDelimiter<'a>(&'a [Value], char); |
| |
| impl<'a> fmt::Display for DisplayValuesWithDelimiter<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| for (i, val) in self.0.iter().enumerate() { |
| if i == 0 { |
| write!(f, "{}", val)?; |
| } else { |
| write!(f, "{}{}", self.1, val)?; |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::cursor::{Cursor, CursorPosition, FuncCursor}; |
| use crate::ir::types; |
| use crate::ir::{ExternalName, Function, InstBuilder, StackSlotData, StackSlotKind}; |
| use alloc::string::ToString; |
| |
| #[test] |
| fn basic() { |
| let mut f = Function::new(); |
| assert_eq!(f.to_string(), "function u0:0() fast {\n}\n"); |
| |
| f.name = ExternalName::testcase("foo"); |
| assert_eq!(f.to_string(), "function %foo() fast {\n}\n"); |
| |
| f.create_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4)); |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n}\n" |
| ); |
| |
| let block = f.dfg.make_block(); |
| f.layout.append_block(block); |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n" |
| ); |
| |
| f.dfg.append_block_param(block, types::I8); |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n" |
| ); |
| |
| f.dfg.append_block_param(block, types::F32.by(4).unwrap()); |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n" |
| ); |
| |
| { |
| let mut cursor = FuncCursor::new(&mut f); |
| cursor.set_position(CursorPosition::After(block)); |
| cursor.ins().return_(&[]) |
| }; |
| assert_eq!( |
| f.to_string(), |
| "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n" |
| ); |
| } |
| |
| #[test] |
| fn aliases() { |
| use crate::ir::InstBuilder; |
| |
| let mut func = Function::new(); |
| { |
| let block0 = func.dfg.make_block(); |
| let mut pos = FuncCursor::new(&mut func); |
| pos.insert_block(block0); |
| |
| // make some detached values for change_to_alias |
| let v0 = pos.func.dfg.append_block_param(block0, types::I32); |
| let v1 = pos.func.dfg.append_block_param(block0, types::I32); |
| let v2 = pos.func.dfg.append_block_param(block0, types::I32); |
| pos.func.dfg.detach_block_params(block0); |
| |
| // alias to a param--will be printed at beginning of block defining param |
| let v3 = pos.func.dfg.append_block_param(block0, types::I32); |
| pos.func.dfg.change_to_alias(v0, v3); |
| |
| // alias to an alias--should print attached to alias, not ultimate target |
| pos.func.dfg.make_value_alias_for_serialization(v0, v2); // v0 <- v2 |
| |
| // alias to a result--will be printed after instruction producing result |
| let _dummy0 = pos.ins().iconst(types::I32, 42); |
| let v4 = pos.ins().iadd(v0, v0); |
| pos.func.dfg.change_to_alias(v1, v4); |
| let _dummy1 = pos.ins().iconst(types::I32, 23); |
| let _v7 = pos.ins().iadd(v1, v1); |
| } |
| assert_eq!( |
| func.to_string(), |
| "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" |
| ); |
| } |
| } |