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 #[allow(unused_variables)]
114 fn super_entity_definition(
115 &mut self,
116 w: &mut dyn Write,
117 func: &Function,
118 entity: AnyEntity,
119 value: &dyn fmt::Display,
120 maybe_fact: Option<&Fact>,
121 ) -> fmt::Result {
122 if let Some(fact) = maybe_fact {
123 writeln!(w, " {entity} ! {fact} = {value}")
124 } else {
125 writeln!(w, " {entity} = {value}")
126 }
127 }
128}
129
130pub struct PlainWriter;
132
133impl FuncWriter for PlainWriter {
134 fn write_instruction(
135 &mut self,
136 w: &mut dyn Write,
137 func: &Function,
138 aliases: &SecondaryMap<Value, Vec<Value>>,
139 inst: Inst,
140 indent: usize,
141 ) -> fmt::Result {
142 write_instruction(w, func, aliases, inst, indent)
143 }
144
145 fn write_block_header(
146 &mut self,
147 w: &mut dyn Write,
148 func: &Function,
149 block: Block,
150 indent: usize,
151 ) -> fmt::Result {
152 write_block_header(w, func, block, indent)
153 }
154}
155
156pub fn write_function(w: &mut dyn Write, func: &Function) -> fmt::Result {
159 decorate_function(&mut PlainWriter, w, func)
160}
161
162fn alias_map(func: &Function) -> SecondaryMap<Value, Vec<Value>> {
164 let mut aliases = SecondaryMap::<_, Vec<_>>::new();
165 for v in func.dfg.values() {
166 if let Some(k) = func.dfg.value_alias_dest_for_serialization(v) {
168 aliases[k].push(v);
169 }
170 }
171 aliases
172}
173
174pub fn decorate_function<FW: FuncWriter>(
178 func_w: &mut FW,
179 w: &mut dyn Write,
180 func: &Function,
181) -> fmt::Result {
182 write!(w, "function ")?;
183 write_function_spec(w, func)?;
184 writeln!(w, " {{")?;
185 let aliases = alias_map(func);
186 let mut any = func_w.write_preamble(w, func)?;
187 for block in &func.layout {
188 if any {
189 writeln!(w)?;
190 }
191 decorate_block(func_w, w, func, &aliases, block)?;
192 any = true;
193 }
194 writeln!(w, "}}")
195}
196
197pub fn write_function_spec(w: &mut dyn Write, func: &Function) -> fmt::Result {
203 write!(w, "{}{}", func.name, func.signature)
204}
205
206fn write_arg(w: &mut dyn Write, func: &Function, arg: Value) -> fmt::Result {
211 let ty = func.dfg.value_type(arg);
212 if let Some(f) = &func.dfg.facts[arg] {
213 write!(w, "{arg} ! {f}: {ty}")
214 } else {
215 write!(w, "{arg}: {ty}")
216 }
217}
218
219pub fn write_block_header(
226 w: &mut dyn Write,
227 func: &Function,
228 block: Block,
229 indent: usize,
230) -> fmt::Result {
231 let cold = if func.layout.is_cold(block) {
232 " cold"
233 } else {
234 ""
235 };
236
237 write!(w, "{1:0$}{2}", indent - 4, "", block)?;
239
240 let mut args = func.dfg.block_params(block).iter().cloned();
241 match args.next() {
242 None => return writeln!(w, "{cold}:"),
243 Some(arg) => {
244 write!(w, "(")?;
245 write_arg(w, func, arg)?;
246 }
247 }
248 for arg in args {
250 write!(w, ", ")?;
251 write_arg(w, func, arg)?;
252 }
253 writeln!(w, "){cold}:")
254}
255
256fn decorate_block<FW: FuncWriter>(
257 func_w: &mut FW,
258 w: &mut dyn Write,
259 func: &Function,
260 aliases: &SecondaryMap<Value, Vec<Value>>,
261 block: Block,
262) -> fmt::Result {
263 let indent = if func.rel_srclocs().is_empty() { 4 } else { 36 };
265
266 func_w.write_block_header(w, func, block, indent)?;
267 for a in func.dfg.block_params(block).iter().cloned() {
268 write_value_aliases(w, aliases, a, indent)?;
269 }
270
271 for inst in func.layout.block_insts(block) {
272 func_w.write_instruction(w, func, aliases, inst, indent)?;
273 }
274
275 Ok(())
276}
277
278fn type_suffix(func: &Function, inst: Inst) -> Option<Type> {
288 let inst_data = &func.dfg.insts[inst];
289 let constraints = inst_data.opcode().constraints();
290
291 if !constraints.is_polymorphic() {
292 return None;
293 }
294
295 if constraints.use_typevar_operand() {
298 let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap();
299 let def_block = match func.dfg.value_def(ctrl_var) {
300 ValueDef::Result(instr, _) => func.layout.inst_block(instr),
301 ValueDef::Param(block, _) => Some(block),
302 ValueDef::Union(..) => None,
303 };
304 if def_block.is_some() && def_block == func.layout.inst_block(inst) {
305 return None;
306 }
307 }
308
309 let rtype = func.dfg.ctrl_typevar(inst);
310 assert!(
311 !rtype.is_invalid(),
312 "Polymorphic instruction must produce a result"
313 );
314 Some(rtype)
315}
316
317fn write_value_aliases(
319 w: &mut dyn Write,
320 aliases: &SecondaryMap<Value, Vec<Value>>,
321 target: Value,
322 indent: usize,
323) -> fmt::Result {
324 let mut todo_stack = vec![target];
325 while let Some(target) = todo_stack.pop() {
326 for &a in &aliases[target] {
327 writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?;
328 todo_stack.push(a);
329 }
330 }
331
332 Ok(())
333}
334
335fn write_instruction(
336 w: &mut dyn Write,
337 func: &Function,
338 aliases: &SecondaryMap<Value, Vec<Value>>,
339 inst: Inst,
340 indent: usize,
341) -> fmt::Result {
342 let mut s = String::with_capacity(16);
344
345 let srcloc = func.srcloc(inst);
347 if !srcloc.is_default() {
348 write!(s, "{srcloc} ")?;
349 }
350
351 write!(w, "{s:indent$}")?;
353
354 let mut has_results = false;
356 for r in func.dfg.inst_results(inst) {
357 if !has_results {
358 has_results = true;
359 write!(w, "{r}")?;
360 } else {
361 write!(w, ", {r}")?;
362 }
363 if let Some(f) = &func.dfg.facts[*r] {
364 write!(w, " ! {f}")?;
365 }
366 }
367 if has_results {
368 write!(w, " = ")?;
369 }
370
371 let opcode = func.dfg.insts[inst].opcode();
373
374 match type_suffix(func, inst) {
375 Some(suf) => write!(w, "{opcode}.{suf}")?,
376 None => write!(w, "{opcode}")?,
377 }
378
379 write_operands(w, &func.dfg, inst)?;
380 writeln!(w)?;
381
382 for r in func.dfg.inst_results(inst) {
384 write_value_aliases(w, aliases, *r, indent)?;
385 }
386 Ok(())
387}
388
389pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {
391 let pool = &dfg.value_lists;
392 let jump_tables = &dfg.jump_tables;
393 let exception_tables = &dfg.exception_tables;
394 use crate::ir::instructions::InstructionData::*;
395 let ctrl_ty = dfg.ctrl_typevar(inst);
396 match dfg.insts[inst] {
397 AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]),
398 AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
399 LoadNoOffset { flags, arg, .. } => write!(w, "{flags} {arg}"),
400 StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]),
401 Unary { arg, .. } => write!(w, " {arg}"),
402 UnaryImm { imm, .. } => write!(w, " {}", {
403 let mut imm = imm;
404 if ctrl_ty.bits() != 0 {
405 imm = imm.sign_extend_from_width(ctrl_ty.bits());
406 }
407 imm
408 }),
409 UnaryIeee16 { imm, .. } => write!(w, " {imm}"),
410 UnaryIeee32 { imm, .. } => write!(w, " {imm}"),
411 UnaryIeee64 { imm, .. } => write!(w, " {imm}"),
412 UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"),
413 UnaryConst {
414 constant_handle, ..
415 } => write!(w, " {constant_handle}"),
416 Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),
417 BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"),
418 BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, {
419 let mut imm = imm;
420 if ctrl_ty.bits() != 0 {
421 imm = imm.sign_extend_from_width(ctrl_ty.bits());
422 }
423 imm
424 }),
425 Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
426 MultiAry { ref args, .. } => {
427 if args.is_empty() {
428 write!(w, "")
429 } else {
430 write!(w, " {}", DisplayValues(args.as_slice(pool)))
431 }
432 }
433 NullAry { .. } => write!(w, " "),
434 TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm),
435 Shuffle { imm, args, .. } => {
436 let data = dfg.immediates.get(imm).expect(
437 "Expected the shuffle mask to already be inserted into the immediates table",
438 );
439 write!(w, " {}, {}, {}", args[0], args[1], data)
440 }
441 IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
442 IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, {
443 let mut imm = imm;
444 if ctrl_ty.bits() != 0 {
445 imm = imm.sign_extend_from_width(ctrl_ty.bits());
446 }
447 imm
448 }),
449 IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code),
450 FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
451 Jump { destination, .. } => {
452 write!(w, " {}", destination.display(pool))
453 }
454 Brif {
455 arg,
456 blocks: [block_then, block_else],
457 ..
458 } => {
459 write!(w, " {}, {}", arg, block_then.display(pool))?;
460 write!(w, ", {}", block_else.display(pool))
461 }
462 BranchTable { arg, table, .. } => {
463 write!(w, " {}, {}", arg, jump_tables[table].display(pool))
464 }
465 Call {
466 func_ref, ref args, ..
467 } => {
468 write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?;
469 write_user_stack_map_entries(w, dfg, inst)
470 }
471 CallIndirect {
472 sig_ref, ref args, ..
473 } => {
474 let args = args.as_slice(pool);
475 write!(
476 w,
477 " {}, {}({})",
478 sig_ref,
479 args[0],
480 DisplayValues(&args[1..])
481 )?;
482 write_user_stack_map_entries(w, dfg, inst)
483 }
484 TryCall {
485 func_ref,
486 ref args,
487 exception,
488 ..
489 } => {
490 write!(
491 w,
492 " {}({}), {}",
493 func_ref,
494 DisplayValues(args.as_slice(pool)),
495 exception_tables[exception].display(pool),
496 )
497 }
498 TryCallIndirect {
499 ref args,
500 exception,
501 ..
502 } => {
503 let args = args.as_slice(pool);
504 write!(
505 w,
506 " {}({}), {}",
507 args[0],
508 DisplayValues(&args[1..]),
509 exception_tables[exception].display(pool),
510 )
511 }
512 FuncAddr { func_ref, .. } => write!(w, " {func_ref}"),
513 StackLoad {
514 stack_slot, offset, ..
515 } => write!(w, " {stack_slot}{offset}"),
516 StackStore {
517 arg,
518 stack_slot,
519 offset,
520 ..
521 } => write!(w, " {arg}, {stack_slot}{offset}"),
522 DynamicStackLoad {
523 dynamic_stack_slot, ..
524 } => write!(w, " {dynamic_stack_slot}"),
525 DynamicStackStore {
526 arg,
527 dynamic_stack_slot,
528 ..
529 } => write!(w, " {arg}, {dynamic_stack_slot}"),
530 Load {
531 flags, arg, offset, ..
532 } => write!(w, "{flags} {arg}{offset}"),
533 Store {
534 flags,
535 args,
536 offset,
537 ..
538 } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset),
539 Trap { code, .. } => write!(w, " {code}"),
540 CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"),
541 }?;
542
543 let mut sep = " ; ";
544 for arg in dfg.inst_values(inst) {
545 if let ValueDef::Result(src, _) = dfg.value_def(arg) {
546 let imm = match dfg.insts[src] {
547 UnaryImm { imm, .. } => {
548 let mut imm = imm;
549 if dfg.ctrl_typevar(src).bits() != 0 {
550 imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits());
551 }
552 imm.to_string()
553 }
554 UnaryIeee16 { imm, .. } => imm.to_string(),
555 UnaryIeee32 { imm, .. } => imm.to_string(),
556 UnaryIeee64 { imm, .. } => imm.to_string(),
557 UnaryConst {
558 constant_handle,
559 opcode: Opcode::F128const,
560 } => Ieee128::try_from(dfg.constants.get(constant_handle))
561 .expect("16-byte f128 constant")
562 .to_string(),
563 UnaryConst {
564 constant_handle, ..
565 } => constant_handle.to_string(),
566 _ => continue,
567 };
568 write!(w, "{sep}{arg} = {imm}")?;
569 sep = ", ";
570 }
571 }
572 Ok(())
573}
574
575fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {
576 let entries = match dfg.user_stack_map_entries(inst) {
577 None => return Ok(()),
578 Some(es) => es,
579 };
580 write!(w, ", stack_map=[")?;
581 let mut need_comma = false;
582 for entry in entries {
583 if need_comma {
584 write!(w, ", ")?;
585 }
586 write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?;
587 need_comma = true;
588 }
589 write!(w, "]")?;
590 Ok(())
591}
592
593struct DisplayValues<'a>(&'a [Value]);
595
596impl<'a> fmt::Display for DisplayValues<'a> {
597 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
598 for (i, val) in self.0.iter().enumerate() {
599 if i == 0 {
600 write!(f, "{val}")?;
601 } else {
602 write!(f, ", {val}")?;
603 }
604 }
605 Ok(())
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use crate::cursor::{Cursor, CursorPosition, FuncCursor};
612 use crate::ir::types;
613 use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName};
614 use alloc::string::ToString;
615
616 #[test]
617 fn basic() {
618 let mut f = Function::new();
619 assert_eq!(f.to_string(), "function u0:0() fast {\n}\n");
620
621 f.name = UserFuncName::testcase("foo");
622 assert_eq!(f.to_string(), "function %foo() fast {\n}\n");
623
624 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0));
625 assert_eq!(
626 f.to_string(),
627 "function %foo() fast {\n ss0 = explicit_slot 4\n}\n"
628 );
629
630 let block = f.dfg.make_block();
631 f.layout.append_block(block);
632 assert_eq!(
633 f.to_string(),
634 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n"
635 );
636
637 f.dfg.append_block_param(block, types::I8);
638 assert_eq!(
639 f.to_string(),
640 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n"
641 );
642
643 f.dfg.append_block_param(block, types::F32.by(4).unwrap());
644 assert_eq!(
645 f.to_string(),
646 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n"
647 );
648
649 {
650 let mut cursor = FuncCursor::new(&mut f);
651 cursor.set_position(CursorPosition::After(block));
652 cursor.ins().return_(&[])
653 };
654 assert_eq!(
655 f.to_string(),
656 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n"
657 );
658
659 let mut f = Function::new();
660 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2));
661 assert_eq!(
662 f.to_string(),
663 "function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n"
664 );
665 }
666
667 #[test]
668 fn aliases() {
669 use crate::ir::InstBuilder;
670
671 let mut func = Function::new();
672 {
673 let block0 = func.dfg.make_block();
674 let mut pos = FuncCursor::new(&mut func);
675 pos.insert_block(block0);
676
677 let v0 = pos.func.dfg.append_block_param(block0, types::I32);
679 let v1 = pos.func.dfg.append_block_param(block0, types::I32);
680 let v2 = pos.func.dfg.append_block_param(block0, types::I32);
681 pos.func.dfg.detach_block_params(block0);
682
683 let v3 = pos.func.dfg.append_block_param(block0, types::I32);
685 pos.func.dfg.change_to_alias(v0, v3);
686
687 pos.func.dfg.make_value_alias_for_serialization(v0, v2); let _dummy0 = pos.ins().iconst(types::I32, 42);
692 let v4 = pos.ins().iadd(v0, v0);
693 pos.func.dfg.change_to_alias(v1, v4);
694 let _dummy1 = pos.ins().iconst(types::I32, 23);
695 let _v7 = pos.ins().iadd(v1, v1);
696 }
697 assert_eq!(
698 func.to_string(),
699 "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"
700 );
701 }
702
703 #[test]
704 fn cold_blocks() {
705 let mut func = Function::new();
706 {
707 let mut pos = FuncCursor::new(&mut func);
708
709 let block0 = pos.func.dfg.make_block();
710 pos.insert_block(block0);
711 pos.func.layout.set_cold(block0);
712
713 let block1 = pos.func.dfg.make_block();
714 pos.insert_block(block1);
715 pos.func.dfg.append_block_param(block1, types::I32);
716 pos.func.layout.set_cold(block1);
717 }
718
719 assert_eq!(
720 func.to_string(),
721 "function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n"
722 );
723 }
724}