1use crate::entity::SecondaryMap;
7use crate::ir::entities::AnyEntity;
8use crate::ir::immediates::Ieee128;
9use crate::ir::pcc::Fact;
10use crate::ir::{Block, DataFlowGraph, Function, Inst, Opcode, SigRef, Type, Value, ValueDef};
11use crate::packed_option::ReservedValue;
12use alloc::string::{String, ToString};
13use alloc::vec::Vec;
14use core::fmt::{self, Write};
15
16pub trait FuncWriter {
18 fn write_block_header(
20 &mut self,
21 w: &mut dyn Write,
22 func: &Function,
23 block: Block,
24 indent: usize,
25 ) -> fmt::Result;
26
27 fn write_instruction(
29 &mut self,
30 w: &mut dyn Write,
31 func: &Function,
32 aliases: &SecondaryMap<Value, Vec<Value>>,
33 inst: Inst,
34 indent: usize,
35 ) -> fmt::Result;
36
37 fn write_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> {
39 self.super_preamble(w, func)
40 }
41
42 fn super_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> {
44 let mut any = false;
45
46 for (ss, slot) in func.dynamic_stack_slots.iter() {
47 any = true;
48 self.write_entity_definition(w, func, ss.into(), slot, None)?;
49 }
50
51 for (ss, slot) in func.sized_stack_slots.iter() {
52 any = true;
53 self.write_entity_definition(w, func, ss.into(), slot, None)?;
54 }
55
56 for (gv, gv_data) in &func.global_values {
57 any = true;
58 let maybe_fact = func.global_value_facts[gv].as_ref();
59 self.write_entity_definition(w, func, gv.into(), gv_data, maybe_fact)?;
60 }
61
62 for (mt, mt_data) in &func.memory_types {
63 any = true;
64 self.write_entity_definition(w, func, mt.into(), mt_data, None)?;
65 }
66
67 for (sig, sig_data) in &func.dfg.signatures {
70 any = true;
71 self.write_entity_definition(w, func, sig.into(), &sig_data, None)?;
72 }
73
74 for (fnref, ext_func) in &func.dfg.ext_funcs {
75 if ext_func.signature != SigRef::reserved_value() {
76 any = true;
77 self.write_entity_definition(
78 w,
79 func,
80 fnref.into(),
81 &ext_func.display(Some(&func.params)),
82 None,
83 )?;
84 }
85 }
86
87 for (&cref, cval) in func.dfg.constants.iter() {
88 any = true;
89 self.write_entity_definition(w, func, cref.into(), cval, None)?;
90 }
91
92 if let Some(limit) = func.stack_limit {
93 any = true;
94 self.write_entity_definition(w, func, AnyEntity::StackLimit, &limit, None)?;
95 }
96
97 Ok(any)
98 }
99
100 fn write_entity_definition(
102 &mut self,
103 w: &mut dyn Write,
104 func: &Function,
105 entity: AnyEntity,
106 value: &dyn fmt::Display,
107 maybe_fact: Option<&Fact>,
108 ) -> fmt::Result {
109 self.super_entity_definition(w, func, entity, value, maybe_fact)
110 }
111
112 fn super_entity_definition(
114 &mut self,
115 w: &mut dyn Write,
116 _func: &Function,
117 entity: AnyEntity,
118 value: &dyn fmt::Display,
119 maybe_fact: Option<&Fact>,
120 ) -> fmt::Result {
121 if let Some(fact) = maybe_fact {
122 writeln!(w, " {entity} ! {fact} = {value}")
123 } else {
124 writeln!(w, " {entity} = {value}")
125 }
126 }
127}
128
129pub struct PlainWriter;
131
132impl FuncWriter for PlainWriter {
133 fn write_instruction(
134 &mut self,
135 w: &mut dyn Write,
136 func: &Function,
137 aliases: &SecondaryMap<Value, Vec<Value>>,
138 inst: Inst,
139 indent: usize,
140 ) -> fmt::Result {
141 write_instruction(w, func, aliases, inst, indent)
142 }
143
144 fn write_block_header(
145 &mut self,
146 w: &mut dyn Write,
147 func: &Function,
148 block: Block,
149 indent: usize,
150 ) -> fmt::Result {
151 write_block_header(w, func, block, indent)
152 }
153}
154
155pub fn write_function(w: &mut dyn Write, func: &Function) -> fmt::Result {
158 decorate_function(&mut PlainWriter, w, func)
159}
160
161fn alias_map(func: &Function) -> SecondaryMap<Value, Vec<Value>> {
163 let mut aliases = SecondaryMap::<_, Vec<_>>::new();
164 for v in func.dfg.values() {
165 if let Some(k) = func.dfg.value_alias_dest_for_serialization(v) {
167 aliases[k].push(v);
168 }
169 }
170 aliases
171}
172
173pub fn decorate_function<FW: FuncWriter>(
177 func_w: &mut FW,
178 w: &mut dyn Write,
179 func: &Function,
180) -> fmt::Result {
181 write!(w, "function ")?;
182 write_function_spec(w, func)?;
183 writeln!(w, " {{")?;
184 let aliases = alias_map(func);
185 let mut any = func_w.write_preamble(w, func)?;
186 for block in &func.layout {
187 if any {
188 writeln!(w)?;
189 }
190 decorate_block(func_w, w, func, &aliases, block)?;
191 any = true;
192 }
193 writeln!(w, "}}")
194}
195
196pub fn write_function_spec(w: &mut dyn Write, func: &Function) -> fmt::Result {
202 write!(w, "{}{}", func.name, func.signature)
203}
204
205fn write_arg(w: &mut dyn Write, func: &Function, arg: Value) -> fmt::Result {
210 let ty = func.dfg.value_type(arg);
211 if let Some(f) = &func.dfg.facts[arg] {
212 write!(w, "{arg} ! {f}: {ty}")
213 } else {
214 write!(w, "{arg}: {ty}")
215 }
216}
217
218pub fn write_block_header(
225 w: &mut dyn Write,
226 func: &Function,
227 block: Block,
228 indent: usize,
229) -> fmt::Result {
230 let cold = if func.layout.is_cold(block) {
231 " cold"
232 } else {
233 ""
234 };
235
236 write!(w, "{1:0$}{2}", indent - 4, "", block)?;
238
239 let mut args = func.dfg.block_params(block).iter().cloned();
240 match args.next() {
241 None => return writeln!(w, "{cold}:"),
242 Some(arg) => {
243 write!(w, "(")?;
244 write_arg(w, func, arg)?;
245 }
246 }
247 for arg in args {
249 write!(w, ", ")?;
250 write_arg(w, func, arg)?;
251 }
252 writeln!(w, "){cold}:")
253}
254
255fn decorate_block<FW: FuncWriter>(
256 func_w: &mut FW,
257 w: &mut dyn Write,
258 func: &Function,
259 aliases: &SecondaryMap<Value, Vec<Value>>,
260 block: Block,
261) -> fmt::Result {
262 let indent = if func.rel_srclocs().is_empty() && func.debug_tags.is_empty() {
264 4
265 } else {
266 36
267 };
268
269 func_w.write_block_header(w, func, block, indent)?;
270 for a in func.dfg.block_params(block).iter().cloned() {
271 write_value_aliases(w, aliases, a, indent)?;
272 }
273
274 for inst in func.layout.block_insts(block) {
275 func_w.write_instruction(w, func, aliases, inst, indent)?;
276 }
277
278 Ok(())
279}
280
281fn type_suffix(func: &Function, inst: Inst) -> Option<Type> {
291 let inst_data = &func.dfg.insts[inst];
292 let constraints = inst_data.opcode().constraints();
293
294 if !constraints.is_polymorphic() {
295 return None;
296 }
297
298 if constraints.use_typevar_operand() {
301 let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap();
302 let def_block = match func.dfg.value_def(ctrl_var) {
303 ValueDef::Result(instr, _) => func.layout.inst_block(instr),
304 ValueDef::Param(block, _) => Some(block),
305 ValueDef::Union(..) => None,
306 };
307 if def_block.is_some() && def_block == func.layout.inst_block(inst) {
308 return None;
309 }
310 }
311
312 let rtype = func.dfg.ctrl_typevar(inst);
313 assert!(
314 !rtype.is_invalid(),
315 "Polymorphic instruction must produce a result"
316 );
317 Some(rtype)
318}
319
320fn write_value_aliases(
322 w: &mut dyn Write,
323 aliases: &SecondaryMap<Value, Vec<Value>>,
324 target: Value,
325 indent: usize,
326) -> fmt::Result {
327 let mut todo_stack = vec![target];
328 while let Some(target) = todo_stack.pop() {
329 for &a in &aliases[target] {
330 writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?;
331 todo_stack.push(a);
332 }
333 }
334
335 Ok(())
336}
337
338fn write_instruction(
339 w: &mut dyn Write,
340 func: &Function,
341 aliases: &SecondaryMap<Value, Vec<Value>>,
342 inst: Inst,
343 mut indent: usize,
344) -> fmt::Result {
345 let mut s = String::with_capacity(16);
347
348 let srcloc = func.srcloc(inst);
350 if !srcloc.is_default() {
351 write!(s, "{srcloc} ")?;
352 }
353
354 write_debug_tags(w, &func, inst, &mut indent)?;
356
357 write!(w, "{s:indent$}")?;
359
360 let mut has_results = false;
362 for r in func.dfg.inst_results(inst) {
363 if !has_results {
364 has_results = true;
365 write!(w, "{r}")?;
366 } else {
367 write!(w, ", {r}")?;
368 }
369 if let Some(f) = &func.dfg.facts[*r] {
370 write!(w, " ! {f}")?;
371 }
372 }
373 if has_results {
374 write!(w, " = ")?;
375 }
376
377 let opcode = func.dfg.insts[inst].opcode();
379
380 match type_suffix(func, inst) {
381 Some(suf) => write!(w, "{opcode}.{suf}")?,
382 None => write!(w, "{opcode}")?,
383 }
384
385 write_operands(w, &func.dfg, inst)?;
386 writeln!(w)?;
387
388 for r in func.dfg.inst_results(inst) {
390 write_value_aliases(w, aliases, *r, indent)?;
391 }
392 Ok(())
393}
394
395pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {
397 let pool = &dfg.value_lists;
398 let jump_tables = &dfg.jump_tables;
399 let exception_tables = &dfg.exception_tables;
400 use crate::ir::instructions::InstructionData::*;
401 let ctrl_ty = dfg.ctrl_typevar(inst);
402 match dfg.insts[inst] {
403 AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]),
404 AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
405 LoadNoOffset { flags, arg, .. } => write!(w, "{flags} {arg}"),
406 StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]),
407 Unary { arg, .. } => write!(w, " {arg}"),
408 UnaryImm { imm, .. } => write!(w, " {}", {
409 let mut imm = imm;
410 if ctrl_ty.bits() != 0 {
411 imm = imm.sign_extend_from_width(ctrl_ty.bits());
412 }
413 imm
414 }),
415 UnaryIeee16 { imm, .. } => write!(w, " {imm}"),
416 UnaryIeee32 { imm, .. } => write!(w, " {imm}"),
417 UnaryIeee64 { imm, .. } => write!(w, " {imm}"),
418 UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"),
419 UnaryConst {
420 constant_handle, ..
421 } => write!(w, " {constant_handle}"),
422 Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),
423 BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"),
424 BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, {
425 let mut imm = imm;
426 if ctrl_ty.bits() != 0 {
427 imm = imm.sign_extend_from_width(ctrl_ty.bits());
428 }
429 imm
430 }),
431 Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
432 MultiAry { ref args, .. } => {
433 if args.is_empty() {
434 write!(w, "")
435 } else {
436 write!(w, " {}", DisplayValues(args.as_slice(pool)))
437 }
438 }
439 NullAry { .. } => write!(w, " "),
440 TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm),
441 Shuffle { imm, args, .. } => {
442 let data = dfg.immediates.get(imm).expect(
443 "Expected the shuffle mask to already be inserted into the immediates table",
444 );
445 write!(w, " {}, {}, {}", args[0], args[1], data)
446 }
447 IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
448 IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, {
449 let mut imm = imm;
450 if ctrl_ty.bits() != 0 {
451 imm = imm.sign_extend_from_width(ctrl_ty.bits());
452 }
453 imm
454 }),
455 IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code),
456 FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
457 Jump { destination, .. } => {
458 write!(w, " {}", destination.display(pool))
459 }
460 Brif {
461 arg,
462 blocks: [block_then, block_else],
463 ..
464 } => {
465 write!(w, " {}, {}", arg, block_then.display(pool))?;
466 write!(w, ", {}", block_else.display(pool))
467 }
468 BranchTable { arg, table, .. } => {
469 write!(w, " {}, {}", arg, jump_tables[table].display(pool))
470 }
471 Call {
472 func_ref, ref args, ..
473 } => {
474 write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?;
475 write_user_stack_map_entries(w, dfg, inst)
476 }
477 CallIndirect {
478 sig_ref, ref args, ..
479 } => {
480 let args = args.as_slice(pool);
481 write!(
482 w,
483 " {}, {}({})",
484 sig_ref,
485 args[0],
486 DisplayValues(&args[1..])
487 )?;
488 write_user_stack_map_entries(w, dfg, inst)
489 }
490 TryCall {
491 func_ref,
492 ref args,
493 exception,
494 ..
495 } => {
496 write!(
497 w,
498 " {}({}), {}",
499 func_ref,
500 DisplayValues(args.as_slice(pool)),
501 exception_tables[exception].display(pool),
502 )
503 }
504 TryCallIndirect {
505 ref args,
506 exception,
507 ..
508 } => {
509 let args = args.as_slice(pool);
510 write!(
511 w,
512 " {}({}), {}",
513 args[0],
514 DisplayValues(&args[1..]),
515 exception_tables[exception].display(pool),
516 )
517 }
518 FuncAddr { func_ref, .. } => write!(w, " {func_ref}"),
519 StackLoad {
520 stack_slot, offset, ..
521 } => write!(w, " {stack_slot}{offset}"),
522 StackStore {
523 arg,
524 stack_slot,
525 offset,
526 ..
527 } => write!(w, " {arg}, {stack_slot}{offset}"),
528 DynamicStackLoad {
529 dynamic_stack_slot, ..
530 } => write!(w, " {dynamic_stack_slot}"),
531 DynamicStackStore {
532 arg,
533 dynamic_stack_slot,
534 ..
535 } => write!(w, " {arg}, {dynamic_stack_slot}"),
536 Load {
537 flags, arg, offset, ..
538 } => write!(w, "{flags} {arg}{offset}"),
539 Store {
540 flags,
541 args,
542 offset,
543 ..
544 } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset),
545 Trap { code, .. } => write!(w, " {code}"),
546 CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"),
547 ExceptionHandlerAddress { block, imm, .. } => write!(w, " {block}, {imm}"),
548 }?;
549
550 let mut sep = " ; ";
551 for arg in dfg.inst_values(inst) {
552 if let ValueDef::Result(src, _) = dfg.value_def(arg) {
553 let imm = match dfg.insts[src] {
554 UnaryImm { imm, .. } => {
555 let mut imm = imm;
556 if dfg.ctrl_typevar(src).bits() != 0 {
557 imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits());
558 }
559 imm.to_string()
560 }
561 UnaryIeee16 { imm, .. } => imm.to_string(),
562 UnaryIeee32 { imm, .. } => imm.to_string(),
563 UnaryIeee64 { imm, .. } => imm.to_string(),
564 UnaryConst {
565 constant_handle,
566 opcode: Opcode::F128const,
567 } => Ieee128::try_from(dfg.constants.get(constant_handle))
568 .expect("16-byte f128 constant")
569 .to_string(),
570 UnaryConst {
571 constant_handle, ..
572 } => constant_handle.to_string(),
573 _ => continue,
574 };
575 write!(w, "{sep}{arg} = {imm}")?;
576 sep = ", ";
577 }
578 }
579 Ok(())
580}
581
582fn write_debug_tags(
583 w: &mut dyn Write,
584 func: &Function,
585 inst: Inst,
586 indent: &mut usize,
587) -> fmt::Result {
588 let tags = func.debug_tags.get(inst);
589 if !tags.is_empty() {
590 let tags = tags
591 .iter()
592 .map(|tag| format!("{tag}"))
593 .collect::<Vec<_>>()
594 .join(", ");
595 let s = format!("<{tags}> ");
596 write!(w, "{s}")?;
597 *indent = indent.saturating_sub(s.len());
598 }
599 Ok(())
600}
601
602fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {
603 let entries = match dfg.user_stack_map_entries(inst) {
604 None => return Ok(()),
605 Some(es) => es,
606 };
607 write!(w, ", stack_map=[")?;
608 let mut need_comma = false;
609 for entry in entries {
610 if need_comma {
611 write!(w, ", ")?;
612 }
613 write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?;
614 need_comma = true;
615 }
616 write!(w, "]")?;
617 Ok(())
618}
619
620struct DisplayValues<'a>(&'a [Value]);
622
623impl<'a> fmt::Display for DisplayValues<'a> {
624 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
625 for (i, val) in self.0.iter().enumerate() {
626 if i == 0 {
627 write!(f, "{val}")?;
628 } else {
629 write!(f, ", {val}")?;
630 }
631 }
632 Ok(())
633 }
634}
635
636#[cfg(test)]
637mod tests {
638 use crate::cursor::{Cursor, CursorPosition, FuncCursor};
639 use crate::ir::types;
640 use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName};
641 use alloc::string::ToString;
642
643 #[test]
644 fn basic() {
645 let mut f = Function::new();
646 assert_eq!(f.to_string(), "function u0:0() fast {\n}\n");
647
648 f.name = UserFuncName::testcase("foo");
649 assert_eq!(f.to_string(), "function %foo() fast {\n}\n");
650
651 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0));
652 assert_eq!(
653 f.to_string(),
654 "function %foo() fast {\n ss0 = explicit_slot 4\n}\n"
655 );
656
657 let block = f.dfg.make_block();
658 f.layout.append_block(block);
659 assert_eq!(
660 f.to_string(),
661 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n"
662 );
663
664 f.dfg.append_block_param(block, types::I8);
665 assert_eq!(
666 f.to_string(),
667 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n"
668 );
669
670 f.dfg.append_block_param(block, types::F32.by(4).unwrap());
671 assert_eq!(
672 f.to_string(),
673 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n"
674 );
675
676 {
677 let mut cursor = FuncCursor::new(&mut f);
678 cursor.set_position(CursorPosition::After(block));
679 cursor.ins().return_(&[])
680 };
681 assert_eq!(
682 f.to_string(),
683 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n"
684 );
685
686 let mut f = Function::new();
687 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2));
688 assert_eq!(
689 f.to_string(),
690 "function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n"
691 );
692 }
693
694 #[test]
695 fn aliases() {
696 use crate::ir::InstBuilder;
697
698 let mut func = Function::new();
699 {
700 let block0 = func.dfg.make_block();
701 let mut pos = FuncCursor::new(&mut func);
702 pos.insert_block(block0);
703
704 let v0 = pos.func.dfg.append_block_param(block0, types::I32);
706 let v1 = pos.func.dfg.append_block_param(block0, types::I32);
707 let v2 = pos.func.dfg.append_block_param(block0, types::I32);
708 pos.func.dfg.detach_block_params(block0);
709
710 let v3 = pos.func.dfg.append_block_param(block0, types::I32);
712 pos.func.dfg.change_to_alias(v0, v3);
713
714 pos.func.dfg.make_value_alias_for_serialization(v0, v2); let _dummy0 = pos.ins().iconst(types::I32, 42);
719 let v4 = pos.ins().iadd(v0, v0);
720 pos.func.dfg.change_to_alias(v1, v4);
721 let _dummy1 = pos.ins().iconst(types::I32, 23);
722 let _v7 = pos.ins().iadd(v1, v1);
723 }
724 assert_eq!(
725 func.to_string(),
726 "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"
727 );
728 }
729
730 #[test]
731 fn cold_blocks() {
732 let mut func = Function::new();
733 {
734 let mut pos = FuncCursor::new(&mut func);
735
736 let block0 = pos.func.dfg.make_block();
737 pos.insert_block(block0);
738 pos.func.layout.set_cold(block0);
739
740 let block1 = pos.func.dfg.make_block();
741 pos.insert_block(block1);
742 pos.func.dfg.append_block_param(block1, types::I32);
743 pos.func.layout.set_cold(block1);
744 }
745
746 assert_eq!(
747 func.to_string(),
748 "function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n"
749 );
750 }
751}