1use crate::entity::SecondaryMap;
7use crate::ir::entities::AnyEntity;
8use crate::ir::immediates::Ieee128;
9use crate::ir::{Block, DataFlowGraph, Function, Inst, Opcode, SigRef, Type, Value, ValueDef};
10use crate::packed_option::ReservedValue;
11use alloc::string::{String, ToString};
12use alloc::vec::Vec;
13use core::fmt::{self, Write};
14
15pub trait FuncWriter {
17 fn write_block_header(
19 &mut self,
20 w: &mut dyn Write,
21 func: &Function,
22 block: Block,
23 indent: usize,
24 ) -> fmt::Result;
25
26 fn write_instruction(
28 &mut self,
29 w: &mut dyn Write,
30 func: &Function,
31 aliases: &SecondaryMap<Value, Vec<Value>>,
32 inst: Inst,
33 indent: usize,
34 ) -> fmt::Result;
35
36 fn write_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> {
38 self.super_preamble(w, func)
39 }
40
41 fn super_preamble(&mut self, w: &mut dyn Write, func: &Function) -> Result<bool, fmt::Error> {
43 let mut any = false;
44
45 for (ss, slot) in func.dynamic_stack_slots.iter() {
46 any = true;
47 self.write_entity_definition(w, func, ss.into(), slot)?;
48 }
49
50 for (ss, slot) in func.sized_stack_slots.iter() {
51 any = true;
52 self.write_entity_definition(w, func, ss.into(), slot)?;
53 }
54
55 for (ar, ar_data) in func.dfg.alias_regions.iter() {
56 any = true;
57 self.write_entity_definition(
58 w,
59 func,
60 ar.into(),
61 &format_args!("{} \"{}\"", ar_data.user_id, ar_data.description),
62 )?;
63 }
64
65 for (gv, gv_data) in &func.global_values {
66 any = true;
67 self.write_entity_definition(
68 w,
69 func,
70 gv.into(),
71 &gv_data.display(&func.dfg.mem_flags),
72 )?;
73 }
74
75 for (sig, sig_data) in &func.dfg.signatures {
78 any = true;
79 self.write_entity_definition(w, func, sig.into(), &sig_data)?;
80 }
81
82 for (fnref, ext_func) in &func.dfg.ext_funcs {
83 if ext_func.signature != SigRef::reserved_value() {
84 any = true;
85 self.write_entity_definition(
86 w,
87 func,
88 fnref.into(),
89 &ext_func.display(Some(&func.params)),
90 )?;
91 }
92 }
93
94 for (&cref, cval) in func.dfg.constants.iter() {
95 any = true;
96 self.write_entity_definition(w, func, cref.into(), cval)?;
97 }
98
99 if let Some(limit) = func.stack_limit {
100 any = true;
101 self.write_entity_definition(w, func, AnyEntity::StackLimit, &limit)?;
102 }
103
104 Ok(any)
105 }
106
107 fn write_entity_definition(
109 &mut self,
110 w: &mut dyn Write,
111 func: &Function,
112 entity: AnyEntity,
113 value: &dyn fmt::Display,
114 ) -> fmt::Result {
115 self.super_entity_definition(w, func, entity, value)
116 }
117
118 fn super_entity_definition(
120 &mut self,
121 w: &mut dyn Write,
122 _func: &Function,
123 entity: AnyEntity,
124 value: &dyn fmt::Display,
125 ) -> fmt::Result {
126 writeln!(w, " {entity} = {value}")
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 write!(w, "{arg}: {ty}")
213}
214
215pub fn write_block_header(
222 w: &mut dyn Write,
223 func: &Function,
224 block: Block,
225 indent: usize,
226) -> fmt::Result {
227 let cold = if func.layout.is_cold(block) {
228 " cold"
229 } else {
230 ""
231 };
232
233 write!(w, "{1:0$}{2}", indent - 4, "", block)?;
235
236 let mut args = func.dfg.block_params(block).iter().cloned();
237 match args.next() {
238 None => return writeln!(w, "{cold}:"),
239 Some(arg) => {
240 write!(w, "(")?;
241 write_arg(w, func, arg)?;
242 }
243 }
244 for arg in args {
246 write!(w, ", ")?;
247 write_arg(w, func, arg)?;
248 }
249 writeln!(w, "){cold}:")
250}
251
252fn decorate_block<FW: FuncWriter>(
253 func_w: &mut FW,
254 w: &mut dyn Write,
255 func: &Function,
256 aliases: &SecondaryMap<Value, Vec<Value>>,
257 block: Block,
258) -> fmt::Result {
259 let indent = if func.rel_srclocs().is_empty() && func.debug_tags.is_empty() {
261 4
262 } else {
263 36
264 };
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 mut 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_debug_tags(w, &func, inst, &mut indent)?;
353
354 write!(w, "{s:indent$}")?;
356
357 let mut has_results = false;
359 for r in func.dfg.inst_results(inst) {
360 if !has_results {
361 has_results = true;
362 write!(w, "{r}")?;
363 } else {
364 write!(w, ", {r}")?;
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, "{} {arg}", dfg.mem_flags[flags]),
400 StoreNoOffset { flags, args, .. } => {
401 write!(w, "{} {}, {}", dfg.mem_flags[flags], args[0], args[1])
402 }
403 Unary { arg, .. } => write!(w, " {arg}"),
404 UnaryImm { imm, .. } => write!(w, " {}", {
405 let mut imm = imm;
406 if ctrl_ty.bits() != 0 {
407 imm = imm.sign_extend_from_width(ctrl_ty.bits());
408 }
409 imm
410 }),
411 UnaryIeee16 { imm, .. } => write!(w, " {imm}"),
412 UnaryIeee32 { imm, .. } => write!(w, " {imm}"),
413 UnaryIeee64 { imm, .. } => write!(w, " {imm}"),
414 UnaryGlobalValue { global_value, .. } => write!(w, " {global_value}"),
415 UnaryConst {
416 constant_handle, ..
417 } => write!(w, " {constant_handle}"),
418 Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),
419 BinaryImm8 { arg, imm, .. } => write!(w, " {arg}, {imm}"),
420 Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
421 MultiAry { ref args, .. } => {
422 if args.is_empty() {
423 write!(w, "")
424 } else {
425 write!(w, " {}", DisplayValues(args.as_slice(pool)))
426 }
427 }
428 NullAry { .. } => write!(w, " "),
429 TernaryImm8 { imm, args, .. } => write!(w, " {}, {}, {}", args[0], args[1], imm),
430 Shuffle { imm, args, .. } => {
431 let data = dfg.immediates.get(imm).expect(
432 "Expected the shuffle mask to already be inserted into the immediates table",
433 );
434 write!(w, " {}, {}, {}", args[0], args[1], data)
435 }
436 IntCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
437 IntAddTrap { args, code, .. } => write!(w, " {}, {}, {}", args[0], args[1], code),
438 FloatCompare { cond, args, .. } => write!(w, " {} {}, {}", cond, args[0], args[1]),
439 Jump { destination, .. } => {
440 write!(w, " {}", destination.display(pool))
441 }
442 Brif {
443 arg,
444 blocks: [block_then, block_else],
445 ..
446 } => {
447 write!(w, " {}, {}", arg, block_then.display(pool))?;
448 write!(w, ", {}", block_else.display(pool))
449 }
450 BranchTable { arg, table, .. } => {
451 write!(w, " {}, {}", arg, jump_tables[table].display(pool))
452 }
453 Call {
454 func_ref, ref args, ..
455 } => {
456 write!(w, " {}({})", func_ref, DisplayValues(args.as_slice(pool)))?;
457 write_user_stack_map_entries(w, dfg, inst)
458 }
459 CallIndirect {
460 sig_ref, ref args, ..
461 } => {
462 let args = args.as_slice(pool);
463 write!(
464 w,
465 " {}, {}({})",
466 sig_ref,
467 args[0],
468 DisplayValues(&args[1..])
469 )?;
470 write_user_stack_map_entries(w, dfg, inst)
471 }
472 TryCall {
473 func_ref,
474 ref args,
475 exception,
476 ..
477 } => {
478 write!(
479 w,
480 " {}({}), {}",
481 func_ref,
482 DisplayValues(args.as_slice(pool)),
483 exception_tables[exception].display(pool),
484 )?;
485 write_user_stack_map_entries(w, dfg, inst)
486 }
487 TryCallIndirect {
488 ref args,
489 exception,
490 ..
491 } => {
492 let args = args.as_slice(pool);
493 write!(
494 w,
495 " {}({}), {}",
496 args[0],
497 DisplayValues(&args[1..]),
498 exception_tables[exception].display(pool),
499 )?;
500 write_user_stack_map_entries(w, dfg, inst)
501 }
502 FuncAddr { func_ref, .. } => write!(w, " {func_ref}"),
503 StackLoad {
504 stack_slot, offset, ..
505 } => write!(w, " {stack_slot}{offset}"),
506 StackStore {
507 arg,
508 stack_slot,
509 offset,
510 ..
511 } => write!(w, " {arg}, {stack_slot}{offset}"),
512 DynamicStackLoad {
513 dynamic_stack_slot, ..
514 } => write!(w, " {dynamic_stack_slot}"),
515 DynamicStackStore {
516 arg,
517 dynamic_stack_slot,
518 ..
519 } => write!(w, " {arg}, {dynamic_stack_slot}"),
520 Load {
521 flags, arg, offset, ..
522 } => write!(w, "{} {arg}{offset}", dfg.mem_flags[flags]),
523 Store {
524 flags,
525 args,
526 offset,
527 ..
528 } => write!(
529 w,
530 "{} {}, {}{}",
531 dfg.mem_flags[flags], args[0], args[1], offset
532 ),
533 Trap { code, .. } => write!(w, " {code}"),
534 CondTrap { arg, code, .. } => write!(w, " {arg}, {code}"),
535 ExceptionHandlerAddress { block, imm, .. } => write!(w, " {block}, {imm}"),
536 }?;
537
538 let mut sep = " ; ";
539 for arg in dfg.inst_values(inst) {
540 if let ValueDef::Result(src, _) = dfg.value_def(arg) {
541 let imm = match dfg.insts[src] {
542 UnaryImm { imm, .. } => {
543 let mut imm = imm;
544 if dfg.ctrl_typevar(src).bits() != 0 {
545 imm = imm.sign_extend_from_width(dfg.ctrl_typevar(src).bits());
546 }
547 imm.to_string()
548 }
549 UnaryIeee16 { imm, .. } => imm.to_string(),
550 UnaryIeee32 { imm, .. } => imm.to_string(),
551 UnaryIeee64 { imm, .. } => imm.to_string(),
552 UnaryConst {
553 constant_handle,
554 opcode: Opcode::F128const,
555 } => Ieee128::try_from(dfg.constants.get(constant_handle))
556 .expect("16-byte f128 constant")
557 .to_string(),
558 UnaryConst {
559 constant_handle, ..
560 } => constant_handle.to_string(),
561 _ => continue,
562 };
563 write!(w, "{sep}{arg} = {imm}")?;
564 sep = ", ";
565 }
566 }
567 Ok(())
568}
569
570fn write_debug_tags(
571 w: &mut dyn Write,
572 func: &Function,
573 inst: Inst,
574 indent: &mut usize,
575) -> fmt::Result {
576 let tags = func.debug_tags.get(inst);
577 if !tags.is_empty() {
578 let tags = tags
579 .iter()
580 .map(|tag| format!("{tag}"))
581 .collect::<Vec<_>>()
582 .join(", ");
583 let s = format!("<{tags}> ");
584 write!(w, "{s}")?;
585 *indent = indent.saturating_sub(s.len());
586 }
587 Ok(())
588}
589
590fn write_user_stack_map_entries(w: &mut dyn Write, dfg: &DataFlowGraph, inst: Inst) -> fmt::Result {
591 let entries = match dfg.user_stack_map_entries(inst) {
592 None => return Ok(()),
593 Some(es) => es,
594 };
595 write!(w, ", stack_map=[")?;
596 let mut need_comma = false;
597 for entry in entries {
598 if need_comma {
599 write!(w, ", ")?;
600 }
601 write!(w, "{} @ {}+{}", entry.ty, entry.slot, entry.offset)?;
602 need_comma = true;
603 }
604 write!(w, "]")?;
605 Ok(())
606}
607
608struct DisplayValues<'a>(&'a [Value]);
610
611impl<'a> fmt::Display for DisplayValues<'a> {
612 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
613 for (i, val) in self.0.iter().enumerate() {
614 if i == 0 {
615 write!(f, "{val}")?;
616 } else {
617 write!(f, ", {val}")?;
618 }
619 }
620 Ok(())
621 }
622}
623
624#[cfg(test)]
625mod tests {
626 use crate::cursor::{Cursor, CursorPosition, FuncCursor};
627 use crate::ir::types;
628 use crate::ir::{Function, InstBuilder, StackSlotData, StackSlotKind, UserFuncName};
629 use alloc::string::ToString;
630
631 #[test]
632 fn basic() {
633 let mut f = Function::new();
634 assert_eq!(f.to_string(), "function u0:0() fast {\n}\n");
635
636 f.name = UserFuncName::testcase("foo");
637 assert_eq!(f.to_string(), "function %foo() fast {\n}\n");
638
639 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 0));
640 assert_eq!(
641 f.to_string(),
642 "function %foo() fast {\n ss0 = explicit_slot 4\n}\n"
643 );
644
645 let block = f.dfg.make_block();
646 f.layout.append_block(block);
647 assert_eq!(
648 f.to_string(),
649 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0:\n}\n"
650 );
651
652 f.dfg.append_block_param(block, types::I8);
653 assert_eq!(
654 f.to_string(),
655 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8):\n}\n"
656 );
657
658 f.dfg.append_block_param(block, types::F32.by(4).unwrap());
659 assert_eq!(
660 f.to_string(),
661 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n}\n"
662 );
663
664 {
665 let mut cursor = FuncCursor::new(&mut f);
666 cursor.set_position(CursorPosition::After(block));
667 cursor.ins().return_(&[])
668 };
669 assert_eq!(
670 f.to_string(),
671 "function %foo() fast {\n ss0 = explicit_slot 4\n\nblock0(v0: i8, v1: f32x4):\n return\n}\n"
672 );
673
674 let mut f = Function::new();
675 f.create_sized_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4, 2));
676 assert_eq!(
677 f.to_string(),
678 "function u0:0() fast {\n ss0 = explicit_slot 4, align = 4\n}\n"
679 );
680 }
681
682 #[test]
683 fn aliases() {
684 use crate::ir::InstBuilder;
685
686 let mut func = Function::new();
687 {
688 let block0 = func.dfg.make_block();
689 let mut pos = FuncCursor::new(&mut func);
690 pos.insert_block(block0);
691
692 let v0 = pos.func.dfg.append_block_param(block0, types::I32);
694 let v1 = pos.func.dfg.append_block_param(block0, types::I32);
695 let v2 = pos.func.dfg.append_block_param(block0, types::I32);
696 pos.func.dfg.detach_block_params(block0);
697
698 let v3 = pos.func.dfg.append_block_param(block0, types::I32);
700 pos.func.dfg.change_to_alias(v0, v3);
701
702 pos.func.dfg.make_value_alias_for_serialization(v0, v2); let _dummy0 = pos.ins().iconst(types::I32, 42);
707 let v4 = pos.ins().iadd(v0, v0);
708 pos.func.dfg.change_to_alias(v1, v4);
709 let _dummy1 = pos.ins().iconst(types::I32, 23);
710 let _v7 = pos.ins().iadd(v1, v1);
711 }
712 assert_eq!(
713 func.to_string(),
714 "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"
715 );
716 }
717
718 #[test]
719 fn cold_blocks() {
720 let mut func = Function::new();
721 {
722 let mut pos = FuncCursor::new(&mut func);
723
724 let block0 = pos.func.dfg.make_block();
725 pos.insert_block(block0);
726 pos.func.layout.set_cold(block0);
727
728 let block1 = pos.func.dfg.make_block();
729 pos.insert_block(block1);
730 pos.func.dfg.append_block_param(block1, types::I32);
731 pos.func.layout.set_cold(block1);
732 }
733
734 assert_eq!(
735 func.to_string(),
736 "function u0:0() fast {\nblock0 cold:\n\nblock1(v0: i32) cold:\n}\n"
737 );
738 }
739}