use std::fmt::Write as _;
use rucc_base::{Interner, Symbol};
use crate::func::Func;
use crate::inst::{Block, BlockCall, Imm, Inst, InstData, MemInfo, Meta, Signature, Value};
use crate::module::{Alias, Datum, Global, Module, Reloc};
use crate::{Extra, FORMAT_VERSION, Linkage, MemOrder, Opcode, Type, Visibility};
pub(crate) fn implied_result(opcode: Opcode) -> bool {
matches!(
opcode,
Opcode::ICmp
| Opcode::FCmp
| Opcode::GlobalAddr
| Opcode::Alloca
| Opcode::Call
| Opcode::CallIndirect
| Opcode::TailCall
)
}
#[must_use]
pub fn print(module: &Module, names: &Interner) -> String {
let mut printer = Printer::new(module, names);
printer.module();
printer.finish()
}
#[must_use]
pub fn print_func(module: &Module, func: &Func, names: &Interner) -> String {
let mut printer = Printer::new(module, names);
printer.func(func);
printer.finish()
}
#[derive(Debug)]
pub struct Printer<'a> {
module: &'a Module,
names: &'a Interner,
out: String,
values: Vec<u32>,
blocks: Vec<u32>,
}
impl<'a> Printer<'a> {
#[must_use]
pub fn new(module: &'a Module, names: &'a Interner) -> Printer<'a> {
Printer { module, names, out: String::new(), values: Vec::new(), blocks: Vec::new() }
}
#[must_use]
pub fn finish(self) -> String {
self.out
}
pub fn module(&mut self) {
let module = self.module;
let name = self.names.resolve(module.name);
let _ = writeln!(self.out, "; ModuleID = '{name}'");
let _ = writeln!(self.out, "; format {FORMAT_VERSION}");
let _ = writeln!(self.out, "target triple = \"{}\"", module.triple);
let _ = writeln!(self.out, "target datalayout = \"{}\"", module.datalayout);
if module.globals().next().is_some() {
self.out.push('\n');
for id in module.globals() {
self.global(&module[id]);
}
}
if module.aliases().next().is_some() {
self.out.push('\n');
for id in module.aliases() {
self.alias(&module[id]);
}
}
for id in module.funcs() {
self.out.push('\n');
self.func(&module[id]);
}
if module.metadata().next().is_some() {
self.out.push('\n');
for meta in module.metadata() {
self.meta_node(meta);
}
}
}
fn global(&mut self, global: &Global) {
let _ = write!(self.out, "global @{} : ", self.names.resolve(global.name));
match self.scalar_init(global) {
Some((ty, imm)) => {
let _ = write!(self.out, "{ty} = ");
self.imm(imm, ty);
}
None => {
let _ = write!(self.out, "bytes {}", global.size);
if let Some(init) = global.init {
self.out.push_str(" = { ");
for (index, &datum) in self.module[init].iter().enumerate() {
if index > 0 {
self.out.push_str(", ");
}
self.datum(datum);
}
self.out.push_str(" }");
}
}
}
let _ = write!(self.out, ", align {}", global.align);
self.linkage(global.linkage, global.visibility);
if let Some(model) = global.tls {
let _ = write!(self.out, ", tls({})", model.name());
}
if global.constant {
self.out.push_str(", constant");
}
self.section(global.section);
self.out.push('\n');
}
fn scalar_init(&self, global: &Global) -> Option<(Type, Imm)> {
let init = global.init?;
let [datum] = self.module[init] else { return None };
let Datum::Scalar { ty, value } = datum else { return None };
(datum.size(self.module) == global.size).then(|| (ty, self.module[value]))
}
fn datum(&mut self, datum: Datum) {
match datum {
Datum::Zero(bytes) => {
let _ = write!(self.out, "zero {bytes}");
}
Datum::Bytes(range) => {
self.out.push_str("bytes ");
let bytes = &self.module[range];
self.string(bytes);
}
Datum::Scalar { ty, value } => {
let _ = write!(self.out, "{ty} ");
self.imm(self.module[value], ty);
}
Datum::Addr(reloc) => {
let Reloc { symbol, addend, size } = self.module[reloc];
let _ = write!(self.out, "addr.{size} @{}", self.names.resolve(symbol));
match addend.signum() {
1 => {
let _ = write!(self.out, " + {addend}");
}
-1 => {
let _ = match addend.checked_neg() {
Some(amount) => write!(self.out, " - {amount}"),
None => write!(self.out, " + {addend}"),
};
}
_ => {}
}
}
}
}
fn alias(&mut self, alias: &Alias) {
let _ = write!(
self.out,
"{} @{} = @{}",
alias.kind.name(),
self.names.resolve(alias.name),
self.names.resolve(alias.target)
);
self.linkage(alias.linkage, alias.visibility);
self.out.push('\n');
}
pub fn func(&mut self, func: &Func) {
self.number(func);
let _ = write!(self.out, "func @{}", self.names.resolve(func.name));
self.signature(func.signature());
self.linkage(func.linkage, func.visibility);
if !func.attrs.is_default() {
let _ = write!(self.out, ", {}", func.attrs);
}
self.section(func.section);
if func.is_declaration() {
self.out.push_str(";\n");
return;
}
self.out.push_str(" {\n");
for (index, block) in func.blocks().enumerate() {
if index > 0 {
self.out.push('\n');
}
self.block(func, block);
}
self.out.push_str("}\n");
}
fn number(&mut self, func: &Func) {
let counts = func.counts();
self.values.clear();
self.values.resize(counts.values, u32::MAX);
self.blocks.clear();
self.blocks.resize(counts.blocks, u32::MAX);
let mut next = 0;
for (index, block) in func.blocks().enumerate() {
self.blocks[block.index()] = index as u32;
for ¶m in &func[block].params {
self.values[param.index()] = next;
next += 1;
}
for inst in func.insts(block) {
for result in func[inst].results() {
self.values[result.index()] = next;
next += 1;
}
}
}
}
fn signature(&mut self, signature: &Signature) {
self.out.push('(');
for (index, &ty) in signature.params.iter().enumerate() {
if index > 0 {
self.out.push_str(", ");
}
let _ = write!(self.out, "{ty}");
}
if signature.variadic {
if !signature.params.is_empty() {
self.out.push_str(", ");
}
self.out.push_str("...");
}
self.out.push(')');
match signature.returns.as_slice() {
[] => {}
[ty] => {
let _ = write!(self.out, " -> {ty}");
}
types => {
self.out.push_str(" -> (");
for (index, &ty) in types.iter().enumerate() {
if index > 0 {
self.out.push_str(", ");
}
let _ = write!(self.out, "{ty}");
}
self.out.push(')');
}
}
}
fn block(&mut self, func: &Func, block: Block) {
let _ = write!(self.out, "block{}", self.blocks[block.index()]);
let params = &func[block].params;
if !params.is_empty() {
self.out.push('(');
for (index, ¶m) in params.iter().enumerate() {
if index > 0 {
self.out.push_str(", ");
}
self.value(param);
let _ = write!(self.out, ": {}", func[param].ty);
}
self.out.push(')');
}
self.out.push_str(":\n");
for inst in func.insts(block) {
self.inst(func, inst);
}
}
fn inst(&mut self, func: &Func, inst: Inst) {
let data = func[inst];
self.out.push_str(" ");
for (index, result) in data.results().enumerate() {
if index > 0 {
self.out.push_str(", ");
}
self.value(result);
}
if data.results > 0 {
self.out.push_str(" = ");
}
self.out.push_str(data.opcode.name());
self.result_types(func, &data);
let _ = write!(self.out, "{}", data.flags);
self.operands(func, &data);
self.out.push('\n');
}
fn result_types(&mut self, func: &Func, data: &InstData) {
let results: Vec<Value> = data.results().collect();
match results.as_slice() {
[] => {}
_ if implied_result(data.opcode) => {}
[result] => {
let ty = func[*result].ty;
let takes_the_same = func[data.args].first().is_some_and(|&arg| func[arg].ty == ty);
if !takes_the_same {
let _ = write!(self.out, ".{ty}");
}
}
types => {
self.out.push_str(".(");
for (index, &result) in types.iter().enumerate() {
if index > 0 {
self.out.push_str(", ");
}
let _ = write!(self.out, "{}", func[result].ty);
}
self.out.push(')');
}
}
}
fn operands(&mut self, func: &Func, data: &InstData) {
let args = &func[data.args];
match data.extra {
Extra::None => self.value_list_spaced(args),
Extra::Imm(imm) => {
self.out.push(' ');
let ty = data.first_result.map_or(Type::VOID, |result| func[result].ty);
self.imm(func[imm], ty);
}
Extra::Symbol(symbol) => {
let _ = write!(self.out, " @{}", self.names.resolve(symbol));
if !args.is_empty() {
self.out.push('(');
self.value_list(args);
self.out.push(')');
}
}
Extra::IntPred(pred) => {
let _ = write!(self.out, " {}", pred.name());
self.value_list_spaced(args);
}
Extra::FloatPred(pred) => {
let _ = write!(self.out, " {}", pred.name());
self.value_list_spaced(args);
}
Extra::Mem(mem) => {
match (data.opcode, args) {
(Opcode::Store | Opcode::AtomicStore, [value, addr]) => {
self.out.push(' ');
self.value(*value);
self.out.push_str(" -> ");
self.value(*addr);
}
_ => self.value_list_spaced(args),
}
self.mem(func[mem]);
}
Extra::Rmw(op, mem) => {
let _ = write!(self.out, " {}", op.name());
self.value_list_spaced(args);
self.mem(func[mem]);
}
Extra::Order(order) => {
let _ = write!(self.out, " {}", order.name());
}
Extra::Targets(targets) => {
if !args.is_empty() {
self.value_list_spaced(args);
self.out.push(',');
}
for (index, &call) in func[targets].iter().enumerate() {
self.out.push_str(if index > 0 { ", " } else { " " });
self.block_call(func, call);
}
}
Extra::Call(call) => {
let info = func[call];
let rest = match info.callee {
Some(callee) => {
let _ = write!(self.out, " @{}", self.names.resolve(callee));
args
}
None => {
self.out.push(' ');
match args.split_first() {
Some((&addr, rest)) => {
self.value(addr);
rest
}
None => {
self.out.push_str("%?");
&[]
}
}
}
};
self.out.push('(');
self.value_list(rest);
self.out.push_str(") : ");
self.signature(&func[info.signature]);
}
Extra::Switch(switch) => {
let info = func[switch];
let ty = args.first().map_or(Type::VOID, |&arg| func[arg].ty);
self.value_list_spaced(args);
if let Some((&default, cases)) = func[info.targets].split_first() {
self.out.push_str(", ");
self.block_call(func, default);
self.out.push_str(", [");
for (index, (&case, &value)) in cases.iter().zip(&func[info.cases]).enumerate()
{
if index > 0 {
self.out.push_str(", ");
}
self.imm(value, ty);
self.out.push_str(" => ");
self.block_call(func, case);
}
self.out.push(']');
}
}
Extra::Asm(asm) => {
let info = func[asm];
self.out.push(' ');
self.string(self.names.resolve(info.template).as_bytes());
self.out.push_str(", ");
self.string(self.names.resolve(info.constraints).as_bytes());
self.out.push_str(", ");
self.string(self.names.resolve(info.clobbers).as_bytes());
self.out.push('(');
self.value_list(args);
self.out.push(')');
if !info.targets.is_empty() {
self.out.push_str(", labels [");
for (index, &call) in func[info.targets].iter().enumerate() {
if index > 0 {
self.out.push_str(", ");
}
self.block_call(func, call);
}
self.out.push(']');
}
}
}
}
fn value_list_spaced(&mut self, args: &[Value]) {
if args.is_empty() {
return;
}
self.out.push(' ');
self.value_list(args);
}
fn value_list(&mut self, args: &[Value]) {
for (index, &arg) in args.iter().enumerate() {
if index > 0 {
self.out.push_str(", ");
}
self.value(arg);
}
}
fn block_call(&mut self, func: &Func, call: BlockCall) {
let _ = write!(self.out, "block{}", self.blocks[call.block.index()]);
let args = &func[call.args];
if !args.is_empty() {
self.out.push('(');
self.value_list(args);
self.out.push(')');
}
}
fn mem(&mut self, info: MemInfo) {
if info.size != 0 {
let _ = write!(self.out, ", size {}", info.size);
}
let _ = write!(self.out, ", align {}", info.align);
if info.order != MemOrder::NotAtomic {
let _ = write!(self.out, ", {}", info.order.name());
}
if let Some(tbaa) = info.tbaa {
let _ = write!(self.out, ", tbaa !{}", tbaa.index());
}
}
fn value(&mut self, value: Value) {
match self.values.get(value.index()).copied() {
Some(number) if number != u32::MAX => {
let _ = write!(self.out, "%{number}");
}
_ => self.out.push_str("%?"),
}
}
fn imm(&mut self, imm: Imm, ty: Type) {
let scalar = if ty.is_vector() { ty.lane() } else { ty };
if scalar.is_float() {
let _ = write!(self.out, "{:#x}", imm.bits());
} else if scalar.is_int() {
let _ = write!(self.out, "{}", imm.signed(scalar));
} else {
let _ = write!(self.out, "{:#x}", imm.bits());
}
}
fn meta_node(&mut self, meta: Meta) {
let node = self.module[meta];
let _ = write!(self.out, "!{} = tbaa ", meta.index());
self.string(self.names.resolve(node.name).as_bytes());
if let Some(parent) = node.parent {
let _ = write!(self.out, ", parent !{}", parent.index());
}
let _ = write!(self.out, ", offset {}", node.offset);
self.out.push('\n');
}
fn linkage(&mut self, linkage: Linkage, visibility: Visibility) {
let _ = write!(self.out, ", linkage({})", linkage.name());
if visibility != Visibility::Default {
let _ = write!(self.out, ", visibility({})", visibility.name());
}
}
fn section(&mut self, section: Option<Symbol>) {
if let Some(section) = section {
self.out.push_str(", section ");
self.string(self.names.resolve(section).as_bytes());
}
}
fn string(&mut self, bytes: &[u8]) {
self.out.push('"');
for &byte in bytes {
match byte {
b'"' => self.out.push_str("\\\""),
b'\\' => self.out.push_str("\\\\"),
0x20..=0x7e => self.out.push(byte as char),
_ => {
let _ = write!(self.out, "\\{byte:02x}");
}
}
}
self.out.push('"');
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::*;
use crate::func::Builder;
use crate::inst::{AsmInfo, CallInfo, MetaNode, SwitchInfo};
use crate::module::{AliasKind, TlsModel};
use crate::{AttrSet, Attrs, Flags, FloatPred, FpContract, IntPred, RmwOp};
fn target() -> TargetInfo {
TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
}
#[test]
fn the_example_in_the_spec() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("example.c"), &target());
let char_node = module.add_meta(MetaNode {
name: names.intern("omnipotent char"),
parent: None,
offset: 0,
});
let int_node = module.add_meta(MetaNode {
name: names.intern("int"),
parent: Some(char_node),
offset: 0,
});
let i32_ = Type::int(32);
let zero_bits = module.add_imm(Imm::int(0, i32_));
let init = module.push_data(&[Datum::Scalar { ty: i32_, value: zero_bits }]);
let mut counter = Global::new(names.intern("counter"), 4, 4);
counter.linkage = Linkage::Internal;
counter.init = Some(init);
module.add_global(counter);
let mut func = Func::new(
names.intern("sum"),
Signature::new().with_params(&[i32_]).with_returns(&[i32_]),
);
func.attrs = Attrs { set: AttrSet::NOUNWIND, fp_contract: FpContract::On };
let entry = func.create_block();
let n = func.append_param(entry, i32_);
let header = func.create_block();
let acc = func.append_param(header, i32_);
let i = func.append_param(header, i32_);
let exit = func.create_block();
let result = func.append_param(exit, i32_);
let mut b = Builder::new(&mut func, entry);
let zero = b.iconst(i32_, 0);
let cmp = b.icmp(IntPred::Sle, n, zero);
b.br_if(cmp, exit, &[zero], header, &[zero, zero]);
let mut b = Builder::new(&mut func, header);
let one = b.iconst(i32_, 1);
let next = b.binary(Opcode::Add, i, one, Flags::NSW);
let total = b.binary(Opcode::Add, acc, next, Flags::NSW);
let done = b.icmp(IntPred::Sge, next, n);
b.br_if(done, exit, &[total], header, &[total, next]);
let mut b = Builder::new(&mut func, exit);
let address = b.value(
InstData {
extra: Extra::Symbol(names.intern("counter")),
..InstData::new(Opcode::GlobalAddr)
},
Type::PTR,
);
b.store(
result,
address,
MemInfo { size: 0, align: 4, order: MemOrder::NotAtomic, tbaa: Some(int_node) },
Flags::NONE,
);
b.ret(&[result]);
module.add_func(func);
assert_eq!(print(&module, &names), crate::fixtures::EXAMPLE);
}
#[test]
fn one_of_almost_everything() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("zoo.c"), &target());
let int_node =
module.add_meta(MetaNode { name: names.intern("int"), parent: None, offset: 0 });
let i32_ = Type::int(32);
let i64_ = Type::int(64);
let f64_ = Type::float(crate::Float::F64);
let mut func = Func::new(
names.intern("zoo"),
Signature::new().with_params(&[i32_, Type::PTR]).with_returns(&[i32_]),
);
let entry = func.create_block();
let n = func.append_param(entry, i32_);
let p = func.append_param(entry, Type::PTR);
let middle = func.create_block();
let other = func.create_block();
let exit = func.create_block();
let taken = func.append_param(exit, i32_);
let mut b = Builder::new(&mut func, entry);
let minus_one = b.iconst(i64_, -1);
let half = b.fconst(f64_, 0x3ff8_0000_0000_0000);
let seven = b.func().add_imm(Imm::int(7, i32_));
let vector = b.value(
InstData { extra: Extra::Imm(seven), ..InstData::new(Opcode::Splat) },
Type::vector(i32_, 4),
);
let stack = b.func().add_mem(MemInfo {
size: 16,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
});
let slot = b.value(
InstData { extra: Extra::Mem(stack), ..InstData::new(Opcode::Alloca) },
Type::PTR,
);
let args = b.func().push_values(&[slot, minus_one]);
let addr = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
let plain = MemInfo { size: 0, align: 4, order: MemOrder::NotAtomic, tbaa: Some(int_node) };
let loaded = b.load(i32_, addr, plain, Flags::NONE);
b.store(loaded, addr, plain, Flags::VOLATILE);
let atomic =
b.func().add_mem(MemInfo { size: 0, align: 4, order: MemOrder::SeqCst, tbaa: None });
let args = b.func().push_values(&[addr, n]);
let old = b.value(
InstData {
args,
extra: Extra::Rmw(RmwOp::Add, atomic),
..InstData::new(Opcode::AtomicRmw)
},
i32_,
);
let args = b.func().push_values(&[addr, old, n]);
b.inst(
InstData { args, extra: Extra::Mem(atomic), ..InstData::new(Opcode::Cmpxchg) },
&[i32_, Type::I1],
);
b.inst(
InstData { extra: Extra::Order(MemOrder::SeqCst), ..InstData::new(Opcode::Fence) },
&[],
);
b.unary(Opcode::SExt, n, i64_);
b.fcmp(FloatPred::Oeq, half, half, Flags::NONE);
let args = b.func().push_values(&[n, n]);
b.inst(InstData { args, ..InstData::new(Opcode::SAddOverflow) }, &[i32_, Type::I1]);
let puts = b.func().add_signature(
Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]).variadic(),
);
b.call(names.intern("puts"), puts, &[p]);
let indirect =
b.func().add_signature(Signature::new().with_params(&[i32_]).with_returns(&[i32_]));
let info = b.func().add_call(CallInfo { callee: None, signature: indirect });
let args = b.func().push_values(&[p, n]);
b.value(
InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::CallIndirect) },
i32_,
);
let copy = b.func().add_mem(MemInfo {
size: 16,
align: 8,
order: MemOrder::NotAtomic,
tbaa: None,
});
let args = b.func().push_values(&[slot, p]);
b.inst(InstData { args, extra: Extra::Mem(copy), ..InstData::new(Opcode::Memcpy) }, &[]);
let asm = b.func().add_asm(AsmInfo {
template: names.intern("pause"),
constraints: names.intern(""),
clobbers: names.intern("memory"),
targets: crate::inst::BlockCallList::EMPTY,
});
b.inst(
InstData {
flags: Flags::VOLATILE,
extra: Extra::Asm(asm),
..InstData::new(Opcode::InlineAsm)
},
&[],
);
let args = b.func().push_values(&[vector]);
b.value(
InstData {
args,
extra: Extra::Symbol(names.intern("x86.sse2.pmovmskb")),
..InstData::new(Opcode::TargetIntrinsic)
},
i32_,
);
b.jump(middle, &[]);
let mut b = Builder::new(&mut func, middle);
let cases = b.func().push_imms(&[Imm::int(0, i32_), Imm::int(-1, i32_)]);
let default = BlockCall { block: other, args: crate::inst::ValueList::EMPTY };
let first = BlockCall { block: exit, args: b.func().push_values(&[n]) };
let second = BlockCall { block: other, args: crate::inst::ValueList::EMPTY };
let targets = b.func().push_block_calls(&[default, first, second]);
let switch = b.func().add_switch(SwitchInfo { targets, cases });
let args = b.func().push_values(&[n]);
b.inst(
InstData { args, extra: Extra::Switch(switch), ..InstData::new(Opcode::Switch) },
&[],
);
let mut b = Builder::new(&mut func, other);
let call = BlockCall { block: exit, args: b.func().push_values(&[n]) };
let targets = b.func().push_block_calls(&[call]);
let goto = b.func().add_asm(AsmInfo {
template: names.intern("jmp %l0"),
constraints: names.intern(""),
clobbers: names.intern(""),
targets,
});
b.inst(InstData { extra: Extra::Asm(goto), ..InstData::new(Opcode::InlineAsm) }, &[]);
let mut b = Builder::new(&mut func, exit);
b.ret(&[taken]);
module.add_func(func);
assert_eq!(print(&module, &names), crate::fixtures::ZOO);
}
#[test]
fn the_shapes_a_symbol_comes_in() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("data.c"), &target());
let i32_ = Type::int(32);
let text = module.push_bytes(b"hi\x00\xff\"\\");
let entry_name = names.intern("hi.str");
let forward = module.add_reloc(Reloc { symbol: entry_name, addend: 8, size: 8 });
let backward = module.add_reloc(Reloc { symbol: entry_name, addend: -8, size: 8 });
let seven = module.add_imm(Imm::int(7, i32_));
let image = module.push_data(&[
Datum::Bytes(text),
Datum::Zero(2),
Datum::Scalar { ty: i32_, value: seven },
Datum::Addr(forward),
Datum::Addr(backward),
]);
let mut table = Global::new(names.intern("table"), 28, 8);
table.init = Some(image);
table.constant = true;
table.section = Some(names.intern(".rodata.rel"));
module.add_global(table);
let mut errno = Global::new(names.intern("errno"), 4, 4);
errno.tls = Some(TlsModel::InitialExec);
errno.visibility = Visibility::Hidden;
module.add_global(errno);
let mut alias = Alias::new(names.intern("total"), names.intern("table"));
alias.linkage = Linkage::Weak;
module.add_alias(alias);
let mut memcpy = Alias::new(names.intern("memcpy"), names.intern("memcpy.resolve"));
memcpy.kind = AliasKind::IFunc;
memcpy.visibility = Visibility::Protected;
module.add_alias(memcpy);
let mut puts = Func::new(
names.intern("puts"),
Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
);
puts.linkage = Linkage::External;
puts.attrs.set = AttrSet::NOUNWIND | AttrSet::WILLRETURN;
module.add_func(puts);
let mut helper =
Func::new(names.intern("helper"), Signature::new().with_returns(&[i32_, i32_]));
helper.linkage = Linkage::Internal;
helper.section = Some(names.intern(".text.hot"));
helper.attrs.set = AttrSet::READNONE | AttrSet::ALWAYS_INLINE;
let block = helper.create_block();
let mut b = Builder::new(&mut helper, block);
let one = b.iconst(i32_, 1);
b.ret(&[one, one]);
module.add_func(helper);
assert_eq!(print(&module, &names), crate::fixtures::SYMBOLS);
}
#[test]
fn numbering_follows_the_text_and_not_the_tables() {
let mut names = Interner::new();
let mut module = Module::new(names.intern("order.c"), &target());
let i32_ = Type::int(32);
let mut func = Func::new(names.intern("f"), Signature::new().with_returns(&[i32_]));
let entry = func.create_block();
let middle = func.create_block();
let exit = func.create_block();
let arrived = func.append_param(exit, i32_);
let mut b = Builder::new(&mut func, exit);
b.ret(&[arrived]);
let mut b = Builder::new(&mut func, middle);
let two = b.iconst(i32_, 2);
b.jump(exit, &[two]);
let mut b = Builder::new(&mut func, entry);
b.jump(middle, &[]);
module.add_func(func);
assert_eq!(
print_func(&module, &module[module.funcs().next().unwrap()], &names),
"\
func @f() -> i32, linkage(external) {
block0:
jump block1
block1:
%0 = iconst.i32 2
jump block2(%0)
block2(%1: i32):
return %1
}
"
);
}
}