1use std::fmt::Write as _;
50
51use crate::inst::{ArithOp, CmpOp, Compare, Convert, Inst, Len, Num, Slot};
52use crate::layout::{LayoutId, Shape};
53use crate::program::{Function, FunctionId, Program};
54
55pub fn program(program: &Program) -> String {
57 let mut out = String::new();
58 for index in 0..program.functions.len() {
59 if index > 0 {
60 out.push('\n');
61 }
62 out.push_str(&function(program, FunctionId(index as u32)));
63 }
64 out
65}
66
67pub fn function(program: &Program, id: FunctionId) -> String {
70 let f = program.function(id);
71 let mut out = String::new();
72 let params: Vec<String> = f
73 .params
74 .iter()
75 .map(|layout| name_of(program, *layout))
76 .collect();
77 let _ = writeln!(
78 out,
79 "fn @{}({}) -> {}{}",
80 f.qualified(),
81 params.join(" "),
82 name_of(program, f.returns),
83 if f.is_async { " async" } else { "" }
84 );
85 let taken = f.param_words(&program.layouts);
86 let _ = write!(out, " frame {}:", f.frame_size());
87 for (slot, repr) in f.reprs.iter().enumerate() {
88 let role = if (slot as u32) < taken { "!" } else { "" };
89 let _ = write!(out, " s{slot}{role}:{repr}");
90 }
91 out.push('\n');
92 for capture in &f.captures {
93 let _ = writeln!(
94 out,
95 " capture {} -> {}",
96 capture.name,
97 location(program, capture.slot, capture.layout)
98 );
99 }
100 for local in &f.locals {
101 let _ = writeln!(
102 out,
103 " local {} -> {} [{}, {})",
104 local.name,
105 location(program, local.slot, local.layout),
106 local.from,
107 local.to
108 );
109 }
110 for (pc, inst) in f.code.iter().enumerate() {
111 let _ = writeln!(out, " {pc:>4} {}", one(program, f, inst));
112 }
113 out
114}
115
116pub fn one(program: &Program, f: &Function, inst: &Inst) -> String {
118 let s = |slot: Slot| match f.repr(slot) {
119 Some(repr) => format!("s{slot}:{repr}"),
120 None => format!("s{slot}:?"),
121 };
122 let l = |layout: LayoutId| name_of(program, layout);
123 let v = |slot: Slot, layout: LayoutId| location(program, slot, layout);
126 match inst {
127 Inst::Unit { dst } => format!("unit {}", s(*dst)),
128 Inst::Bool { dst, value } => format!("bool {} {value}", s(*dst)),
129 Inst::Int { dst, value } => format!("int {} {value}", s(*dst)),
130 Inst::Tag { dst, layout, case } => {
134 format!("tag {} {}", s(*dst), case_name(program, *layout, *case))
135 }
136 Inst::FuncRef { dst, callee } => {
142 format!(
143 "func-ref {} @{}",
144 s(*dst),
145 program.function(*callee).qualified()
146 )
147 }
148 Inst::Float { dst, bits } => format!("float {} {}", s(*dst), f64::from_bits(*bits)),
149 Inst::Str { dst, text } => format!("str {} {:?}", s(*dst), program.string(*text)),
150 Inst::Copy { dst, src, layout } => {
151 format!("copy {} {}", v(*dst, *layout), v(*src, *layout))
152 }
153 Inst::Clear { slot, layout } => format!("clear {}", v(*slot, *layout)),
154 Inst::Neg { num, dst, a } => format!("neg.{} {} {}", num_name(*num), s(*dst), s(*a)),
155 Inst::Arith { num, op, dst, a, b } => format!(
156 "{}.{} {} {} {}",
157 arith_name(*op),
158 num_name(*num),
159 s(*dst),
160 s(*a),
161 s(*b)
162 ),
163 Inst::Cmp { on, op, dst, a, b } => format!(
164 "{}.{} {} {} {}",
165 cmp_name(*op),
166 compare_name(*on),
167 s(*dst),
168 s(*a),
169 s(*b)
170 ),
171 Inst::ArithImm { op, dst, a, value } => {
179 format!("{}.int.imm {} {} {value}", arith_name(*op), s(*dst), s(*a))
180 }
181 Inst::CmpImm { op, dst, a, value } => {
182 format!("{}.int.imm {} {} {value}", cmp_name(*op), s(*dst), s(*a))
183 }
184 Inst::Not { dst, a } => format!("not {} {}", s(*dst), s(*a)),
185 Inst::Convert { to, dst, a } => format!(
186 "{} {} {}",
187 match to {
188 Convert::IntToFloat => "int-to-float",
189 Convert::FloatToInt => "float-to-int",
190 },
191 s(*dst),
192 s(*a)
193 ),
194 Inst::Jump { to } => format!("jump {to}"),
195 Inst::BranchFalse { cond, to } => format!("branch-false {} {to}", s(*cond)),
196 Inst::Switch { on, table } => {
197 let table = program.table(*table);
198 let targets: Vec<String> = table.targets.iter().map(|to| to.to_string()).collect();
199 format!(
200 "switch {} [{}] else {}",
201 s(*on),
202 targets.join(" "),
203 table.default
204 )
205 }
206 Inst::Return { src } => format!("return {}", v(*src, f.returns)),
207 Inst::Call { dst, callee, args } => {
208 let target = program.function(*callee);
209 format!(
210 "call {} {} ({})",
211 v(*dst, target.returns),
212 target.qualified(),
213 args_of(program, *args)
214 )
215 }
216 Inst::CallClosure {
222 dst,
223 closure,
224 args,
225 result,
226 } => format!(
227 "call-closure {} {} ({})",
228 v(*dst, *result),
229 s(*closure),
230 args_of(program, *args)
231 ),
232 Inst::CallHost { dst, op, args } => {
233 let op = program.host_op(*op);
234 format!(
235 "call-host {} {} ({})",
236 v(*dst, op.result),
237 op.qualified(),
238 args_of(program, *args)
239 )
240 }
241 Inst::CallResource {
242 dst,
243 receiver,
244 op,
245 args,
246 } => {
247 let op = program.host_op(*op);
248 format!(
249 "call-resource {} {} {} ({})",
250 v(*dst, op.result),
251 s(*receiver),
252 op.qualified(),
253 args_of(program, *args)
254 )
255 }
256 Inst::CallBuiltin { dst, builtin, args } => {
257 let builtin = program.builtin(*builtin);
258 format!(
259 "call-builtin {} {}.{} ({})",
260 v(*dst, builtin.result),
261 builtin.receiver,
262 builtin.operation,
263 args_of(program, *args)
264 )
265 }
266 Inst::Alloc { dst, layout, len } => {
267 let shape = &program.layout(*layout).shape;
268 let len = match len {
269 Len::Fixed => String::new(),
270 Len::Count(n) => format!(" x{n}"),
271 Len::Slot(slot) => format!(" x{}", s(*slot)),
272 };
273 format!(
274 "alloc {} {}<{}>{len}",
275 s(*dst),
276 l(*layout),
277 shape_name(shape)
278 )
279 }
280 Inst::LoadField {
281 dst,
282 obj,
283 at,
284 layout,
285 } => format!("load-field {} {} +{at}", v(*dst, *layout), s(*obj)),
286 Inst::StoreField {
287 obj,
288 at,
289 src,
290 layout,
291 } => format!("store-field {} +{at} {}", s(*obj), v(*src, *layout)),
292 Inst::LoadElem {
293 dst,
294 obj,
295 index,
296 layout,
297 } => format!("load-elem {} {} {}", v(*dst, *layout), s(*obj), s(*index)),
298 Inst::StoreElem {
299 obj,
300 index,
301 src,
302 layout,
303 } => format!("store-elem {} {} {}", s(*obj), s(*index), v(*src, *layout)),
304 Inst::ByteAt { dst, obj, at } => {
305 format!("byte-at {} {} {}", s(*dst), s(*obj), s(*at))
306 }
307 Inst::AllocBytes { dst, len } => format!("alloc-bytes {} {}", s(*dst), s(*len)),
308 Inst::WriteByte { bytes, at, value } => {
309 format!("write-byte {} {} {}", s(*bytes), s(*at), s(*value))
310 }
311 Inst::CopyBytes { args } => format!("copy-bytes ({})", args_of(program, *args)),
312 Inst::FinishString { dst, bytes } => {
313 format!("finish-string {} {}", s(*dst), s(*bytes))
314 }
315 Inst::AllocBuffer { dst, capacity } => {
316 format!("alloc-buffer {} {}", s(*dst), s(*capacity))
317 }
318 Inst::AppendByte { buffer, value } => {
319 format!("append-byte {} {}", s(*buffer), s(*value))
320 }
321 Inst::AppendBytes { args } => format!("append-bytes ({})", args_of(program, *args)),
322 Inst::FinishBuffer { dst, buffer } => {
323 format!("finish-buffer {} {}", s(*dst), s(*buffer))
324 }
325 Inst::Len { dst, obj } => format!("len {} {}", s(*dst), s(*obj)),
326 Inst::LayoutOf { dst, obj } => format!("layout-of {} {}", s(*dst), s(*obj)),
327 Inst::AddrOfSlot { dst, slot } => format!("addr-of-slot {} {}", s(*dst), s(*slot)),
328 Inst::AddrOfField { dst, obj, at } => {
329 format!("addr-of-field {} {} +{at}", s(*dst), s(*obj))
330 }
331 Inst::AddrOfElem {
332 dst,
333 obj,
334 index,
335 layout,
336 } => format!(
337 "addr-of-elem {} {} {} {}",
338 s(*dst),
339 s(*obj),
340 s(*index),
341 l(*layout)
342 ),
343 Inst::AddrOfPart { dst, addr, at } => {
344 format!("addr-of-part {} {} +{at}", s(*dst), s(*addr))
345 }
346 Inst::Load { dst, addr, layout } => {
347 format!("load {} {}", v(*dst, *layout), s(*addr))
348 }
349 Inst::Store { addr, src, layout } => {
350 format!("store {} {}", s(*addr), v(*src, *layout))
351 }
352 Inst::Box { dst, src, layout } => format!("box {} {}", s(*dst), v(*src, *layout)),
353 Inst::Unbox { dst, src, layout } => {
354 format!("unbox {} {}", v(*dst, *layout), s(*src))
355 }
356 Inst::ScopeEnter { dst, name } => {
357 format!("scope.enter {} {:?}", s(*dst), program.string(*name))
358 }
359 Inst::ScopeLeave {
360 scope,
361 failed,
362 error,
363 layout,
364 } => format!(
365 "scope.leave {} {} {}",
366 s(*scope),
367 s(*failed),
368 v(*error, *layout)
369 ),
370 Inst::ScopeCancel { scope } => format!("scope.cancel {}", s(*scope)),
371 Inst::Spawn {
372 dst,
373 scope,
374 closure,
375 answer,
376 } => format!(
377 "spawn {} {} {} {}",
378 s(*dst),
379 s(*scope),
380 s(*closure),
381 l(*answer)
382 ),
383 Inst::Await { dst, task, answer } => {
384 format!("await {} {}", v(*dst, *answer), s(*task))
385 }
386 Inst::Cancel { task } => format!("cancel {}", s(*task)),
387 Inst::Settled { dst, src, answer } => {
388 format!("settled {} {}", s(*dst), v(*src, *answer))
389 }
390 Inst::SharedLock { cell } => format!("shared.lock {}", s(*cell)),
391 Inst::SharedUnlock { cell } => format!("shared.unlock {}", s(*cell)),
392 Inst::Trap { message } => format!("trap {:?}", program.string(*message)),
393 Inst::AssertFailed { message } => format!("assert.failed {}", s(*message)),
394 }
395}
396
397fn case_name(program: &Program, layout: LayoutId, case: crate::CaseId) -> String {
406 match program.layouts.get(layout.index()) {
407 Some(held) => match &held.shape {
408 crate::layout::Shape::Enum { cases, .. } => match cases.get(case.index()) {
409 Some(found) => format!("{}.{}", held.name, found.name),
410 None => format!("{}.{case}", held.name),
411 },
412 _ => format!("{}.{case}", held.name),
413 },
414 None => format!("{layout}.{case}"),
415 }
416}
417
418fn name_of(program: &Program, layout: LayoutId) -> String {
421 match program.layouts.get(layout.index()) {
422 Some(held) => held.name.to_string(),
423 None => layout.to_string(),
424 }
425}
426
427fn args_of(program: &Program, args: crate::ArgsId) -> String {
433 match program.args.get(args.index()) {
434 Some(list) => list
435 .iter()
436 .map(|arg| location(program, arg.slot, arg.layout))
437 .collect::<Vec<_>>()
438 .join(" "),
439 None => args.to_string(),
440 }
441}
442
443fn location(program: &Program, slot: Slot, layout: LayoutId) -> String {
462 let Some(held) = program.layouts.get(layout.index()) else {
463 return format!("s{slot}:{layout}");
464 };
465 match held.width() {
466 0 | 1 => format!("s{slot}:{}", held.name),
467 width => format!("s{slot}..s{}:{}", slot as u64 + width as u64 - 1, held.name),
470 }
471}
472
473fn num_name(num: Num) -> &'static str {
474 match num {
475 Num::Int => "int",
476 Num::Float => "float",
477 }
478}
479
480fn compare_name(on: Compare) -> &'static str {
481 match on {
482 Compare::Int => "int",
483 Compare::Float => "float",
484 Compare::Bool => "bool",
485 Compare::Str => "str",
486 Compare::Tag => "tag",
487 Compare::Identity => "identity",
488 }
489}
490
491fn arith_name(op: ArithOp) -> &'static str {
492 match op {
493 ArithOp::Add => "add",
494 ArithOp::Sub => "sub",
495 ArithOp::Mul => "mul",
496 ArithOp::Div => "div",
497 ArithOp::Rem => "rem",
498 }
499}
500
501fn cmp_name(op: CmpOp) -> &'static str {
502 match op {
503 CmpOp::Eq => "eq",
504 CmpOp::Ne => "ne",
505 CmpOp::Lt => "lt",
506 CmpOp::Le => "le",
507 CmpOp::Gt => "gt",
508 CmpOp::Ge => "ge",
509 }
510}
511
512fn shape_name(shape: &Shape) -> &'static str {
513 match shape {
514 Shape::Free => "free",
515 Shape::Word(_) => "word",
516 Shape::Str => "str",
517 Shape::Bytes => "bytes",
518 Shape::Struct { .. } => "struct",
519 Shape::Enum { .. } => "enum",
520 Shape::Elements {
521 growable: false, ..
522 } => "array",
523 Shape::Elements { growable: true, .. } => "store",
524 Shape::Vector { .. } => "vector",
525 Shape::ByteBuffer => "buffer",
526 Shape::Members { .. } => "set",
527 Shape::Entries { .. } => "map",
528 Shape::Closure { .. } => "closure",
529 Shape::Shared { .. } => "shared",
530 Shape::Boxed => "boxed",
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use std::sync::Arc;
537
538 use cove_diag::{FileId, Span};
539
540 use super::*;
541 use crate::layout::{Case, Layout};
542 use crate::program::{Arg, HostOp, Local};
543 use crate::repr::{RefMap, Repr};
544 use crate::{ArgsId, HostOpId};
545
546 const INT: LayoutId = LayoutId(0);
548 const STR: LayoutId = LayoutId(1);
552 const POINT: LayoutId = LayoutId(2);
554 const RESULT: LayoutId = LayoutId(3);
557 const MISSING: LayoutId = LayoutId(9);
559
560 fn layouts() -> Vec<Layout> {
561 vec![
562 Layout::word("Int", Repr::Int),
563 Layout::object("String", Shape::Str),
564 Layout::inline(
565 "m.Point",
566 Shape::Struct {
567 fields: Vec::new(),
568 opaque: false,
569 },
570 vec![Repr::Int, Repr::Int],
571 ),
572 Layout::inline(
573 "Result",
574 Shape::Enum {
575 cases: vec![
576 Case {
577 name: Arc::from("Ok"),
578 parts: Vec::new(),
579 },
580 Case {
581 name: Arc::from("Err"),
582 parts: Vec::new(),
583 },
584 ],
585 payload: vec![Repr::Unit, Repr::Ref],
586 },
587 vec![Repr::Tag, Repr::Unit, Repr::Ref],
588 ),
589 ]
590 }
591
592 fn span() -> Span {
593 Span::new(FileId(0), 0, 0)
594 }
595
596 fn function(code: Vec<Inst>) -> Function {
599 let reprs = vec![
600 Repr::Tag,
601 Repr::Unit,
602 Repr::Ref,
603 Repr::Int,
604 Repr::Int,
605 Repr::Int,
606 Repr::Ref,
607 ];
608 Function {
609 module: Arc::from("m"),
610 name: Arc::from("f"),
611 params: Vec::new(),
612 spans: vec![span(); code.len()],
613 refs: RefMap::of(&reprs),
614 reprs,
615 returns: RESULT,
616 captures: Vec::new(),
617 code,
618 locals: Vec::new(),
619 inlined: Vec::new(),
620 span: span(),
621 is_async: false,
622 stub: false,
623 }
624 }
625
626 fn program() -> Program {
627 Program {
628 layouts: layouts(),
629 str_layout: STR,
630 ..Program::default()
631 }
632 }
633
634 fn line(inst: Inst) -> String {
636 let held = program();
637 one(&held, &function(vec![inst.clone()]), &inst)
638 }
639
640 #[test]
644 fn a_one_word_value_is_its_base_slot_and_its_layout() {
645 assert_eq!(
646 line(Inst::Copy {
647 dst: 5,
648 src: 3,
649 layout: INT,
650 }),
651 "copy s5:Int s3:Int"
652 );
653 assert_eq!(
656 line(Inst::Clear {
657 slot: 6,
658 layout: STR
659 }),
660 "clear s6:String"
661 );
662 }
663
664 #[test]
667 fn an_inline_struct_names_every_slot_it_covers() {
668 assert_eq!(
669 line(Inst::Copy {
670 dst: 3,
671 src: 3,
672 layout: POINT,
673 }),
674 "copy s3..s4:m.Point s3..s4:m.Point"
675 );
676 }
677
678 #[test]
683 fn a_call_s_answer_names_the_whole_run_it_writes() {
684 let mut held = program();
685 held.args.push(vec![Arg {
686 slot: 6,
687 layout: STR,
688 }]);
689 held.host_ops.push(HostOp {
690 module: Arc::from("console"),
691 operation: Arc::from("println"),
692 resource: None,
693 result: RESULT,
694 });
695 let inst = Inst::CallHost {
696 dst: 0,
697 op: HostOpId(0),
698 args: ArgsId(0),
699 };
700 assert_eq!(
701 one(&held, &function(vec![inst.clone()]), &inst),
702 "call-host s0..s2:Result console.println (s6:String)"
703 );
704 assert_eq!(
705 one(
706 &held,
707 &function(vec![Inst::Return { src: 0 }]),
708 &Inst::Return { src: 0 }
709 ),
710 "return s0..s2:Result"
711 );
712 }
713
714 #[test]
718 fn a_closure_call_names_its_answer_and_leaves_its_callee_a_word() {
719 let mut held = program();
720 held.args.push(vec![Arg {
721 slot: 5,
722 layout: INT,
723 }]);
724 let inst = Inst::CallClosure {
725 dst: 0,
726 closure: 6,
727 args: ArgsId(0),
728 result: RESULT,
729 };
730 assert_eq!(
731 one(&held, &function(vec![inst.clone()]), &inst),
732 "call-closure s0..s2:Result s6:ref (s5:Int)"
733 );
734 }
735
736 #[test]
740 fn a_word_operation_still_prints_one_word_and_its_repr() {
741 assert_eq!(
742 line(Inst::Arith {
743 num: Num::Int,
744 op: ArithOp::Add,
745 dst: 5,
746 a: 3,
747 b: 4,
748 }),
749 "add.int s5:int s3:int s4:int"
750 );
751 assert_eq!(
752 line(Inst::Tag {
753 dst: 0,
754 layout: RESULT,
755 case: crate::CaseId(1),
756 }),
757 "tag s0:tag Result.Err"
758 );
759 assert_eq!(
762 line(Inst::LoadField {
763 dst: 3,
764 obj: 6,
765 at: 2,
766 layout: POINT,
767 }),
768 "load-field s3..s4:m.Point s6:ref +2"
769 );
770 }
771
772 #[test]
777 fn a_layout_the_table_does_not_hold_renders_rather_than_panicking() {
778 assert_eq!(
779 line(Inst::Clear {
780 slot: 5,
781 layout: MISSING,
782 }),
783 "clear s5:layout9"
784 );
785 }
786
787 #[test]
790 fn the_frame_is_words_and_the_names_over_it_are_locations() {
791 let mut held = program();
792 let mut f = function(vec![Inst::Return { src: 0 }]);
793 f.locals = vec![
794 Local {
795 name: Arc::from("wide"),
796 slot: 0,
797 layout: RESULT,
798 from: 0,
799 to: 1,
800 },
801 Local {
802 name: Arc::from("n"),
803 slot: 5,
804 layout: INT,
805 from: 0,
806 to: 1,
807 },
808 ];
809 held.functions.push(f);
810 assert_eq!(
813 super::function(&held, FunctionId(0)),
814 "\
815fn @m.f() -> Result
816 frame 7: s0:tag s1:unit s2:ref s3:int s4:int s5:int s6:ref
817 local wide -> s0..s2:Result [0, 1)
818 local n -> s5:Int [0, 1)
819 0 return s0..s2:Result
820"
821 );
822 }
823}