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() { 4 } else { 36 };
264
265 func_w.write_block_header(w, func, block, indent)?;
266 for a in func.dfg.block_params(block).iter().cloned() {
267 write_value_aliases(w, aliases, a, indent)?;
268 }
269
270 for inst in func.layout.block_insts(block) {
271 func_w.write_instruction(w, func, aliases, inst, indent)?;
272 }
273
274 Ok(())
275}
276
277fn type_suffix(func: &Function, inst: Inst) -> Option<Type> {
287 let inst_data = &func.dfg.insts[inst];
288 let constraints = inst_data.opcode().constraints();
289
290 if !constraints.is_polymorphic() {
291 return None;
292 }
293
294 if constraints.use_typevar_operand() {
297 let ctrl_var = inst_data.typevar_operand(&func.dfg.value_lists).unwrap();
298 let def_block = match func.dfg.value_def(ctrl_var) {
299 ValueDef::Result(instr, _) => func.layout.inst_block(instr),
300 ValueDef::Param(block, _) => Some(block),
301 ValueDef::Union(..) => None,
302 };
303 if def_block.is_some() && def_block == func.layout.inst_block(inst) {
304 return None;
305 }
306 }
307
308 let rtype = func.dfg.ctrl_typevar(inst);
309 assert!(
310 !rtype.is_invalid(),
311 "Polymorphic instruction must produce a result"
312 );
313 Some(rtype)
314}
315
316fn write_value_aliases(
318 w: &mut dyn Write,
319 aliases: &SecondaryMap<Value, Vec<Value>>,
320 target: Value,
321 indent: usize,
322) -> fmt::Result {
323 let mut todo_stack = vec![target];
324 while let Some(target) = todo_stack.pop() {
325 for &a in &aliases[target] {
326 writeln!(w, "{1:0$}{2} -> {3}", indent, "", a, target)?;
327 todo_stack.push(a);
328 }
329 }
330
331 Ok(())
332}
333
334fn write_instruction(
335 w: &mut dyn Write,
336 func: &Function,
337 aliases: &SecondaryMap<Value, Vec<Value>>,
338 inst: Inst,
339 indent: usize,
340) -> fmt::Result {
341 let mut s = String::with_capacity(16);
343
344 let srcloc = func.srcloc(inst);
346 if !srcloc.is_default() {
347 write!(s, "{srcloc} ")?;
348 }
349
350 write!(w, "{s:indent$}")?;
352
353 let mut has_results = false;
355 for r in func.dfg.inst_results(inst) {
356 if !has_results {
357 has_results = true;
358 write!(w, "{r}")?;
359 } else {
360 write!(w, ", {r}")?;
361 }
362 if let Some(f) = &func.dfg.facts[*r] {
363 write!(w, " ! {f}")?;
364 }
365 }
366 if has_results {
367 write!(w, " = ")?;
368 }
369
370 let opcode = func.dfg.insts[inst].opcode();
372
373 match type_suffix(func, inst) {
374 Some(suf) => write!(w, "{opcode}.{suf}")?,
375 None => write!(w, "{opcode}")?,
376 }
377
378 write_operands(w, &func.dfg, inst)?;
379 writeln!(w)?;
380
381 for r in func.dfg.inst_results(inst) {
383 write_value_aliases(w, aliases, *r, indent)?;
384 }
385 Ok(())
386}
387
388pub fn write_operands(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {
390 let pool = &dfg.value_lists;
391 let jump_tables = &dfg.jump_tables;
392 let exception_tables = &dfg.exception_tables;
393 use crate::ir::instructions::InstructionData::*;
394 let ctrl_ty = dfg.ctrl_typevar(inst);
395 match dfg.insts[inst] {
396 AtomicRmw { op, args, .. } => write!(w, " {} {}, {}", op, args[0], args[1]),
397 AtomicCas { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
398 LoadNoOffset { flags, arg, .. } => write!(w, "{flags} {arg}"),
399 StoreNoOffset { flags, args, .. } => write!(w, "{} {}, {}", flags, args[0], args[1]),
400 Unary { arg, .. } => write!(w, " {arg}"),
401 UnaryImm { imm, .. } => write!(w, " {}", {
402 let mut imm = imm;
403 if ctrl_ty.bits() != 0 {
404 imm = imm.sign_extend_from_width(ctrl_ty.bits());
405 }
406 imm
407 }),
408 UnaryIeee16 { imm, .. } => write!(w, " {imm}"),
409 UnaryIeee32 { imm, .. } => write!(w, " {imm}"),
410 UnaryIeee64 { imm, .. } => write!(w, " {imm}"),
411 UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"),
412 UnaryConst {
413 constant_handle, ..
414 } => write!(w, " {constant_handle}"),
415 Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),
416 BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"),
417 BinaryImm64 { arg, imm, .. } => write!(w, " {}, {}", arg, {
418 let mut imm = imm;
419 if ctrl_ty.bits() != 0 {
420 imm = imm.sign_extend_from_width(ctrl_ty.bits());
421 }
422 imm
423 }),
424 Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
425 MultiAry { ref args, .. } => {
426 if args.is_empty() {
427 write!(w, "")
428 } else {
429 write!(w, " {}", DisplayValues(args.as_slice(pool)))
430 }
431 }
432 NullAry { .. } => write!(w, " "),
433 TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm),
434 Shuffle { imm, args, .. } => {
435 let data = dfg.immediates.get(imm).expect(
436 "Expected the shuffle mask to already be inserted into the immediates table",
437 );
438 write!(w, " {}, {}, {}", args[0], args[1], data)
439 }
440 IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
441 IntCompareImm { cond, arg, imm, .. } => write!(w, " {} {}, {}", cond, arg, {
442 let mut imm = imm;
443 if ctrl_ty.bits() != 0 {
444 imm = imm.sign_extend_from_width(ctrl_ty.bits());
445 }
446 imm
447 }),
448 IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code),
449 FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
450 Jump { destination, .. } => {
451 write!(w, " {}", destination.display(pool))
452 }
453 Brif {
454 arg,
455 blocks: [block_then, block_else],
456 ..
457 } => {
458 write!(w, " {}, {}", arg, block_then.display(pool))?;
459 write!(w, ", {}", block_else.display(pool))
460 }
461 BranchTable { arg, table, .. } => {
462 write!(w, " {}, {}", arg, jump_tables[table].display(pool))
463 }
464 Call {
465 func_ref, ref args, ..
466 } => {
467 write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?;
468 write_user_stack_map_entries(w, dfg, inst)
469 }
470 CallIndirect {
471 sig_ref, ref args, ..
472 } => {
473 let args = args.as_slice(pool);
474 write!(
475 w,
476 " {}, {}({})",
477 sig_ref,
478 args[0],
479 DisplayValues(&args[1..])
480 )?;
481 write_user_stack_map_entries(w, dfg, inst)
482 }
483 TryCall {
484 func_ref,
485 ref args,
486 exception,
487 ..
488 } => {
489 write!(
490 w,
491 " {}({}), {}",
492 func_ref,
493 DisplayValues(args.as_slice(pool)),
494 exception_tables[exception].display(pool),
495 )
496 }
497 TryCallIndirect {
498 ref args,
499 exception,
500 ..
501 } => {
502 let args = args.as_slice(pool);
503 write!(
504 w,
505 " {}({}), {}",
506 args[0],
507 DisplayValues(&args[1..]),
508 exception_tables[exception].display(pool),
509 )
510 }
511 FuncAddr { func_ref, .. } => write!(w, " {func_ref}"),
512 StackLoad {
513 stack_slot, offset, ..
514 } => write!(w, " {stack_slot}{offset}"),
515 StackStore {
516 arg,
517 stack_slot,
518 offset,
519 ..
520 } => write!(w, " {arg}, {stack_slot}{offset}"),
521 DynamicStackLoad {
522 dynamic_stack_slot, ..
523 } => write!(w, " {dynamic_stack_slot}"),
524 DynamicStackStore {
525 arg,
526 dynamic_stack_slot,
527 ..
528 } => write!(w, " {arg}, {dynamic_stack_slot}"),
529 Load {
530 flags, arg, offset, ..
531 } => write!(w, "{flags} {arg}{offset}"),
532 Store {
533 flags,
534 args,
535 offset,
536 ..
537 } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset),
538 Trap { code, .. } => write!(w, " {code}"),
539 CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"),
540 }?;
541
542 let mut sep = " ; ";
543 for arg in dfg.inst_values(inst) {
544 if let ValueDef::Result(src, _) = dfg.value_def(arg) {
545 let imm = match dfg.insts[src] {
546 UnaryImm { imm, .. } => {
547 let mut imm = imm;
548 if dfg.ctrl_typevar(src).bits() != 0 {
549 imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits());
550 }
551 imm.to_string()
552 }
553 UnaryIeee16 { imm, .. } => imm.to_string(),
554 UnaryIeee32 { imm, .. } => imm.to_string(),
555 UnaryIeee64 { imm, .. } => imm.to_string(),
556 UnaryConst {
557 constant_handle,
558 opcode: Opcode::F128const,
559 } => Ieee128::try_from(dfg.constants.get(constant_handle))
560 .expect("16-byte f128 constant")
561 .to_string(),
562 UnaryConst {
563 constant_handle, ..
564 } => constant_handle.to_string(),
565 _ => continue,
566 };
567 write!(w, "{sep}{arg} = {imm}")?;
568 sep = ", ";
569 }
570 }
571 Ok(())
572}
573
574fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {
575 let entries = match dfg.user_stack_map_entries(inst) {
576 None => return Ok(()),
577 Some(es) => es,
578 };
579 write!(w, ", stack_map=[")?;
580 let mut need_comma = false;
581 for entry in entries {
582 if need_comma {
583 write!(w, ", ")?;
584 }
585 write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?;
586 need_comma = true;
587 }
588 write!(w, "]")?;
589 Ok(())
590}
591
592struct DisplayValues<'a>(&'a [Value]);
594
595impl<'a> fmt::Display for DisplayValues<'a> {
596 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
597 for (i, val) in self.0.iter().enumerate() {
598 if i == 0 {
599 write!(f, "{val}")?;
600 } else {
601 write!(f, ", {val}")?;
602 }
603 }
604 Ok(())
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use crate::cursor::{Cursor, CursorPosition, FuncCursor};
611 use crate::ir::types;
612 use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName};
613 use alloc::string::ToString;
614
615 #[test]
616 fn basic() {
617 let mut f = Function::new();
618 assert_eq!(f.to_string(), "function u0:0() fast {\n}\n");
619
620 f.name = UserFuncName::testcase("foo");
621 assert_eq!(f.to_string(), "function %foo() fast {\n}\n");
622
623 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0));
624 assert_eq!(
625 f.to_string(),
626 "function %foo() fast {\n ss0 = explicit_slot 4\n}\n"
627 );
628
629 let block = f.dfg.make_block();
630 f.layout.append_block(block);
631 assert_eq!(
632 f.to_string(),
633 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n"
634 );
635
636 f.dfg.append_block_param(block, types::I8);
637 assert_eq!(
638 f.to_string(),
639 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n"
640 );
641
642 f.dfg.append_block_param(block, types::F32.by(4).unwrap());
643 assert_eq!(
644 f.to_string(),
645 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n"
646 );
647
648 {
649 let mut cursor = FuncCursor::new(&mut f);
650 cursor.set_position(CursorPosition::After(block));
651 cursor.ins().return_(&[])
652 };
653 assert_eq!(
654 f.to_string(),
655 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n"
656 );
657
658 let mut f = Function::new();
659 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2));
660 assert_eq!(
661 f.to_string(),
662 "function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n"
663 );
664 }
665
666 #[test]
667 fn aliases() {
668 use crate::ir::InstBuilder;
669
670 let mut func = Function::new();
671 {
672 let block0 = func.dfg.make_block();
673 let mut pos = FuncCursor::new(&mut func);
674 pos.insert_block(block0);
675
676 let v0 = pos.func.dfg.append_block_param(block0, types::I32);
678 let v1 = pos.func.dfg.append_block_param(block0, types::I32);
679 let v2 = pos.func.dfg.append_block_param(block0, types::I32);
680 pos.func.dfg.detach_block_params(block0);
681
682 let v3 = pos.func.dfg.append_block_param(block0, types::I32);
684 pos.func.dfg.change_to_alias(v0, v3);
685
686 pos.func.dfg.make_value_alias_for_serialization(v0, v2); let _dummy0 = pos.ins().iconst(types::I32, 42);
691 let v4 = pos.ins().iadd(v0, v0);
692 pos.func.dfg.change_to_alias(v1, v4);
693 let _dummy1 = pos.ins().iconst(types::I32, 23);
694 let _v7 = pos.ins().iadd(v1, v1);
695 }
696 assert_eq!(
697 func.to_string(),
698 "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"
699 );
700 }
701
702 #[test]
703 fn cold_blocks() {
704 let mut func = Function::new();
705 {
706 let mut pos = FuncCursor::new(&mut func);
707
708 let block0 = pos.func.dfg.make_block();
709 pos.insert_block(block0);
710 pos.func.layout.set_cold(block0);
711
712 let block1 = pos.func.dfg.make_block();
713 pos.insert_block(block1);
714 pos.func.dfg.append_block_param(block1, types::I32);
715 pos.func.layout.set_cold(block1);
716 }
717
718 assert_eq!(
719 func.to_string(),
720 "function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n"
721 );
722 }
723}