With FuncEnvironment using FuncCursors in place of full FunctionBuilders, it's useful to move several of these convenience functions from FunctionBuilder to Function.
424 lines
13 KiB
Rust
424 lines
13 KiB
Rust
//! Converting Cretonne IL to text.
|
|
//!
|
|
//! The `write` module provides the `write_function` function which converts an IL `Function` to an
|
|
//! equivalent textual representation. This textual representation can be read back by the
|
|
//! `cretonne-reader` crate.
|
|
|
|
use ir::{Function, DataFlowGraph, Ebb, Inst, Value, ValueDef, Type};
|
|
use isa::{TargetIsa, RegInfo};
|
|
use std::fmt::{self, Result, Error, Write};
|
|
use std::result;
|
|
|
|
/// Write `func` to `w` as equivalent text.
|
|
/// Use `isa` to emit ISA-dependent annotations.
|
|
pub fn write_function(w: &mut Write, func: &Function, isa: Option<&TargetIsa>) -> Result {
|
|
let regs = isa.map(TargetIsa::register_info);
|
|
let regs = regs.as_ref();
|
|
|
|
write_spec(w, func, regs)?;
|
|
writeln!(w, " {{")?;
|
|
let mut any = write_preamble(w, func, regs)?;
|
|
for ebb in &func.layout {
|
|
if any {
|
|
writeln!(w, "")?;
|
|
}
|
|
write_ebb(w, func, isa, ebb)?;
|
|
any = true;
|
|
}
|
|
writeln!(w, "}}")
|
|
}
|
|
|
|
// ====--------------------------------------------------------------------------------------====//
|
|
//
|
|
// Function spec.
|
|
//
|
|
// ====--------------------------------------------------------------------------------------====//
|
|
|
|
fn write_spec(w: &mut Write, func: &Function, regs: Option<&RegInfo>) -> Result {
|
|
write!(w, "function {}{}", func.name, func.signature.display(regs))
|
|
}
|
|
|
|
fn write_preamble(
|
|
w: &mut Write,
|
|
func: &Function,
|
|
regs: Option<&RegInfo>,
|
|
) -> result::Result<bool, Error> {
|
|
let mut any = false;
|
|
|
|
for ss in func.stack_slots.keys() {
|
|
any = true;
|
|
writeln!(w, " {} = {}", ss, func.stack_slots[ss])?;
|
|
}
|
|
|
|
for gv in func.global_vars.keys() {
|
|
any = true;
|
|
writeln!(w, " {} = {}", gv, func.global_vars[gv])?;
|
|
}
|
|
|
|
for heap in func.heaps.keys() {
|
|
any = true;
|
|
writeln!(w, " {} = {}", heap, func.heaps[heap])?;
|
|
}
|
|
|
|
// Write out all signatures before functions since function declarations can refer to
|
|
// signatures.
|
|
for sig in func.dfg.signatures.keys() {
|
|
any = true;
|
|
writeln!(
|
|
w,
|
|
" {} = {}",
|
|
sig,
|
|
func.dfg.signatures[sig].display(regs)
|
|
)?;
|
|
}
|
|
|
|
for fnref in func.dfg.ext_funcs.keys() {
|
|
any = true;
|
|
writeln!(w, " {} = {}", fnref, func.dfg.ext_funcs[fnref])?;
|
|
}
|
|
|
|
for jt in func.jump_tables.keys() {
|
|
any = true;
|
|
writeln!(w, " {} = {}", jt, func.jump_tables[jt])?;
|
|
}
|
|
|
|
Ok(any)
|
|
}
|
|
|
|
// ====--------------------------------------------------------------------------------------====//
|
|
//
|
|
// Basic blocks
|
|
//
|
|
// ====--------------------------------------------------------------------------------------====//
|
|
|
|
pub fn write_arg(w: &mut Write, func: &Function, arg: Value) -> Result {
|
|
write!(w, "{}: {}", arg, func.dfg.value_type(arg))
|
|
}
|
|
|
|
pub fn write_ebb_header(w: &mut Write, func: &Function, ebb: Ebb) -> Result {
|
|
// Write out the basic block header, outdented:
|
|
//
|
|
// ebb1:
|
|
// ebb1(v1: i32):
|
|
// ebb10(v4: f64, v5: b1):
|
|
//
|
|
|
|
// If we're writing encoding annotations, shift by 20.
|
|
if !func.encodings.is_empty() {
|
|
write!(w, " ")?;
|
|
}
|
|
|
|
let mut args = func.dfg.ebb_args(ebb).iter().cloned();
|
|
match args.next() {
|
|
None => return writeln!(w, "{}:", ebb),
|
|
Some(arg) => {
|
|
write!(w, "{}(", ebb)?;
|
|
write_arg(w, func, arg)?;
|
|
}
|
|
}
|
|
// Remaining arguments.
|
|
for arg in args {
|
|
write!(w, ", ")?;
|
|
write_arg(w, func, arg)?;
|
|
}
|
|
writeln!(w, "):")
|
|
}
|
|
|
|
pub fn write_ebb(w: &mut Write, func: &Function, isa: Option<&TargetIsa>, ebb: Ebb) -> Result {
|
|
write_ebb_header(w, func, ebb)?;
|
|
for inst in func.layout.ebb_insts(ebb) {
|
|
write_instruction(w, func, isa, inst)?;
|
|
}
|
|
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_ebb = match func.dfg.value_def(ctrl_var) {
|
|
ValueDef::Res(instr, _) => func.layout.inst_ebb(instr),
|
|
ValueDef::Arg(ebb, _) => Some(ebb),
|
|
};
|
|
if def_ebb.is_some() && def_ebb == func.layout.inst_ebb(inst) {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
let rtype = func.dfg.ctrl_typevar(inst);
|
|
assert!(
|
|
!rtype.is_void(),
|
|
"Polymorphic instruction must produce a result"
|
|
);
|
|
Some(rtype)
|
|
}
|
|
|
|
// Write out any value aliases appearing in `inst`.
|
|
fn write_value_aliases(w: &mut Write, func: &Function, inst: Inst, indent: usize) -> Result {
|
|
for &arg in func.dfg.inst_args(inst) {
|
|
let resolved = func.dfg.resolve_aliases(arg);
|
|
if resolved != arg {
|
|
writeln!(w, "{1:0$}{2} -> {3}", indent, "", arg, resolved)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn write_instruction(
|
|
w: &mut Write,
|
|
func: &Function,
|
|
isa: Option<&TargetIsa>,
|
|
inst: Inst,
|
|
) -> Result {
|
|
// Indent all instructions to col 24 if any encodings are present.
|
|
let indent = if func.encodings.is_empty() { 4 } else { 24 };
|
|
|
|
// Value aliases come out on lines before the instruction using them.
|
|
write_value_aliases(w, func, inst, indent)?;
|
|
|
|
// Write out encoding info.
|
|
if let Some(enc) = func.encodings.get(inst).cloned() {
|
|
let mut s = String::with_capacity(16);
|
|
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)?;
|
|
}
|
|
// Align instruction following ISA annotation to col 24.
|
|
write!(w, "{:23} ", s)?;
|
|
} else {
|
|
// No annotations, simply indent.
|
|
write!(w, "{1:0$}", indent, "")?;
|
|
}
|
|
|
|
// 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, "")
|
|
}
|
|
|
|
/// Write the operands of `inst` to `w` with a prepended space.
|
|
pub fn write_operands(
|
|
w: &mut Write,
|
|
dfg: &DataFlowGraph,
|
|
isa: Option<&TargetIsa>,
|
|
inst: Inst,
|
|
) -> Result {
|
|
let pool = &dfg.value_lists;
|
|
use ir::instructions::InstructionData::*;
|
|
match dfg[inst] {
|
|
Nullary { .. } => write!(w, ""),
|
|
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),
|
|
UnaryGlobalVar { global_var, .. } => write!(w, " {}", global_var),
|
|
Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),
|
|
BinaryImm { 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)))
|
|
}
|
|
}
|
|
InsertLane { lane, args, .. } => write!(w, " {}, {}, {}", args[0], lane, args[1]),
|
|
ExtractLane { lane, arg, .. } => write!(w, " {}, {}", arg, lane),
|
|
IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
|
|
IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, imm),
|
|
FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
|
|
Jump {
|
|
destination,
|
|
ref args,
|
|
..
|
|
} => {
|
|
if args.is_empty() {
|
|
write!(w, " {}", destination)
|
|
} else {
|
|
write!(
|
|
w,
|
|
" {}({})",
|
|
destination,
|
|
DisplayValues(args.as_slice(pool))
|
|
)
|
|
}
|
|
}
|
|
Branch {
|
|
destination,
|
|
ref args,
|
|
..
|
|
} => {
|
|
let args = args.as_slice(pool);
|
|
write!(w, " {}, {}", args[0], destination)?;
|
|
if args.len() > 1 {
|
|
write!(w, "({})", DisplayValues(&args[1..]))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
BranchIcmp {
|
|
cond,
|
|
destination,
|
|
ref args,
|
|
..
|
|
} => {
|
|
let args = args.as_slice(pool);
|
|
write!(w, " {} {}, {}, {}", cond, args[0], args[1], destination)?;
|
|
if args.len() > 2 {
|
|
write!(w, "({})", DisplayValues(&args[2..]))?;
|
|
}
|
|
Ok(())
|
|
}
|
|
BranchTable { arg, table, .. } => write!(w, " {}, {}", arg, table),
|
|
Call { func_ref, ref args, .. } => {
|
|
write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))
|
|
}
|
|
IndirectCall { sig_ref, ref args, .. } => {
|
|
let args = args.as_slice(pool);
|
|
write!(
|
|
w,
|
|
" {}, {}({})",
|
|
sig_ref,
|
|
args[0],
|
|
DisplayValues(&args[1..])
|
|
)
|
|
}
|
|
StackLoad { stack_slot, offset, .. } => write!(w, " {}{}", stack_slot, offset),
|
|
StackStore {
|
|
arg,
|
|
stack_slot,
|
|
offset,
|
|
..
|
|
} => write!(w, " {}, {}{}", arg, stack_slot, offset),
|
|
HeapLoad { arg, offset, .. } => write!(w, " {}{}", arg, offset),
|
|
HeapStore { args, offset, .. } => write!(w, " {}, {}{}", args[0], args[1], offset),
|
|
HeapAddr { heap, arg, imm, .. } => write!(w, " {}, {}, {}", heap, arg, imm),
|
|
Load { flags, arg, offset, .. } => write!(w, "{} {}{}", flags, arg, offset),
|
|
Store {
|
|
flags,
|
|
args,
|
|
offset,
|
|
..
|
|
} => write!(w, "{} {}, {}{}", flags, args[0], 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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Displayable slice of values.
|
|
struct DisplayValues<'a>(&'a [Value]);
|
|
|
|
impl<'a> fmt::Display for DisplayValues<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result {
|
|
for (i, val) in self.0.iter().enumerate() {
|
|
if i == 0 {
|
|
write!(f, "{}", val)?;
|
|
} else {
|
|
write!(f, ", {}", val)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use ir::{Function, FunctionName, StackSlotData, StackSlotKind};
|
|
use ir::types;
|
|
|
|
#[test]
|
|
fn basic() {
|
|
let mut f = Function::new();
|
|
assert_eq!(f.to_string(), "function %() native {\n}\n");
|
|
|
|
f.name = FunctionName::new("foo");
|
|
assert_eq!(f.to_string(), "function %foo() native {\n}\n");
|
|
|
|
f.create_stack_slot(StackSlotData::new(StackSlotKind::Local, 4));
|
|
assert_eq!(
|
|
f.to_string(),
|
|
"function %foo() native {\n ss0 = local 4\n}\n"
|
|
);
|
|
|
|
let ebb = f.dfg.make_ebb();
|
|
f.layout.append_ebb(ebb);
|
|
assert_eq!(
|
|
f.to_string(),
|
|
"function %foo() native {\n ss0 = local 4\n\nebb0:\n}\n"
|
|
);
|
|
|
|
f.dfg.append_ebb_arg(ebb, types::I8);
|
|
assert_eq!(
|
|
f.to_string(),
|
|
"function %foo() native {\n ss0 = local 4\n\nebb0(v0: i8):\n}\n"
|
|
);
|
|
|
|
f.dfg.append_ebb_arg(ebb, types::F32.by(4).unwrap());
|
|
assert_eq!(
|
|
f.to_string(),
|
|
"function %foo() native {\n ss0 = local 4\n\nebb0(v0: i8, v1: f32x4):\n}\n"
|
|
);
|
|
}
|
|
}
|