1use std::collections::{BTreeMap, HashMap};
8
9use sway_types::SourceEngine;
10
11use crate::{
12 asm::*,
13 block::Block,
14 constant::{ConstantContent, ConstantValue},
15 context::Context,
16 function::{Function, FunctionContent},
17 instruction::{FuelVmInstruction, InstOp, Predicate, Register},
18 metadata::{MetadataIndex, Metadatum},
19 module::{Kind, ModuleContent},
20 value::{Value, ValueContent, ValueDatum},
21 AnalysisResult, AnalysisResultT, AnalysisResults, BinaryOpKind, BlockArgument, ConfigContent,
22 IrError, Module, Pass, PassMutability, ScopedPass, UnaryOpKind,
23};
24
25#[derive(Debug)]
26pub(crate) enum Doc {
27 Empty,
28 Space,
29 Comma,
30
31 Text(String),
32 Line(Box<Doc>),
33
34 Pair(Box<Doc>, Box<Doc>),
35
36 List(Vec<Doc>),
37 ListSep(Vec<Doc>, Box<Doc>),
38
39 Parens(Box<Doc>),
40
41 Indent(i64, Box<Doc>),
42}
43
44impl Doc {
45 pub(crate) fn text<S: Into<String>>(s: S) -> Self {
46 Doc::Text(s.into())
47 }
48
49 fn line(doc: Doc) -> Self {
50 Doc::Line(Box::new(doc))
51 }
52
53 pub(crate) fn text_line<S: Into<String>>(s: S) -> Self {
54 Doc::Line(Box::new(Doc::Text(s.into())))
55 }
56
57 fn indent(n: i64, doc: Doc) -> Doc {
58 Doc::Indent(n, Box::new(doc))
59 }
60
61 fn list_sep(docs: Vec<Doc>, sep: Doc) -> Doc {
62 Doc::ListSep(docs, Box::new(sep))
63 }
64
65 fn in_parens_comma_sep(docs: Vec<Doc>) -> Doc {
66 Doc::Parens(Box::new(Doc::list_sep(docs, Doc::Comma)))
67 }
68
69 pub(crate) fn append(self, doc: Doc) -> Doc {
70 match (&self, &doc) {
71 (Doc::Empty, _) => doc,
72 (_, Doc::Empty) => self,
73 _ => Doc::Pair(Box::new(self), Box::new(doc)),
74 }
75 }
76
77 fn and(self, doc: Doc) -> Doc {
78 match doc {
79 Doc::Empty => doc,
80 _ => Doc::Pair(Box::new(self), Box::new(doc)),
81 }
82 }
83
84 pub(crate) fn build(self) -> String {
85 build_doc(self, 0)
86 }
87}
88
89pub fn to_string(context: &Context) -> String {
93 context_print(context, &|_, doc| doc)
94}
95
96pub(crate) fn context_print(context: &Context, map_doc: &impl Fn(&Value, Doc) -> Doc) -> String {
97 let mut md_namer = MetadataNamer::default();
98 context
99 .modules
100 .iter()
101 .fold(Doc::Empty, |doc, (_, module)| {
102 doc.append(module_to_doc(context, &mut md_namer, module, map_doc))
103 })
104 .append(md_namer.to_doc(context))
105 .build()
106}
107
108pub(crate) fn block_print(
109 context: &Context,
110 function: Function,
111 block: Block,
112 map_doc: &impl Fn(&Value, Doc) -> Doc,
113) -> String {
114 let mut md_namer = MetadataNamer::default();
115 let mut namer = Namer::new(function);
116 block_to_doc(context, &mut md_namer, &mut namer, &block, map_doc).build()
117}
118
119pub struct ModulePrinterResult;
120impl AnalysisResultT for ModulePrinterResult {}
121
122pub fn module_printer_pass(
124 context: &Context,
125 _analyses: &AnalysisResults,
126 module: Module,
127) -> Result<AnalysisResult, IrError> {
128 let mut md_namer = MetadataNamer::default();
129 print!(
130 "{}",
131 module_to_doc(
132 context,
133 &mut md_namer,
134 context.modules.get(module.0).unwrap(),
135 &|_, doc| doc
136 )
137 .append(md_namer.to_doc(context))
138 .build()
139 );
140 Ok(Box::new(ModulePrinterResult))
141}
142
143pub fn module_print(context: &Context, _analyses: &AnalysisResults, module: Module) {
145 let mut md_namer = MetadataNamer::default();
146 println!(
147 "{}",
148 module_to_doc(
149 context,
150 &mut md_namer,
151 context.modules.get(module.0).unwrap(),
152 &|_, doc| doc
153 )
154 .append(md_namer.to_doc(context))
155 .build()
156 );
157}
158
159pub fn function_print(context: &Context, function: Function) {
161 let mut md_namer = MetadataNamer::default();
162 println!(
163 "{}",
164 function_to_doc(
165 context,
166 &mut md_namer,
167 &mut Namer::new(function),
168 context.functions.get(function.0).unwrap(),
169 &|_, doc| doc
170 )
171 .append(md_namer.to_doc(context))
172 .build()
173 );
174}
175
176pub const MODULE_PRINTER_NAME: &str = "module-printer";
177
178pub fn create_module_printer_pass() -> Pass {
179 Pass {
180 name: MODULE_PRINTER_NAME,
181 descr: "Print module to stdout",
182 deps: vec![],
183 runner: ScopedPass::ModulePass(PassMutability::Analysis(module_printer_pass)),
184 }
185}
186
187fn module_to_doc<'a>(
188 context: &'a Context,
189 md_namer: &mut MetadataNamer,
190 module: &'a ModuleContent,
191 map_doc: &impl Fn(&Value, Doc) -> Doc,
192) -> Doc {
193 Doc::line(Doc::Text(format!(
194 "{} {{",
195 match module.kind {
196 Kind::Contract => "contract",
197 Kind::Library => "library",
198 Kind::Predicate => "predicate",
199 Kind::Script => "script",
200 }
201 )))
202 .append(Doc::indent(
203 4,
204 Doc::List(
205 module
206 .configs
207 .values()
208 .map(|value| config_to_doc(context, value, md_namer))
209 .collect(),
210 ),
211 ))
212 .append(if !module.configs.is_empty() {
213 Doc::line(Doc::Empty)
214 } else {
215 Doc::Empty
216 })
217 .append(Doc::indent(
218 4,
219 Doc::list_sep(
220 module
221 .global_variables
222 .iter()
223 .map(|(name, var)| {
224 let var_content = &context.global_vars[var.0];
225 let init_doc = match &var_content.initializer {
226 Some(const_val) => Doc::text(format!(
227 " = const {}",
228 const_val.get_content(context).as_lit_string(context)
229 )),
230 None => Doc::Empty,
231 };
232 let mut_string = if var_content.mutable { "mut " } else { "" };
233 Doc::line(
234 Doc::text(format!(
235 "{}global {} : {}",
236 mut_string,
237 name.join("::"),
238 var.get_inner_type(context).as_string(context),
239 ))
240 .append(init_doc),
241 )
242 })
243 .collect(),
244 Doc::line(Doc::Empty),
245 ),
246 ))
247 .append(Doc::indent(
248 4,
249 Doc::list_sep(
250 module
251 .functions
252 .iter()
253 .map(|function| {
254 function_to_doc(
255 context,
256 md_namer,
257 &mut Namer::new(*function),
258 &context.functions[function.0],
259 map_doc,
260 )
261 })
262 .collect(),
263 Doc::line(Doc::Empty),
264 ),
265 ))
266 .append(Doc::text_line("}"))
267}
268
269fn config_to_doc(
270 context: &Context,
271 configurable: &ConfigContent,
272 md_namer: &mut MetadataNamer,
273) -> Doc {
274 match configurable {
275 ConfigContent::V0 {
276 name,
277 constant,
278 opt_metadata,
279 ..
280 } => Doc::line(
281 Doc::text(format!(
282 "{} = config {}",
283 name,
284 constant.get_content(context).as_lit_string(context)
285 ))
286 .append(md_namer.md_idx_to_doc(context, opt_metadata)),
287 ),
288 ConfigContent::V1 {
289 name,
290 ty,
291 encoded_bytes,
292 decode_fn,
293 opt_metadata,
294 ..
295 } => {
296 let ty = ty.as_string(context);
297 let bytes = encoded_bytes
298 .iter()
299 .map(|b| format!("{b:02x}"))
300 .collect::<Vec<String>>()
301 .concat();
302 Doc::line(
303 Doc::text(format!(
304 "{} = config {}, {}, 0x{}",
305 name,
306 ty,
307 decode_fn.get().get_name(context),
308 bytes,
309 ))
310 .append(md_namer.md_idx_to_doc(context, opt_metadata)),
311 )
312 }
313 }
314}
315
316fn function_to_doc<'a>(
317 context: &'a Context,
318 md_namer: &mut MetadataNamer,
319 namer: &mut Namer,
320 function: &'a FunctionContent,
321 map_doc: &impl Fn(&Value, Doc) -> Doc,
322) -> Doc {
323 let public = if function.is_public { "pub " } else { "" };
324 let entry = if function.is_entry { "entry " } else { "" };
325 let original_entry = if context.experimental.new_encoding {
334 if function.is_original_entry {
335 "entry_orig "
336 } else {
337 ""
338 }
339 } else if !function.is_entry && function.is_original_entry {
340 "entry_orig "
341 } else {
342 ""
343 };
344 let fallback = if function.is_fallback {
345 "fallback "
346 } else {
347 ""
348 };
349 Doc::line(
350 Doc::text(format!(
351 "{}{}{}{}fn {}",
352 public, entry, original_entry, fallback, function.name
353 ))
354 .append(
355 function
356 .selector
357 .map(|bytes| {
358 Doc::text(format!(
359 "<{:02x}{:02x}{:02x}{:02x}>",
360 bytes[0], bytes[1], bytes[2], bytes[3]
361 ))
362 })
363 .unwrap_or(Doc::Empty),
364 )
365 .append(Doc::in_parens_comma_sep(
366 function
367 .arguments
368 .iter()
369 .map(|(name, arg_val)| {
370 if let ValueContent {
371 value: ValueDatum::Argument(BlockArgument { ty, .. }),
372 metadata,
373 ..
374 } = &context.values[arg_val.0]
375 {
376 Doc::text(name)
377 .append(
378 Doc::Space.and(md_namer.md_idx_to_doc_no_comma(context, metadata)),
379 )
380 .append(Doc::text(format!(": {}", ty.as_string(context))))
381 } else {
382 unreachable!("Unexpected non argument value for function arguments.")
383 }
384 })
385 .collect(),
386 ))
387 .append(Doc::text(format!(
388 " -> {}",
389 function.return_type.as_string(context)
390 )))
391 .append(md_namer.md_idx_to_doc(context, &function.metadata))
392 .append(Doc::text(" {")),
393 )
394 .append(Doc::indent(
395 4,
396 Doc::list_sep(
397 vec![
398 Doc::List(
399 function
400 .local_storage
401 .iter()
402 .map(|(name, var)| {
403 let var_content = &context.local_vars[var.0];
404 let init_doc = match &var_content.initializer {
405 Some(const_val) => Doc::text(format!(
406 " = const {}",
407 const_val.get_content(context).as_lit_string(context)
408 )),
409 None => Doc::Empty,
410 };
411 let mut_str = if var_content.mutable { "mut " } else { "" };
412 Doc::line(
413 Doc::text(format!(
415 "local {mut_str}{} {name}",
416 var.get_inner_type(context).as_string(context)
417 ))
418 .append(init_doc),
419 )
420 })
421 .collect(),
422 ),
423 Doc::list_sep(
424 function
425 .blocks
426 .iter()
427 .map(|block| block_to_doc(context, md_namer, namer, block, map_doc))
428 .collect(),
429 Doc::line(Doc::Empty),
430 ),
431 ],
432 Doc::line(Doc::Empty),
433 ),
434 ))
435 .append(Doc::text_line("}"))
436}
437
438fn block_to_doc(
439 context: &Context,
440 md_namer: &mut MetadataNamer,
441 namer: &mut Namer,
442 block: &Block,
443 map_doc: &impl Fn(&Value, Doc) -> Doc,
444) -> Doc {
445 let block_content = &context.blocks[block.0];
446 Doc::line(
447 Doc::text(block_content.label.to_string()).append(
448 Doc::in_parens_comma_sep(
449 block
450 .arg_iter(context)
451 .map(|arg_val| {
452 Doc::text(namer.name(context, arg_val)).append(Doc::text(format!(
453 ": {}",
454 arg_val.get_type(context).unwrap().as_string(context)
455 )))
456 })
457 .collect(),
458 )
459 .append(Doc::Text(":".to_string())),
460 ),
461 )
462 .append(Doc::List(
463 block
464 .instruction_iter(context)
465 .map(|current_value| {
466 let doc = instruction_to_doc(context, md_namer, namer, block, ¤t_value);
467 (map_doc)(¤t_value, doc)
468 })
469 .collect(),
470 ))
471}
472
473fn constant_to_doc(
474 context: &Context,
475 md_namer: &mut MetadataNamer,
476 namer: &mut Namer,
477 const_val: &Value,
478) -> Doc {
479 if let ValueContent {
480 value: ValueDatum::Constant(constant),
481 metadata,
482 } = &context.values[const_val.0]
483 {
484 Doc::line(
485 Doc::text(format!(
486 "{} = const {}",
487 namer.name(context, const_val),
488 constant.get_content(context).as_lit_string(context)
489 ))
490 .append(md_namer.md_idx_to_doc(context, metadata)),
491 )
492 } else {
493 unreachable!("Not a constant value.")
494 }
495}
496
497fn maybe_constant_to_doc(
498 context: &Context,
499 md_namer: &mut MetadataNamer,
500 namer: &mut Namer,
501 maybe_const_val: &Value,
502) -> Doc {
503 if !namer.is_known(maybe_const_val) && maybe_const_val.is_constant(context) {
505 constant_to_doc(context, md_namer, namer, maybe_const_val)
506 } else {
507 Doc::Empty
508 }
509}
510
511fn instruction_to_doc<'a>(
512 context: &'a Context,
513 md_namer: &mut MetadataNamer,
514 namer: &mut Namer,
515 block: &Block,
516 ins_value: &'a Value,
517) -> Doc {
518 if let ValueContent {
519 value: ValueDatum::Instruction(instruction),
520 metadata,
521 } = &context.values[ins_value.0]
522 {
523 match &instruction.op {
524 InstOp::AsmBlock(asm, args) => {
525 asm_block_to_doc(context, md_namer, namer, ins_value, asm, args, metadata)
526 }
527 InstOp::BitCast(value, ty) => maybe_constant_to_doc(context, md_namer, namer, value)
528 .append(Doc::line(
529 Doc::text(format!(
530 "{} = bitcast {} to {}",
531 namer.name(context, ins_value),
532 namer.name(context, value),
533 ty.as_string(context),
534 ))
535 .append(md_namer.md_idx_to_doc(context, metadata)),
536 )),
537 InstOp::UnaryOp { op, arg } => {
538 let op_str = match op {
539 UnaryOpKind::Not => "not",
540 };
541 maybe_constant_to_doc(context, md_namer, namer, arg).append(Doc::line(
542 Doc::text(format!(
543 "{} = {op_str} {}",
544 namer.name(context, ins_value),
545 namer.name(context, arg),
546 ))
547 .append(md_namer.md_idx_to_doc(context, metadata)),
548 ))
549 }
550 InstOp::BinaryOp { op, arg1, arg2 } => {
551 let op_str = match op {
552 BinaryOpKind::Add => "add",
553 BinaryOpKind::Sub => "sub",
554 BinaryOpKind::Mul => "mul",
555 BinaryOpKind::Div => "div",
556 BinaryOpKind::And => "and",
557 BinaryOpKind::Or => "or",
558 BinaryOpKind::Xor => "xor",
559 BinaryOpKind::Mod => "mod",
560 BinaryOpKind::Rsh => "rsh",
561 BinaryOpKind::Lsh => "lsh",
562 };
563 maybe_constant_to_doc(context, md_namer, namer, arg1)
564 .append(maybe_constant_to_doc(context, md_namer, namer, arg2))
565 .append(Doc::line(
566 Doc::text(format!(
567 "{} = {op_str} {}, {}",
568 namer.name(context, ins_value),
569 namer.name(context, arg1),
570 namer.name(context, arg2),
571 ))
572 .append(md_namer.md_idx_to_doc(context, metadata)),
573 ))
574 }
575 InstOp::Branch(to_block) =>
576 {
578 to_block
579 .args
580 .iter()
581 .fold(Doc::Empty, |doc, param| {
582 doc.append(maybe_constant_to_doc(context, md_namer, namer, param))
583 })
584 .append(Doc::line(
585 Doc::text(format!("br {}", context.blocks[to_block.block.0].label,))
586 .append(
587 Doc::in_parens_comma_sep(
588 to_block
589 .args
590 .iter()
591 .map(|arg_val| Doc::text(namer.name(context, arg_val)))
592 .collect(),
593 )
594 .append(md_namer.md_idx_to_doc(context, metadata)),
595 ),
596 ))
597 }
598 InstOp::Call(func, args) => args
599 .iter()
600 .fold(Doc::Empty, |doc, arg_val| {
601 doc.append(maybe_constant_to_doc(context, md_namer, namer, arg_val))
602 })
603 .append(Doc::line(
604 Doc::text(format!(
605 "{} = call {}",
606 namer.name(context, ins_value),
607 context.functions[func.0].name
608 ))
609 .append(Doc::in_parens_comma_sep(
610 args.iter()
611 .map(|arg_val| Doc::text(namer.name(context, arg_val)))
612 .collect(),
613 ))
614 .append(md_namer.md_idx_to_doc(context, metadata)),
615 )),
616 InstOp::CastPtr(val, ty) => Doc::line(
617 Doc::text(format!(
618 "{} = cast_ptr {} to {}",
619 namer.name(context, ins_value),
620 namer.name(context, val),
621 ty.as_string(context)
622 ))
623 .append(md_namer.md_idx_to_doc(context, metadata)),
624 ),
625 InstOp::Cmp(pred, lhs_value, rhs_value) => {
626 let pred_str = match pred {
627 Predicate::Equal => "eq",
628 Predicate::LessThan => "lt",
629 Predicate::GreaterThan => "gt",
630 };
631 maybe_constant_to_doc(context, md_namer, namer, lhs_value)
632 .append(maybe_constant_to_doc(context, md_namer, namer, rhs_value))
633 .append(Doc::line(
634 Doc::text(format!(
635 "{} = cmp {pred_str} {} {}",
636 namer.name(context, ins_value),
637 namer.name(context, lhs_value),
638 namer.name(context, rhs_value),
639 ))
640 .append(md_namer.md_idx_to_doc(context, metadata)),
641 ))
642 }
643 InstOp::ConditionalBranch {
644 cond_value,
645 true_block,
646 false_block,
647 } => {
648 let true_label = &context.blocks[true_block.block.0].label;
649 let false_label = &context.blocks[false_block.block.0].label;
650 let doc = true_block.args.iter().fold(
652 maybe_constant_to_doc(context, md_namer, namer, cond_value),
653 |doc, param| doc.append(maybe_constant_to_doc(context, md_namer, namer, param)),
654 );
655 let doc = false_block.args.iter().fold(doc, |doc, param| {
656 doc.append(maybe_constant_to_doc(context, md_namer, namer, param))
657 });
658 doc.append(Doc::line(
659 Doc::text(format!("cbr {}", namer.name(context, cond_value),)).append(
660 Doc::text(format!(", {true_label}")).append(
661 Doc::in_parens_comma_sep(
662 true_block
663 .args
664 .iter()
665 .map(|arg_val| Doc::text(namer.name(context, arg_val)))
666 .collect(),
667 )
668 .append(
669 Doc::text(format!(", {false_label}")).append(
670 Doc::in_parens_comma_sep(
671 false_block
672 .args
673 .iter()
674 .map(|arg_val| Doc::text(namer.name(context, arg_val)))
675 .collect(),
676 )
677 .append(md_namer.md_idx_to_doc(context, metadata)),
678 ),
679 ),
680 ),
681 ),
682 ))
683 }
684 InstOp::ContractCall {
685 return_type,
686 name,
687 params,
688 coins,
689 asset_id,
690 gas,
691 } => maybe_constant_to_doc(context, md_namer, namer, coins)
692 .append(maybe_constant_to_doc(context, md_namer, namer, asset_id))
693 .append(maybe_constant_to_doc(context, md_namer, namer, gas))
694 .append(Doc::line(
695 Doc::text(format!(
696 "{} = contract_call {} {} {}, {}, {}, {}",
697 namer.name(context, ins_value),
698 return_type.as_string(context),
699 name.as_deref().unwrap_or(""),
700 namer.name(context, params),
701 namer.name(context, coins),
702 namer.name(context, asset_id),
703 namer.name(context, gas),
704 ))
705 .append(md_namer.md_idx_to_doc(context, metadata)),
706 )),
707 InstOp::FuelVm(fuel_vm_instr) => match fuel_vm_instr {
708 FuelVmInstruction::Gtf { index, tx_field_id } => {
709 maybe_constant_to_doc(context, md_namer, namer, index).append(Doc::line(
710 Doc::text(format!(
711 "{} = gtf {}, {}",
712 namer.name(context, ins_value),
713 namer.name(context, index),
714 tx_field_id,
715 ))
716 .append(md_namer.md_idx_to_doc(context, metadata)),
717 ))
718 }
719 FuelVmInstruction::Log {
720 log_val,
721 log_ty,
722 log_id,
723 } => maybe_constant_to_doc(context, md_namer, namer, log_val)
724 .append(maybe_constant_to_doc(context, md_namer, namer, log_id))
725 .append(Doc::line(
726 Doc::text(format!(
727 "log {} {}, {}",
728 log_ty.as_string(context),
729 namer.name(context, log_val),
730 namer.name(context, log_id),
731 ))
732 .append(md_namer.md_idx_to_doc(context, metadata)),
733 )),
734 FuelVmInstruction::ReadRegister(reg) => Doc::line(
735 Doc::text(format!(
736 "{} = read_register {}",
737 namer.name(context, ins_value),
738 match reg {
739 Register::Of => "of",
740 Register::Pc => "pc",
741 Register::Ssp => "ssp",
742 Register::Sp => "sp",
743 Register::Fp => "fp",
744 Register::Hp => "hp",
745 Register::Error => "err",
746 Register::Ggas => "ggas",
747 Register::Cgas => "cgas",
748 Register::Bal => "bal",
749 Register::Is => "is",
750 Register::Ret => "ret",
751 Register::Retl => "retl",
752 Register::Flag => "flag",
753 },
754 ))
755 .append(md_namer.md_idx_to_doc(context, metadata)),
756 ),
757 FuelVmInstruction::Revert(v) => maybe_constant_to_doc(context, md_namer, namer, v)
758 .append(Doc::line(
759 Doc::text(format!("revert {}", namer.name(context, v),))
760 .append(md_namer.md_idx_to_doc(context, metadata)),
761 )),
762 FuelVmInstruction::JmpMem => Doc::line(
763 Doc::text("jmp_mem".to_string())
764 .append(md_namer.md_idx_to_doc(context, metadata)),
765 ),
766 FuelVmInstruction::Smo {
767 recipient,
768 message,
769 message_size,
770 coins,
771 } => maybe_constant_to_doc(context, md_namer, namer, recipient)
772 .append(maybe_constant_to_doc(context, md_namer, namer, message))
773 .append(maybe_constant_to_doc(
774 context,
775 md_namer,
776 namer,
777 message_size,
778 ))
779 .append(maybe_constant_to_doc(context, md_namer, namer, coins))
780 .append(Doc::line(
781 Doc::text(format!(
782 "smo {}, {}, {}, {}",
783 namer.name(context, recipient),
784 namer.name(context, message),
785 namer.name(context, message_size),
786 namer.name(context, coins),
787 ))
788 .append(md_namer.md_idx_to_doc(context, metadata)),
789 )),
790 FuelVmInstruction::StateClear {
791 key,
792 number_of_slots,
793 } => maybe_constant_to_doc(context, md_namer, namer, number_of_slots).append(
794 Doc::line(
795 Doc::text(format!(
796 "state_clear key {}, {}",
797 namer.name(context, key),
798 namer.name(context, number_of_slots),
799 ))
800 .append(md_namer.md_idx_to_doc(context, metadata)),
801 ),
802 ),
803 FuelVmInstruction::StateLoadQuadWord {
804 load_val,
805 key,
806 number_of_slots,
807 } => maybe_constant_to_doc(context, md_namer, namer, number_of_slots).append(
808 Doc::line(
809 Doc::text(format!(
810 "{} = state_load_quad_word {}, key {}, {}",
811 namer.name(context, ins_value),
812 namer.name(context, load_val),
813 namer.name(context, key),
814 namer.name(context, number_of_slots),
815 ))
816 .append(md_namer.md_idx_to_doc(context, metadata)),
817 ),
818 ),
819 FuelVmInstruction::StateLoadWord(key) => Doc::line(
820 Doc::text(format!(
821 "{} = state_load_word key {}",
822 namer.name(context, ins_value),
823 namer.name(context, key),
824 ))
825 .append(md_namer.md_idx_to_doc(context, metadata)),
826 ),
827 FuelVmInstruction::StateStoreQuadWord {
828 stored_val,
829 key,
830 number_of_slots,
831 } => maybe_constant_to_doc(context, md_namer, namer, number_of_slots).append(
832 Doc::line(
833 Doc::text(format!(
834 "{} = state_store_quad_word {}, key {}, {}",
835 namer.name(context, ins_value),
836 namer.name(context, stored_val),
837 namer.name(context, key),
838 namer.name(context, number_of_slots),
839 ))
840 .append(md_namer.md_idx_to_doc(context, metadata)),
841 ),
842 ),
843 FuelVmInstruction::StateStoreWord { stored_val, key } => {
844 maybe_constant_to_doc(context, md_namer, namer, stored_val).append(Doc::line(
845 Doc::text(format!(
846 "{} = state_store_word {}, key {}",
847 namer.name(context, ins_value),
848 namer.name(context, stored_val),
849 namer.name(context, key),
850 ))
851 .append(md_namer.md_idx_to_doc(context, metadata)),
852 ))
853 }
854 FuelVmInstruction::WideUnaryOp { op, arg, result } => {
855 let op_str = match op {
856 UnaryOpKind::Not => "not",
857 };
858 maybe_constant_to_doc(context, md_namer, namer, arg).append(Doc::line(
859 Doc::text(format!(
860 "wide {op_str} {} to {}",
861 namer.name(context, arg),
862 namer.name(context, result),
863 ))
864 .append(md_namer.md_idx_to_doc(context, metadata)),
865 ))
866 }
867 FuelVmInstruction::WideBinaryOp {
868 op,
869 arg1,
870 arg2,
871 result,
872 } => {
873 let op_str = match op {
874 BinaryOpKind::Add => "add",
875 BinaryOpKind::Sub => "sub",
876 BinaryOpKind::Mul => "mul",
877 BinaryOpKind::Div => "div",
878 BinaryOpKind::And => "and",
879 BinaryOpKind::Or => "or",
880 BinaryOpKind::Xor => "xor",
881 BinaryOpKind::Mod => "mod",
882 BinaryOpKind::Rsh => "rsh",
883 BinaryOpKind::Lsh => "lsh",
884 };
885 maybe_constant_to_doc(context, md_namer, namer, arg1)
886 .append(maybe_constant_to_doc(context, md_namer, namer, arg2))
887 .append(Doc::line(
888 Doc::text(format!(
889 "wide {op_str} {}, {} to {}",
890 namer.name(context, arg1),
891 namer.name(context, arg2),
892 namer.name(context, result),
893 ))
894 .append(md_namer.md_idx_to_doc(context, metadata)),
895 ))
896 }
897 FuelVmInstruction::WideModularOp {
898 op,
899 result,
900 arg1,
901 arg2,
902 arg3,
903 } => {
904 let op_str = match op {
905 BinaryOpKind::Mod => "mod",
906 _ => unreachable!(),
907 };
908 maybe_constant_to_doc(context, md_namer, namer, arg1)
909 .append(maybe_constant_to_doc(context, md_namer, namer, arg2))
910 .append(maybe_constant_to_doc(context, md_namer, namer, arg3))
911 .append(Doc::line(
912 Doc::text(format!(
913 "wide {op_str} {}, {}, {} to {}",
914 namer.name(context, arg1),
915 namer.name(context, arg2),
916 namer.name(context, arg3),
917 namer.name(context, result),
918 ))
919 .append(md_namer.md_idx_to_doc(context, metadata)),
920 ))
921 }
922 FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => {
923 let pred_str = match op {
924 Predicate::Equal => "eq",
925 Predicate::LessThan => "lt",
926 Predicate::GreaterThan => "gt",
927 };
928 maybe_constant_to_doc(context, md_namer, namer, arg1)
929 .append(maybe_constant_to_doc(context, md_namer, namer, arg2))
930 .append(Doc::line(
931 Doc::text(format!(
932 "{} = wide cmp {pred_str} {} {}",
933 namer.name(context, ins_value),
934 namer.name(context, arg1),
935 namer.name(context, arg2),
936 ))
937 .append(md_namer.md_idx_to_doc(context, metadata)),
938 ))
939 }
940 FuelVmInstruction::Retd { ptr, len } => {
941 maybe_constant_to_doc(context, md_namer, namer, ptr)
942 .append(maybe_constant_to_doc(context, md_namer, namer, len))
943 .append(Doc::line(
944 Doc::text(format!(
945 "retd {} {}",
946 namer.name(context, ptr),
947 namer.name(context, len),
948 ))
949 .append(md_namer.md_idx_to_doc(context, metadata)),
950 ))
951 }
952 },
953 InstOp::GetElemPtr {
954 base,
955 elem_ptr_ty,
956 indices,
957 } => indices
958 .iter()
959 .fold(Doc::Empty, |acc, idx| {
960 acc.append(maybe_constant_to_doc(context, md_namer, namer, idx))
961 })
962 .append(Doc::line(
963 Doc::text(format!(
964 "{} = get_elem_ptr {}, {}, ",
965 namer.name(context, ins_value),
966 namer.name(context, base),
967 elem_ptr_ty.as_string(context),
968 ))
969 .append(Doc::list_sep(
970 indices
971 .iter()
972 .map(|idx| Doc::text(namer.name(context, idx)))
973 .collect(),
974 Doc::Comma,
975 ))
976 .append(md_namer.md_idx_to_doc(context, metadata)),
977 )),
978 InstOp::GetLocal(local_var) => {
979 let name = block
980 .get_function(context)
981 .lookup_local_name(context, local_var)
982 .unwrap();
983 Doc::line(
984 Doc::text(format!(
985 "{} = get_local {}, {name}",
986 namer.name(context, ins_value),
987 local_var.get_type(context).as_string(context),
988 ))
989 .append(md_namer.md_idx_to_doc(context, metadata)),
990 )
991 }
992 InstOp::GetGlobal(global_var) => {
993 let name = block
994 .get_function(context)
995 .get_module(context)
996 .lookup_global_variable_name(context, global_var)
997 .unwrap();
998 Doc::line(
999 Doc::text(format!(
1000 "{} = get_global {}, {name}",
1001 namer.name(context, ins_value),
1002 global_var.get_type(context).as_string(context),
1003 ))
1004 .append(md_namer.md_idx_to_doc(context, metadata)),
1005 )
1006 }
1007 InstOp::GetConfig(_, name) => Doc::line(
1008 match block.get_module(context).get_config(context, name).unwrap() {
1009 ConfigContent::V0 { name, ptr_ty, .. }
1010 | ConfigContent::V1 { name, ptr_ty, .. } => Doc::text(format!(
1011 "{} = get_config {}, {}",
1012 namer.name(context, ins_value),
1013 ptr_ty.as_string(context),
1014 name,
1015 )),
1016 }
1017 .append(md_namer.md_idx_to_doc(context, metadata)),
1018 ),
1019 InstOp::IntToPtr(value, ty) => maybe_constant_to_doc(context, md_namer, namer, value)
1020 .append(Doc::line(
1021 Doc::text(format!(
1022 "{} = int_to_ptr {} to {}",
1023 namer.name(context, ins_value),
1024 namer.name(context, value),
1025 ty.as_string(context),
1026 ))
1027 .append(md_namer.md_idx_to_doc(context, metadata)),
1028 )),
1029 InstOp::Load(src_value) => Doc::line(
1030 Doc::text(format!(
1031 "{} = load {}",
1032 namer.name(context, ins_value),
1033 namer.name(context, src_value),
1034 ))
1035 .append(md_namer.md_idx_to_doc(context, metadata)),
1036 ),
1037 InstOp::MemCopyBytes {
1038 dst_val_ptr,
1039 src_val_ptr,
1040 byte_len,
1041 } => Doc::line(
1042 Doc::text(format!(
1043 "mem_copy_bytes {}, {}, {}",
1044 namer.name(context, dst_val_ptr),
1045 namer.name(context, src_val_ptr),
1046 byte_len,
1047 ))
1048 .append(md_namer.md_idx_to_doc(context, metadata)),
1049 ),
1050 InstOp::MemCopyVal {
1051 dst_val_ptr,
1052 src_val_ptr,
1053 } => Doc::line(
1054 Doc::text(format!(
1055 "mem_copy_val {}, {}",
1056 namer.name(context, dst_val_ptr),
1057 namer.name(context, src_val_ptr),
1058 ))
1059 .append(md_namer.md_idx_to_doc(context, metadata)),
1060 ),
1061 InstOp::Nop => Doc::line(
1062 Doc::text(format!("{} = nop", namer.name(context, ins_value)))
1063 .append(md_namer.md_idx_to_doc(context, metadata)),
1064 ),
1065 InstOp::PtrToInt(value, ty) => maybe_constant_to_doc(context, md_namer, namer, value)
1066 .append(Doc::line(
1067 Doc::text(format!(
1068 "{} = ptr_to_int {} to {}",
1069 namer.name(context, ins_value),
1070 namer.name(context, value),
1071 ty.as_string(context),
1072 ))
1073 .append(md_namer.md_idx_to_doc(context, metadata)),
1074 )),
1075 InstOp::Ret(v, t) => {
1076 maybe_constant_to_doc(context, md_namer, namer, v).append(Doc::line(
1077 Doc::text(format!(
1078 "ret {} {}",
1079 t.as_string(context),
1080 namer.name(context, v),
1081 ))
1082 .append(md_namer.md_idx_to_doc(context, metadata)),
1083 ))
1084 }
1085 InstOp::Store {
1086 dst_val_ptr,
1087 stored_val,
1088 } => maybe_constant_to_doc(context, md_namer, namer, stored_val).append(Doc::line(
1089 Doc::text(format!(
1090 "store {} to {}",
1091 namer.name(context, stored_val),
1092 namer.name(context, dst_val_ptr),
1093 ))
1094 .append(md_namer.md_idx_to_doc(context, metadata)),
1095 )),
1096 }
1097 } else {
1098 unreachable!("Unexpected non instruction for block contents.")
1099 }
1100}
1101
1102fn asm_block_to_doc(
1103 context: &Context,
1104 md_namer: &mut MetadataNamer,
1105 namer: &mut Namer,
1106 ins_value: &Value,
1107 asm: &AsmBlock,
1108 args: &[AsmArg],
1109 metadata: &Option<MetadataIndex>,
1110) -> Doc {
1111 let AsmBlock {
1112 body,
1113 return_type,
1114 return_name,
1115 ..
1116 } = &asm;
1117 args.iter()
1118 .fold(
1119 Doc::Empty,
1120 |doc, AsmArg { initializer, .. }| match initializer {
1121 Some(init_val) if init_val.is_constant(context) => {
1122 doc.append(maybe_constant_to_doc(context, md_namer, namer, init_val))
1123 }
1124 _otherwise => doc,
1125 },
1126 )
1127 .append(Doc::line(
1128 Doc::text(format!("{} = asm", namer.name(context, ins_value)))
1129 .append(Doc::in_parens_comma_sep(
1130 args.iter()
1131 .map(|AsmArg { name, initializer }| {
1132 Doc::text(name.as_str()).append(match initializer {
1133 Some(init_val) => {
1134 Doc::text(format!(": {}", namer.name(context, init_val)))
1135 }
1136 None => Doc::Empty,
1137 })
1138 })
1139 .collect(),
1140 ))
1141 .append(
1142 Doc::text(format!(
1143 " -> {}{}",
1144 return_type.as_string(context),
1145 return_name
1146 .as_ref()
1147 .map_or("".to_string(), |rn| format!(" {rn}"))
1148 ))
1149 .append(md_namer.md_idx_to_doc(context, metadata)),
1150 )
1151 .append(Doc::text(" {")),
1152 ))
1153 .append(Doc::indent(
1154 4,
1155 Doc::List(
1156 body.iter()
1157 .map(
1158 |AsmInstruction {
1159 op_name: name,
1160 args,
1161 immediate,
1162 metadata,
1163 }| {
1164 Doc::line(
1165 Doc::text(format!("{:6} ", name.as_str())).append(
1166 Doc::list_sep(
1167 args.iter().map(|arg| Doc::text(arg.as_str())).collect(),
1168 Doc::text(" "),
1169 )
1170 .append(match immediate {
1171 Some(imm_str) => Doc::text(format!(" {imm_str}")),
1172 None => Doc::Empty,
1173 })
1174 .append(md_namer.md_idx_to_doc(context, metadata)),
1175 ),
1176 )
1177 },
1178 )
1179 .collect(),
1180 ),
1181 ))
1182 .append(Doc::text_line("}"))
1183}
1184
1185impl ConstantContent {
1186 fn as_lit_string(&self, context: &Context) -> String {
1187 match &self.value {
1188 ConstantValue::Undef => format!("{} undef", self.ty.as_string(context)),
1189 ConstantValue::Unit => "unit ()".into(),
1190 ConstantValue::Bool(b) => format!("bool {}", if *b { "true" } else { "false" }),
1191 ConstantValue::Uint(v) => format!("{} {}", self.ty.as_string(context), v),
1192 ConstantValue::U256(v) => {
1193 let bytes = v.to_be_bytes();
1194 format!(
1195 "u256 0x{}",
1196 bytes
1197 .iter()
1198 .map(|b| format!("{b:02x}"))
1199 .collect::<Vec<String>>()
1200 .concat()
1201 )
1202 }
1203 ConstantValue::B256(v) => {
1204 let bytes = v.to_be_bytes();
1205 format!(
1206 "b256 0x{}",
1207 bytes
1208 .iter()
1209 .map(|b| format!("{b:02x}"))
1210 .collect::<Vec<String>>()
1211 .concat()
1212 )
1213 }
1214 ConstantValue::String(bs) => format!(
1215 "{} \"{}\"",
1216 self.ty.as_string(context),
1217 bs.iter()
1218 .map(
1219 |b| if b.is_ascii() && !b.is_ascii_control() && *b != b'\\' && *b != b'"' {
1220 format!("{}", *b as char)
1221 } else {
1222 format!("\\x{b:02x}")
1223 }
1224 )
1225 .collect::<Vec<_>>()
1226 .join("")
1227 ),
1228 ConstantValue::Array(elems) => format!(
1229 "{} [{}]",
1230 self.ty.as_string(context),
1231 elems
1232 .iter()
1233 .map(|elem| elem.as_lit_string(context))
1234 .collect::<Vec<String>>()
1235 .join(", ")
1236 ),
1237 ConstantValue::Slice(elems) => format!(
1238 "__slice[{}] [{}]",
1239 self.ty.as_string(context),
1240 elems
1241 .iter()
1242 .map(|elem| elem.as_lit_string(context))
1243 .collect::<Vec<String>>()
1244 .join(", ")
1245 ),
1246 ConstantValue::Struct(fields) => format!(
1247 "{} {{ {} }}",
1248 self.ty.as_string(context),
1249 fields
1250 .iter()
1251 .map(|field| field.as_lit_string(context))
1252 .collect::<Vec<String>>()
1253 .join(", ")
1254 ),
1255 ConstantValue::Reference(constant) => format!("&({})", constant.as_lit_string(context)),
1256 ConstantValue::RawUntypedSlice(bytes) => {
1257 format!(
1258 "{} 0x{}",
1259 self.ty.as_string(context),
1260 bytes
1261 .iter()
1262 .map(|b| format!("{b:02x}"))
1263 .collect::<Vec<String>>()
1264 .concat()
1265 )
1266 }
1267 }
1268 }
1269}
1270
1271struct Namer {
1272 function: Function,
1273 names: HashMap<Value, String>,
1274 next_value_idx: u64,
1275}
1276
1277impl Namer {
1278 fn new(function: Function) -> Self {
1279 Namer {
1280 function,
1281 names: HashMap::new(),
1282 next_value_idx: 0,
1283 }
1284 }
1285
1286 fn name(&mut self, context: &Context, value: &Value) -> String {
1287 match &context.values[value.0].value {
1288 ValueDatum::Argument(_) => self
1289 .function
1290 .lookup_arg_name(context, value)
1291 .cloned()
1292 .unwrap_or_else(|| self.default_name(value)),
1293 ValueDatum::Constant(_) => self.default_name(value),
1294 ValueDatum::Instruction(_) => self.default_name(value),
1295 }
1296 }
1297
1298 fn default_name(&mut self, value: &Value) -> String {
1299 self.names.get(value).cloned().unwrap_or_else(|| {
1300 let new_name = format!("v{}", self.next_value_idx);
1301 self.next_value_idx += 1;
1302 self.names.insert(*value, new_name.clone());
1303 new_name
1304 })
1305 }
1306
1307 fn is_known(&self, value: &Value) -> bool {
1308 self.names.contains_key(value)
1309 }
1310}
1311
1312#[derive(Default)]
1313struct MetadataNamer {
1314 md_map: BTreeMap<MetadataIndex, u64>,
1315 next_md_idx: u64,
1316}
1317
1318impl MetadataNamer {
1319 fn values_sorted(&self) -> impl Iterator<Item = (u64, MetadataIndex)> {
1320 let mut items = self
1321 .md_map
1322 .clone()
1323 .into_iter()
1324 .map(|(a, b)| (b, a))
1325 .collect::<Vec<_>>();
1326 items.sort_unstable();
1327 items.into_iter()
1328 }
1329
1330 fn get(&self, md_idx: &MetadataIndex) -> Option<u64> {
1331 self.md_map.get(md_idx).copied()
1332 }
1333
1334 fn md_idx_to_doc_no_comma(&mut self, context: &Context, md_idx: &Option<MetadataIndex>) -> Doc {
1343 md_idx
1344 .map(|md_idx| Doc::text(format!("!{}", self.add_md_idx(context, &md_idx))))
1345 .unwrap_or(Doc::Empty)
1346 }
1347
1348 fn md_idx_to_doc(&mut self, context: &Context, md_idx: &Option<MetadataIndex>) -> Doc {
1349 Doc::Comma.and(self.md_idx_to_doc_no_comma(context, md_idx))
1350 }
1351
1352 fn add_md_idx(&mut self, context: &Context, md_idx: &MetadataIndex) -> u64 {
1353 self.md_map.get(md_idx).copied().unwrap_or_else(|| {
1354 self.add_md(context, &context.metadata[md_idx.0]);
1356
1357 let new_idx = self.next_md_idx;
1359 self.next_md_idx += 1;
1360 self.md_map.insert(*md_idx, new_idx);
1361 new_idx
1362 })
1363 }
1364
1365 fn add_md(&mut self, context: &Context, md: &Metadatum) {
1366 match md {
1367 Metadatum::Integer(_) | Metadatum::String(_) | Metadatum::SourceId(_) => (),
1368 Metadatum::Index(idx) => {
1369 let _ = self.add_md_idx(context, idx);
1370 }
1371 Metadatum::Struct(_tag, els) => {
1372 for el in els {
1373 self.add_md(context, el);
1374 }
1375 }
1376 Metadatum::List(idcs) => {
1377 for idx in idcs {
1378 self.add_md_idx(context, idx);
1379 }
1380 }
1381 }
1382 }
1383
1384 fn to_doc(&self, context: &Context) -> Doc {
1385 fn md_to_string(
1386 md_namer: &MetadataNamer,
1387 md: &Metadatum,
1388 source_engine: &SourceEngine,
1389 ) -> String {
1390 match md {
1391 Metadatum::Integer(i) => i.to_string(),
1392 Metadatum::Index(idx) => format!(
1393 "!{}",
1394 md_namer
1395 .get(idx)
1396 .unwrap_or_else(|| panic!("Metadata index ({idx:?}) not found in namer."))
1397 ),
1398 Metadatum::String(s) => format!("{s:?}"),
1399 Metadatum::SourceId(id) => {
1400 let path = source_engine.get_path(id);
1401 format!("{path:?}")
1402 }
1403 Metadatum::Struct(tag, els) => {
1404 format!(
1405 "{tag} {}",
1406 els.iter()
1407 .map(|el_md| md_to_string(md_namer, el_md, source_engine))
1408 .collect::<Vec<_>>()
1409 .join(" ")
1410 )
1411 }
1412 Metadatum::List(idcs) => {
1413 format!(
1414 "({})",
1415 idcs.iter()
1416 .map(|idx| format!(
1417 "!{}",
1418 md_namer.get(idx).unwrap_or_else(|| panic!(
1419 "Metadata list index ({idx:?}) not found in namer."
1420 ))
1421 ))
1422 .collect::<Vec<_>>()
1423 .join(" ")
1424 )
1425 }
1426 }
1427 }
1428
1429 let md_lines = self
1430 .values_sorted()
1431 .map(|(ref_idx, md_idx)| {
1432 Doc::text_line(format!(
1433 "!{ref_idx} = {}",
1434 md_to_string(self, &context.metadata[md_idx.0], context.source_engine)
1435 ))
1436 })
1437 .collect::<Vec<_>>();
1438
1439 if md_lines.is_empty() {
1441 Doc::Empty
1442 } else {
1443 Doc::line(Doc::Empty).append(Doc::List(md_lines))
1444 }
1445 }
1446}
1447
1448fn build_doc(doc: Doc, indent: i64) -> String {
1450 match doc {
1451 Doc::Empty => "".into(),
1452 Doc::Space => " ".into(),
1453 Doc::Comma => ", ".into(),
1454
1455 Doc::Text(t) => t,
1456 Doc::Line(d) => {
1457 if matches!(*d, Doc::Empty) {
1458 "\n".into()
1459 } else {
1460 format!("{}{}\n", " ".repeat(indent as usize), build_doc(*d, indent))
1461 }
1462 }
1463
1464 Doc::Pair(l, r) => [build_doc(*l, indent), build_doc(*r, indent)].concat(),
1465
1466 Doc::List(v) => v
1467 .into_iter()
1468 .map(|d| build_doc(d, indent))
1469 .collect::<Vec<String>>()
1470 .concat(),
1471 Doc::ListSep(v, s) => v
1472 .into_iter()
1473 .filter_map(|d| match &d {
1474 Doc::Empty => None,
1475 Doc::List(vs) => {
1476 if vs.is_empty() {
1477 None
1478 } else {
1479 Some(build_doc(d, indent))
1480 }
1481 }
1482 _ => Some(build_doc(d, indent)),
1483 })
1484 .collect::<Vec<String>>()
1485 .join(&build_doc(*s, indent)),
1486
1487 Doc::Parens(d) => format!("({})", build_doc(*d, indent)),
1488
1489 Doc::Indent(n, d) => build_doc(*d, indent + n),
1490 }
1491}