Skip to main content

cove_ir/
print.rs

1//! A disassembly, for reading a lowering and for a test to assert on.
2//!
3//! The format is one instruction per line, `pc  opcode operands`, with slot
4//! numbers written `s0`, `s1`.
5//!
6//! An operand is one of two things and the line says which. A **word** is
7//! annotated with what that one word holds — `s3:int`, `s5:tag` — and a word
8//! is what arithmetic, a branch's condition and a field offset take. A
9//! **value location** is annotated with its *layout* — `s3:Point` — because a
10//! value is a run of words and the layout is what says how many. Where the
11//! run is wider than one word the whole of it is named: `s5..s7:Result` is
12//! the three words `s5`, `s6` and `s7`.
13//!
14//! Naming the run is what makes the calling convention visible. A
15//! `call-host s5..s7:Result console.println (s4:String)` writes three words,
16//! and until issue #299 the same line read
17//! `call-host s5:tag console.println (s4:String) Result`: a destination that
18//! looked like one word, the width parked at the end of the line, and `s6`
19//! and `s7` reading as registers with nothing to do with it. The width was
20//! there; which slots it covered was not.
21//!
22//! So the layout is written once, on the location it describes, and a `copy`,
23//! a `clear`, a `return` and a call no longer repeat it after their operands.
24//! That includes [`Inst::CallClosure`], whose *callee* is a function id read
25//! out of an object at run time but whose *answer* is not: the checker
26//! settles a call through a value against the callee's function type, so the
27//! instruction carries the layout the destination has to be, and the line
28//! reads like every other call's.
29//!
30//! Which operands are locations is not a second opinion. It is where
31//! [`mod@crate::verify`] asks whether a run of words *fits* — the same layout, on
32//! the same slot — so a listing and the check disagreeing is a bug in one of
33//! them rather than a matter of taste. A layout the table does not hold
34//! prints as its id and no range, `s5:layout7`, because nothing then says how
35//! wide the location is.
36//!
37//! Before the code come the frame's names, one to a line —
38//! `local count -> s3:Int [4, 11)` — because a slot number is not an answer
39//! to what the source called something and a slot is reused by several
40//! variables in turn. The pair is the half-open range of program counters the
41//! name denotes that slot over; see [`crate::Local`]. The `frame` line above
42//! them is the one place that is per *word* throughout: it is the ground
43//! truth every range indexes into.
44//!
45//! A test that pins a lowering pins this text, so it is written to be
46//! diffed: one fact per line, and no alignment that changes when an
47//! unrelated line grows.
48
49use 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
55/// Renders every function of `program`.
56pub 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
67/// Renders one function: its boundary, its frame, the names bound in it,
68/// then its code.
69pub 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
116/// Renders one instruction.
117pub 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    // A whole value location, where `s` above renders one word. Which of the
124    // two an operand is, is what this module exists to say on a line.
125    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        // Named, not numbered, for `Inst::FuncRef`'s reason below: a case
131        // added before this one changes its index and would otherwise
132        // change every listing that never mentions it.
133        Inst::Tag { dst, layout, case } => {
134            format!("tag {} {}", s(*dst), case_name(program, *layout, *case))
135        }
136        // Named, not numbered — the whole point of this instruction over an
137        // `Inst::Int` carrying the same word. `FunctionId` is dense and
138        // renumbers whenever an unrelated declaration is added or moved, so
139        // printing it would make this line, and the golden test that pins
140        // it, churn on changes that have nothing to do with this closure.
141        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        // An immediate is written bare, and the `.imm` on the opcode is what
172        // says the last operand is one. Nothing else is needed: a slot in
173        // this format is always `sN:repr`, so a number standing where an
174        // operand goes is already not a slot — it is how `jump 2` writes a
175        // program counter, how `int s5:int 7` writes a value, and how
176        // `load-field s2:Int s1:ref +0` writes an offset. Marking it
177        // `#7` would spell a distinction the format already draws.
178        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        // The callee is a word in a slot, read out of the closure object
217        // when the instruction runs, and it is written as one. The answer is
218        // a location like every other call's: `Inst::CallClosure` carries
219        // that layout itself, because there is no declared callee to read it
220        // from. See the module docs.
221        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
397/// What a case is called in a listing: `Shape.Circle`, not `1`.
398///
399/// The number is the fact the instruction carries and the name is what a
400/// reader wants, and printing the number is what made a listing change when
401/// an unrelated case was declared before this one. Where the layout is not an
402/// enum, or the index is past its cases, the id is printed instead — a
403/// listing is read while a lowering is being debugged, and a lowering that
404/// produced either of those is the thing being debugged.
405fn 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
418/// What a layout is called in a listing, or its id where the table is too
419/// short to say — a listing is also read while a lowering is being debugged.
420fn 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
427/// An argument is a value location, so it prints as one: the *layout* it
428/// names rather than the `Repr` of its first word, and the whole run of slots
429/// the callee will read. A listing that showed `s3:int` for a `Point` would
430/// show the same thing for its `x`, and which of the two a call passes is the
431/// question the argument list exists to answer.
432fn 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
443/// A whole value location: `s3:Int` for a one-word value, `s5..s7:Result` for
444/// one that runs over three.
445///
446/// The base slot, the last slot of the run, and the layout that decides
447/// which. The run is `layout.words`, which is the same thing
448/// [`mod@crate::verify`]'s `fits` walks — the printer and the check read one
449/// fact rather than two that can drift.
450///
451/// A one-word value stays compact. `s3..s3:Int` would be noise on the great
452/// majority of the lines in a listing, and a reader who wants the width of a
453/// scalar has the `frame` line above.
454///
455/// Two cases are rendered rather than indexed. A layout the table does not
456/// hold prints as its id and no range, because nothing says how wide it is;
457/// and a zero-word layout — [`crate::Layout::free`], which is not a value —
458/// prints as its base alone rather than as a range that runs backwards. A
459/// listing is read while a lowering is being debugged, and a lowering that
460/// produced either is the thing being debugged.
461fn 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        // In `u64`, because an ill-formed program may name a slot near the
468        // top of the range and this renders it rather than panicking.
469        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    /// One word.
547    const INT: LayoutId = LayoutId(0);
548    /// One word, and an address rather than a scalar — the case that is one
549    /// word *and* a family of its own, so a listing must not read it as the
550    /// `ref` its frame word says.
551    const STR: LayoutId = LayoutId(1);
552    /// Two words, inline: `struct Point { x: Int, y: Int }`.
553    const POINT: LayoutId = LayoutId(2);
554    /// Three words, inline: `Result<Unit, Error>` is a tag, a `Unit` and the
555    /// error's one reference. This is the layout issue #299 is written about.
556    const RESULT: LayoutId = LayoutId(3);
557    /// Nothing holds this: the table stops before it.
558    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    /// A frame wide enough for every case here: `s0..s2` is a `Result`,
597    /// `s3..s4` a `Point`, `s5` an `Int` and `s6` a `String`.
598    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    /// What one instruction reads as, in a program with the layouts above.
635    fn line(inst: Inst) -> String {
636        let held = program();
637        one(&held, &function(vec![inst.clone()]), &inst)
638    }
639
640    /// A one-word value is its base slot and its layout, with no range: a
641    /// `s5..s5:Int` on nearly every line of a listing would be noise, and the
642    /// layout is still what says which family the word is of.
643    #[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        // One word and an address: the frame says `ref`, and which family of
654        // reference it is is exactly what the location's layout adds.
655        assert_eq!(
656            line(Inst::Clear {
657                slot: 6,
658                layout: STR
659            }),
660            "clear s6:String"
661        );
662    }
663
664    /// An inline struct names both of the slots it covers. Its frame words
665    /// are two `Repr::Int`s and say nothing about where the value ends.
666    #[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    /// Issue #299's own example. The call writes `s0`, `s1` and `s2`, and the
679    /// line says so; it used to read `call-host s0:tag console.println (…)
680    /// Result`, which named the discriminant and left the other two words
681    /// looking like unrelated registers.
682    #[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    /// A closure call reads like every other call. Its *callee* is a word in
715    /// a slot — that is the run-time fact — and its *answer* is a location,
716    /// because the instruction carries the layout the checker settled.
717    #[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    /// A word operation still prints one word and its `Repr`. Arithmetic, a
737    /// discriminant and a field offset are about the word in front of them,
738    /// and a range on them would claim an extent nothing has.
739    #[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        // A field is read *into* a location and *out of* one word's offset,
760        // so this one line has both spellings on it.
761        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    /// A layout the table does not hold renders as its id and no range,
773    /// because nothing then says how wide the location is. A listing is read
774    /// while a lowering is being debugged, and this is the shape of a
775    /// lowering that is being debugged.
776    #[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    /// The frame line stays per *word*, and the names above the code are
788    /// locations like any other operand: `wide` is three slots and says so.
789    #[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        // `super::function`, because this module has a `function` of its own
811        // that builds the one being rendered.
812        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}