Skip to main content

cove_ir/
verify.rs

1//! A static check that a lowered program is well formed.
2//!
3//! The machine takes the lowering's word for a great deal: that a value
4//! location fits the frame it is in, that a jump lands on an instruction,
5//! that a call passes the layouts the callee declares, and — the one that
6//! matters most — that a slot's [`Repr`] is what [`Function::refs`] says it
7//! is. A collection walks frames using that map, so a lowering that wrote a
8//! reference into a slot the map calls an `Int` would produce a dangling
9//! reference at the next collection and a wrong answer some time after that.
10//!
11//! # A location agrees with its layout, word for word
12//!
13//! Every instruction that moves a value names the layout it is moving, and a
14//! layout is a run of [`Repr`]s. So the check is not "the destination is a
15//! reference" but "the destination's words *are* the layout's words, in
16//! order". That is what makes the one-value-many-slots rule checkable: a
17//! `Copy` of a three-word `Wrapper` into a location whose second word is a
18//! `Float` is a fault here rather than a `Float` traced as a pointer later.
19//!
20//! # A width is checked, not assumed
21//!
22//! Two of those checks are about how far a run of words reaches, and they are
23//! here because nothing downstream can make them. A value location has to fit
24//! the frame it is in — `slot + width <= frame_size` — or a `Copy` near the
25//! top of a frame reads or writes the frame above it, which was the shape of
26//! five separate failures while this backend was being built and which
27//! `Memory::copy_words` was left asserting about in a debug build. And a
28//! field access has to fit the object it names, which this can say wherever
29//! the object's layout is a static fact; where it is not, the machine's own
30//! bounds check is what answers, from the header.
31//!
32//! This is where those assumptions are checked, once, before anything runs.
33//! It is not a type checker: `cove-sema` already did that, and a failure here
34//! is a bug in the lowering rather than a fault in the program. It exists so
35//! that such a bug is a loud failure at lowering time instead of a quiet one
36//! at collection time.
37
38use crate::inst::{Compare, Inst, Len, Num, Slot};
39use crate::layout::{LayoutId, Shape};
40use crate::program::{Function, FunctionId, Program};
41use crate::repr::{RefMap, Repr};
42
43/// A way in which a lowered program is not well formed.
44#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct Invalid {
46    /// The function the fault is in, as `module.name`.
47    pub function: String,
48    /// The instruction it is at, or `None` when the fault is the function's
49    /// own — a frame whose reference map disagrees with its reprs, say.
50    pub pc: Option<usize>,
51    pub what: String,
52}
53
54impl std::fmt::Display for Invalid {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match self.pc {
57            Some(pc) => write!(f, "{}+{pc}: {}", self.function, self.what),
58            None => write!(f, "{}: {}", self.function, self.what),
59        }
60    }
61}
62
63/// Checks every function of `program`, reporting every fault rather than the
64/// first: one lowering bug usually shows up in several places, and seeing
65/// all of them is what says which one is the cause.
66pub fn verify(program: &Program) -> Result<(), Vec<Invalid>> {
67    let mut faults = Vec::new();
68    for (index, function) in program.functions.iter().enumerate() {
69        Check {
70            program,
71            function,
72            id: FunctionId(index as u32),
73            objects: Vec::new(),
74            funcs: Vec::new(),
75            faults: &mut faults,
76        }
77        .run();
78    }
79    if faults.is_empty() {
80        Ok(())
81    } else {
82        Err(faults)
83    }
84}
85
86/// Marks `width` words starting at `slot` as written by something
87/// [`Check::slot_facts`] declines to guess about — the `Some(None)` case any
88/// writer other than the one this fact is about produces.
89fn poison<T>(seen: &mut [Option<Option<T>>], slot: Slot, width: u32) {
90    for at in slot..slot.saturating_add(width) {
91        if let Some(place) = seen.get_mut(at as usize) {
92            *place = Some(None);
93        }
94    }
95}
96
97/// Marks `slot` as written by `id` — the answer stays `id` if every writer
98/// [`Check::slot_facts`] has seen so far agrees, and becomes the poisoned
99/// [`Option::None`] the moment two disagree.
100fn identify<T: Copy + PartialEq>(seen: &mut [Option<Option<T>>], slot: Slot, id: T) {
101    if let Some(place) = seen.get_mut(slot as usize) {
102        *place = match *place {
103            None => Some(Some(id)),
104            Some(Some(held)) if held == id => Some(Some(id)),
105            _ => Some(None),
106        };
107    }
108}
109
110struct Check<'a> {
111    program: &'a Program,
112    function: &'a Function,
113    id: FunctionId,
114    /// The layout of the object each reference slot holds, where the whole
115    /// function agrees on one. See [`Check::slot_facts`].
116    objects: Vec<Option<LayoutId>>,
117    /// The callee each slot holds, where the whole function agrees on one and
118    /// the writer was [`Inst::FuncRef`]. See [`Check::slot_facts`].
119    funcs: Vec<Option<FunctionId>>,
120    faults: &'a mut Vec<Invalid>,
121}
122
123impl Check<'_> {
124    fn run(&mut self) {
125        let (objects, funcs) = self.slot_facts();
126        self.objects = objects;
127        self.funcs = funcs;
128        self.check_frame();
129        for pc in 0..self.function.code.len() {
130            self.check_inst(pc);
131        }
132        self.check_falls_off_the_end();
133    }
134
135    /// Which slots hold an object whose layout is a static fact, and which
136    /// hold a callee whose id is one — one walk of the code answering both,
137    /// because a slot is disqualified from the second the same way it is
138    /// from the first.
139    ///
140    /// A `Repr::Ref` slot carries no layout — that is the point of the header
141    /// — so in general only the machine can bound a field access. But a slot
142    /// that is written by allocations alone, all naming one layout, holds
143    /// either null or an object of that layout at every program counter: a
144    /// slot's `Repr` is fixed for the whole function and a run is only ever
145    /// reused by a location of the same words, so the *set* of layouts ever
146    /// written into a slot bounds what it can hold without a walk of the
147    /// control flow. One layout and no other writer is the case this can
148    /// answer, and it is the common one — a lowering allocates an object and
149    /// reads its fields in the same breath.
150    ///
151    /// The second answer is the same question about [`Inst::FuncRef`] instead
152    /// of [`Inst::Alloc`]: a slot written by one, and by nothing else, holds
153    /// that callee at every program counter. A temporary is given back to the
154    /// pool once its value is stored, [`crate::lower::closures`] among its
155    /// callers, so one slot number can hold two different closures' callees
156    /// in one function — which is a second writer with a different id, and
157    /// poisons the answer exactly as a second, different [`Inst::Alloc`]
158    /// would.
159    ///
160    /// Anything else is `None`, which means the check the answer feeds is
161    /// skipped rather than failed. A slot written by a call, a load or a copy
162    /// holds whatever the callee or the source held, and this declines to
163    /// guess.
164    fn slot_facts(&self) -> (Vec<Option<LayoutId>>, Vec<Option<FunctionId>>) {
165        // `Some(None)` is "written, by something that says no fact"; `None`
166        // is "not written yet". The parameters and the captures are written
167        // by the caller, so they start as the first.
168        let mut objects: Vec<Option<Option<LayoutId>>> = vec![None; self.function.reprs.len()];
169        let mut funcs: Vec<Option<Option<FunctionId>>> = vec![None; self.function.reprs.len()];
170        let words = |id: LayoutId| {
171            self.program
172                .layouts
173                .get(id.index())
174                .map_or(1, |layout| layout.width())
175        };
176        for at in 0..self.function.param_words(&self.program.layouts) {
177            poison(&mut objects, at, 1);
178            poison(&mut funcs, at, 1);
179        }
180        for capture in &self.function.captures {
181            poison(&mut objects, capture.slot, words(capture.layout));
182            poison(&mut funcs, capture.slot, words(capture.layout));
183        }
184        for inst in &self.function.code {
185            match *inst {
186                // The three that say what they allocate. A `Clear` is not
187                // among them and is not a writer either: it stores null, and
188                // null is refused by the machine before a layout is asked
189                // about. None of the three is `Inst::FuncRef`, so all three
190                // poison the second answer the way any other writer does.
191                Inst::Alloc { dst, layout, .. } => {
192                    identify(&mut objects, dst, layout);
193                    poison(&mut funcs, dst, 1);
194                }
195                Inst::Str { dst, .. } => {
196                    identify(&mut objects, dst, self.program.str_layout);
197                    poison(&mut funcs, dst, 1);
198                }
199                Inst::Box { dst, .. } => {
200                    identify(&mut objects, dst, self.program.boxed_layout);
201                    poison(&mut funcs, dst, 1);
202                }
203                // The one instruction that identifies a callee rather than a
204                // layout. It is not an allocation, so it poisons the first
205                // answer exactly as `Inst::Int` does.
206                Inst::FuncRef { dst, callee } => {
207                    poison(&mut objects, dst, 1);
208                    identify(&mut funcs, dst, callee);
209                }
210                Inst::Clear { .. } | Inst::Jump { .. } | Inst::BranchFalse { .. } => {}
211                Inst::Switch { .. } | Inst::Return { .. } | Inst::Trap { .. } => {}
212                // Scheduler state, not objects. A `Repr::Task` and a
213                // `Repr::Scope` word name a table entry, so there is no
214                // layout for one of these to claim.
215                Inst::ScopeEnter { dst, .. } => {
216                    poison(&mut objects, dst, 1);
217                    poison(&mut funcs, dst, 1);
218                }
219                Inst::Spawn { dst, .. } => {
220                    poison(&mut objects, dst, 1);
221                    poison(&mut funcs, dst, 1);
222                }
223                Inst::Settled { dst, .. } => {
224                    poison(&mut objects, dst, 1);
225                    poison(&mut funcs, dst, 1);
226                }
227                Inst::ScopeCancel { .. } | Inst::Cancel { .. } => {}
228                // Neither writes a slot: what they change is the cell's own
229                // lock word, which is not a location this frame numbers.
230                Inst::SharedLock { .. } | Inst::SharedUnlock { .. } => {}
231                Inst::ScopeLeave {
232                    failed,
233                    error,
234                    layout,
235                    ..
236                } => {
237                    poison(&mut objects, failed, 1);
238                    poison(&mut funcs, failed, 1);
239                    poison(&mut objects, error, words(layout));
240                    poison(&mut funcs, error, words(layout));
241                }
242                Inst::Await { dst, answer, .. } => {
243                    poison(&mut objects, dst, words(answer));
244                    poison(&mut funcs, dst, words(answer));
245                }
246                // Writes nothing a program can read: what it writes is the
247                // run's report of where an assertion failed.
248                Inst::AssertFailed { .. } => {}
249                Inst::Store { .. } | Inst::StoreField { .. } | Inst::StoreElem { .. } => {}
250                Inst::Unit { dst } | Inst::Bool { dst, .. } => {
251                    poison(&mut objects, dst, 1);
252                    poison(&mut funcs, dst, 1);
253                }
254                Inst::Int { dst, .. } | Inst::Tag { dst, .. } | Inst::Float { dst, .. } => {
255                    poison(&mut objects, dst, 1);
256                    poison(&mut funcs, dst, 1);
257                }
258                Inst::Neg { dst, .. } | Inst::Not { dst, .. } => {
259                    poison(&mut objects, dst, 1);
260                    poison(&mut funcs, dst, 1);
261                }
262                Inst::Arith { dst, .. } | Inst::Cmp { dst, .. } => {
263                    poison(&mut objects, dst, 1);
264                    poison(&mut funcs, dst, 1);
265                }
266                Inst::ArithImm { dst, .. } | Inst::CmpImm { dst, .. } => {
267                    poison(&mut objects, dst, 1);
268                    poison(&mut funcs, dst, 1);
269                }
270                Inst::Convert { dst, .. } => {
271                    poison(&mut objects, dst, 1);
272                    poison(&mut funcs, dst, 1);
273                }
274                Inst::ByteAt { dst, .. } | Inst::Len { dst, .. } | Inst::LayoutOf { dst, .. } => {
275                    poison(&mut objects, dst, 1);
276                    poison(&mut funcs, dst, 1);
277                }
278                // `AllocBytes` always allocates `Program::bytes_layout`, but
279                // this poisons `dst` exactly as `ByteAt` does rather than
280                // `identify`ing it the way `Inst::Alloc` and `Inst::Str` do:
281                // a byte run under construction is not a value this pass
282                // needs to reason about by layout, only by `Repr`, and
283                // `FinishString` immediately relabels the same slot to a
284                // `Str` object anyway.
285                Inst::AllocBytes { dst, .. } | Inst::FinishString { dst, .. } => {
286                    poison(&mut objects, dst, 1);
287                    poison(&mut funcs, dst, 1);
288                }
289                // ADR 0052's two, and the same answer for the same reason:
290                // `AllocBuffer` always allocates `Program::buffer_layout` and
291                // `FinishBuffer` answers the store its owner was holding,
292                // relabelled to a `Str` object. Neither is a layout this pass
293                // reasons about, only a `Repr`.
294                Inst::AllocBuffer { dst, .. } | Inst::FinishBuffer { dst, .. } => {
295                    poison(&mut objects, dst, 1);
296                    poison(&mut funcs, dst, 1);
297                }
298                // Neither writes a frame slot: `WriteByte` writes a byte of
299                // the object `bytes` already names, and `CopyBytes` writes
300                // into the object its `args` table's `dst` already names.
301                Inst::WriteByte { .. } | Inst::CopyBytes { .. } => {}
302                // Nor do the growable appends: what each changes is the store
303                // the owner in `buffer` names, and the owner's own length word.
304                Inst::AppendByte { .. } | Inst::AppendBytes { .. } => {}
305                // Forming the address of a slot is also a write to it, as
306                // far as this is concerned: a `var` argument is that address
307                // handed to a callee, and what the callee stores through it
308                // lands in this frame. The checker holds the two to one type
309                // and so to one layout, but a static claim about a slot
310                // should not rest on an argument made somewhere else.
311                Inst::AddrOfSlot { dst, slot } => {
312                    poison(&mut objects, dst, 1);
313                    poison(&mut funcs, dst, 1);
314                    poison(&mut objects, slot, 1);
315                    poison(&mut funcs, slot, 1);
316                }
317                Inst::AddrOfField { dst, .. } => {
318                    poison(&mut objects, dst, 1);
319                    poison(&mut funcs, dst, 1);
320                }
321                Inst::AddrOfElem { dst, .. } | Inst::AddrOfPart { dst, .. } => {
322                    poison(&mut objects, dst, 1);
323                    poison(&mut funcs, dst, 1);
324                }
325                Inst::Copy { dst, layout, .. }
326                | Inst::Load { dst, layout, .. }
327                | Inst::LoadField { dst, layout, .. }
328                | Inst::LoadElem { dst, layout, .. }
329                | Inst::Unbox { dst, layout, .. } => {
330                    poison(&mut objects, dst, words(layout));
331                    poison(&mut funcs, dst, words(layout));
332                }
333                Inst::Call { dst, callee, .. } => {
334                    let width = match self.program.functions.get(callee.index()) {
335                        Some(target) => words(target.returns),
336                        None => 1,
337                    };
338                    poison(&mut objects, dst, width);
339                    poison(&mut funcs, dst, width);
340                }
341                Inst::CallClosure { dst, result, .. } => {
342                    poison(&mut objects, dst, words(result));
343                    poison(&mut funcs, dst, words(result));
344                }
345                Inst::CallHost { dst, op, .. } | Inst::CallResource { dst, op, .. } => {
346                    let width = match self.program.host_ops.get(op.index()) {
347                        Some(op) => words(op.result),
348                        None => 1,
349                    };
350                    poison(&mut objects, dst, width);
351                    poison(&mut funcs, dst, width);
352                }
353                Inst::CallBuiltin { dst, builtin, .. } => {
354                    let width = match self.program.builtins.get(builtin.index()) {
355                        Some(builtin) => words(builtin.result),
356                        None => 1,
357                    };
358                    poison(&mut objects, dst, width);
359                    poison(&mut funcs, dst, width);
360                }
361            }
362        }
363        (
364            objects.into_iter().map(Option::flatten).collect(),
365            funcs.into_iter().map(Option::flatten).collect(),
366        )
367    }
368
369    fn fault(&mut self, pc: Option<usize>, what: impl Into<String>) {
370        self.faults.push(Invalid {
371            function: self.function.qualified(),
372            pc,
373            what: what.into(),
374        });
375    }
376
377    /// The frame's own invariants: the parameters fit, the answer's layout
378    /// exists, the spans line up, the reference map is the one the reprs
379    /// imply, and every name is of a location and a range this function has.
380    fn check_frame(&mut self) {
381        let size = self.function.frame_size();
382        let mut at = 0;
383        for (index, param) in self.function.params.clone().into_iter().enumerate() {
384            if !self.layout_exists(None, param) {
385                continue;
386            }
387            let width = self.program.layout(param).width();
388            if !self.fits(None, at, param, &format!("parameter {index}")) {
389                return;
390            }
391            at += width;
392        }
393        if !self.layout_exists(None, self.function.returns) {
394            return;
395        }
396        if self.function.spans.len() != self.function.code.len() {
397            self.fault(
398                None,
399                format!(
400                    "has {} instructions but {} spans",
401                    self.function.code.len(),
402                    self.function.spans.len()
403                ),
404            );
405        }
406        let expected = RefMap::of(&self.function.reprs);
407        if expected != self.function.refs {
408            self.fault(
409                None,
410                "reference map disagrees with the frame's reprs, so a collection would \
411                 scan the wrong slots"
412                    .to_string(),
413            );
414        }
415        for capture in self.function.captures.clone() {
416            if !self.layout_exists(None, capture.layout) {
417                continue;
418            }
419            let name = capture.name.clone();
420            self.fits(
421                None,
422                capture.slot,
423                capture.layout,
424                &format!("capture `{name}`"),
425            );
426        }
427        // Nothing runs a local — it is read when a person asks what a frame
428        // holds — so what is checked is that it *names* something that
429        // exists: a location the frame has, over a range of this function's
430        // code. A local pointing past either would be a debugger's answer
431        // about a slot or an instruction that is not there.
432        for index in 0..self.function.locals.len() {
433            // One `Local` at a time rather than `self.function.locals.clone()`:
434            // the loop body needs `&mut self` for `fault`, which a borrow of
435            // the table itself would still be holding, but a name and four
436            // `Copy` fields cost far less than a second copy of the table.
437            let local = self.function.locals[index].clone();
438            let name = local.name;
439            if self.layout_exists(None, local.layout) {
440                self.fits(None, local.slot, local.layout, &format!("local `{name}`"));
441            }
442            if local.from > local.to {
443                self.fault(
444                    None,
445                    format!(
446                        "local `{name}` is bound at {} and freed at {}",
447                        local.from, local.to
448                    ),
449                );
450            } else if local.to as usize > self.function.code.len() {
451                self.fault(
452                    None,
453                    format!(
454                        "local `{name}` is live to {} and the function has {} instructions",
455                        local.to,
456                        self.function.code.len()
457                    ),
458                );
459            }
460        }
461        // The same question about the same kind of table, asked of the bodies
462        // an expansion wrote here. `Inlined` is `Local`'s shape and is read by
463        // the same readers — an error's chain, a backtrace, a profile — so a
464        // range or a slot that names nothing is the same fault, and it is one
465        // nothing at run time would notice: an expansion is not executed
466        // *through* its record, it is merely described by it.
467        for index in 0..self.function.inlined.len() {
468            let held = self.function.inlined[index].clone();
469            let callee = held.callee.index();
470            let name = match self.program.functions.get(callee) {
471                Some(function) => function.qualified(),
472                None => {
473                    self.fault(
474                        None,
475                        format!("an expanded body names function {callee}, which is not one"),
476                    );
477                    continue;
478                }
479            };
480            if held.from > held.to {
481                self.fault(
482                    None,
483                    format!(
484                        "the expansion of `{name}` runs from {} to {}",
485                        held.from, held.to
486                    ),
487                );
488            } else if held.to as usize > self.function.code.len() {
489                self.fault(
490                    None,
491                    format!(
492                        "the expansion of `{name}` ends at {} and the function has {} \
493                         instructions",
494                        held.to,
495                        self.function.code.len()
496                    ),
497                );
498            }
499            for local in held.locals {
500                let bound = local.name;
501                if self.layout_exists(None, local.layout) {
502                    self.fits(
503                        None,
504                        local.slot,
505                        local.layout,
506                        &format!("local `{bound}` of the expanded `{name}`"),
507                    );
508                }
509                if local.from > local.to || local.to as usize > self.function.code.len() {
510                    self.fault(
511                        None,
512                        format!(
513                            "local `{bound}` of the expanded `{name}` is live from {} to {}, \
514                             and the function has {} instructions",
515                            local.from,
516                            local.to,
517                            self.function.code.len()
518                        ),
519                    );
520                }
521            }
522        }
523        let _ = size;
524    }
525
526    /// A function whose last instruction can fall through has nowhere to go.
527    fn check_falls_off_the_end(&mut self) {
528        let last = self.function.code.len().checked_sub(1);
529        let ends = matches!(
530            last.map(|pc| &self.function.code[pc]),
531            Some(Inst::Return { .. } | Inst::Jump { .. } | Inst::Switch { .. } | Inst::Trap { .. })
532        );
533        if !ends {
534            self.fault(
535                last,
536                "the last instruction can fall through, and there is nothing after it",
537            );
538        }
539    }
540
541    fn check_inst(&mut self, pc: usize) {
542        let inst = self.function.code[pc].clone();
543        let at = Some(pc);
544        match inst {
545            Inst::Unit { dst } => self.expect(at, dst, &[Repr::Unit]),
546            Inst::Bool { dst, .. } => self.expect(at, dst, &[Repr::Bool]),
547            Inst::Int { dst, .. } => self.expect(at, dst, &[Repr::Int, Repr::Duration]),
548            // The one place a case index is written, and the only check that
549            // it names a case of the enum it claims to. `Inst::Int` could
550            // write the same word and be bounded against nothing.
551            Inst::Tag { dst, layout, case } => {
552                self.expect(at, dst, &[Repr::Tag]);
553                if self.in_range(at, layout.index(), self.program.layouts.len(), "layout") {
554                    match &self.program.layout(layout).shape {
555                        crate::layout::Shape::Enum { cases, .. } => {
556                            if case.index() >= cases.len() {
557                                let count = cases.len();
558                                self.fault(
559                                    at,
560                                    format!(
561                                        "names {case} of {}, which has {count} case(s)",
562                                        self.program.layout(layout).name
563                                    ),
564                                );
565                            }
566                        }
567                        _ => self.fault(
568                            at,
569                            format!(
570                                "writes a case of {}, which is not an enum",
571                                self.program.layout(layout).name
572                            ),
573                        ),
574                    }
575                }
576            }
577            Inst::FuncRef { dst, callee } => {
578                if !self.in_range(at, callee.index(), self.program.functions.len(), "function") {
579                    return;
580                }
581                self.expect(at, dst, &[Repr::Int]);
582            }
583            Inst::Float { dst, .. } => self.expect(at, dst, &[Repr::Float]),
584            Inst::Str { dst, text } => {
585                self.expect(at, dst, &[Repr::Ref]);
586                self.in_range(at, text.index(), self.program.strings.len(), "string");
587            }
588            Inst::Copy { dst, src, layout } => {
589                if self.layout_exists(at, layout) {
590                    self.fits(at, dst, layout, "the destination of a copy");
591                    self.fits(at, src, layout, "the source of a copy");
592                }
593            }
594            Inst::Clear { slot, layout } => {
595                if self.layout_exists(at, layout) {
596                    self.fits(at, slot, layout, "what a clear zeroes");
597                }
598            }
599            Inst::Neg { num, dst, a } => {
600                let want = Self::numeric(num);
601                self.expect(at, dst, want);
602                self.expect(at, a, want);
603            }
604            Inst::Arith { num, dst, a, b, .. } => {
605                let want = Self::numeric(num);
606                self.expect(at, dst, want);
607                self.expect(at, a, want);
608                self.expect(at, b, want);
609            }
610            // The same claims `Inst::Arith` makes, less the one about an
611            // operand that is not there. `Num` is not a field: an immediate is
612            // an `i64`, so the reading is the integer one, and a `Duration` is
613            // nanoseconds and admitted for the same reason it is there.
614            Inst::ArithImm { dst, a, .. } => {
615                let want = Self::numeric(Num::Int);
616                self.expect(at, dst, want);
617                self.expect(at, a, want);
618            }
619            Inst::CmpImm { dst, a, .. } => {
620                self.expect(at, dst, &[Repr::Bool]);
621                self.expect(at, a, &[Repr::Int, Repr::Duration]);
622            }
623            Inst::Cmp { on, dst, a, b, .. } => {
624                self.expect(at, dst, &[Repr::Bool]);
625                let want: &[Repr] = match on {
626                    Compare::Int => &[Repr::Int, Repr::Duration],
627                    Compare::Float => &[Repr::Float],
628                    Compare::Bool => &[Repr::Bool],
629                    Compare::Str => &[Repr::Ref],
630                    // `is` compares words, and the only words whose identity
631                    // is a language-level question are references.
632                    Compare::Identity => &[Repr::Ref],
633                    Compare::Tag => &[Repr::Tag],
634                };
635                self.expect(at, a, want);
636                self.expect(at, b, want);
637            }
638            Inst::Not { dst, a } => {
639                self.expect(at, dst, &[Repr::Bool]);
640                self.expect(at, a, &[Repr::Bool]);
641            }
642            Inst::Convert { to, dst, a } => {
643                let (from, into) = match to {
644                    crate::inst::Convert::IntToFloat => (Repr::Int, Repr::Float),
645                    crate::inst::Convert::FloatToInt => (Repr::Float, Repr::Int),
646                };
647                self.expect(at, a, &[from]);
648                self.expect(at, dst, &[into]);
649            }
650            Inst::Jump { to } => self.target(at, to),
651            Inst::BranchFalse { cond, to } => {
652                self.expect(at, cond, &[Repr::Bool]);
653                self.target(at, to);
654            }
655            Inst::Switch { on, table } => {
656                // The discriminant of an enum location is its first word and
657                // is an `Int`; so is the layout id a `dyn` dispatch switches
658                // on. Nothing else is dispatched on, and a slot's `Repr` is
659                // the strongest thing a static check has to say about which
660                // word this is — a location's extent is a fact about the
661                // instruction that produced the word, not about the frame.
662                self.expect(at, on, &[Repr::Tag, Repr::Int]);
663                if self.in_range(at, table.index(), self.program.tables.len(), "table") {
664                    let table = self.program.table(table).clone();
665                    for to in table.targets.iter().chain(std::iter::once(&table.default)) {
666                        self.target(at, *to);
667                    }
668                }
669            }
670            Inst::Return { src } => {
671                let returns = self.function.returns;
672                if self.layout_exists(at, returns) {
673                    self.fits(at, src, returns, "what is returned");
674                }
675            }
676            Inst::Call { dst, callee, args } => {
677                if !self.in_range(at, callee.index(), self.program.functions.len(), "function") {
678                    return;
679                }
680                let target = self.program.function(callee);
681                let returns = target.returns;
682                let params = target.params.clone();
683                let name = target.qualified();
684                if self.layout_exists(at, returns) {
685                    self.fits(at, dst, returns, "the destination of a call");
686                }
687                self.check_args(at, args, &params, &name);
688            }
689            // The callee is a word read out of an object, and the answer's
690            // layout is not: the checker settled this call against the
691            // callee's function type, so how wide the destination has to be
692            // is as static here as at any other call. It is checked the same
693            // way, from the layout the instruction carries.
694            Inst::CallClosure {
695                dst,
696                closure,
697                args,
698                result,
699            } => {
700                self.expect(at, closure, &[Repr::Ref]);
701                if self.layout_exists(at, result) {
702                    self.fits(at, dst, result, "the answer of a closure call");
703                }
704                self.each_arg(at, args);
705            }
706            Inst::CallHost { dst, op, args } => {
707                if self.in_range(at, op.index(), self.program.host_ops.len(), "host op") {
708                    let result = self.program.host_op(op).result;
709                    if self.layout_exists(at, result) {
710                        self.fits(at, dst, result, "the answer of a host call");
711                    }
712                }
713                self.each_arg(at, args);
714            }
715            // The receiver is a `Repr::Host` word and never an argument: the
716            // registry takes the handle as the thing being addressed and the
717            // host is handed only what follows it. Whether the word names a
718            // resource this run holds is the machine's question, because a
719            // handle is a name the *host* minted and nothing static can say
720            // which one a slot will hold.
721            Inst::CallResource {
722                dst,
723                receiver,
724                op,
725                args,
726            } => {
727                self.expect(at, receiver, &[Repr::Host]);
728                if self.in_range(at, op.index(), self.program.host_ops.len(), "host op") {
729                    let held = self.program.host_op(op).clone();
730                    if held.resource.is_none() {
731                        let named = held.qualified();
732                        self.fault(
733                            at,
734                            format!(
735                                "is addressed to a resource, but `{named}` names no resource kind"
736                            ),
737                        );
738                    }
739                    if self.layout_exists(at, held.result) {
740                        self.fits(at, dst, held.result, "the answer of a host call");
741                    }
742                }
743                self.each_arg(at, args);
744            }
745            Inst::CallBuiltin { dst, builtin, args } => {
746                if self.in_range(at, builtin.index(), self.program.builtins.len(), "builtin") {
747                    let result = self.program.builtin(builtin).result;
748                    if self.layout_exists(at, result) {
749                        self.fits(at, dst, result, "the answer of a builtin");
750                    }
751                }
752                self.each_arg(at, args);
753            }
754            Inst::Alloc { dst, layout, len } => {
755                self.expect(at, dst, &[Repr::Ref]);
756                if self.layout_exists(at, layout) {
757                    let described = self.program.layout(layout);
758                    // A box's payload is one word of `LayoutId` and then the
759                    // value that layout describes, so its width is in the
760                    // header rather than in the shape — and `Alloc` sizes an
761                    // object by its shape. Allocating one here would make a
762                    // box of a two-word value one word short and the copy
763                    // into it would run off the end of the object.
764                    // `Inst::Box` is the only correct allocator for one,
765                    // because it is the only one that is told what is going
766                    // in.
767                    if matches!(described.shape, Shape::Boxed) {
768                        let name = described.name.clone();
769                        self.fault(
770                            at,
771                            format!(
772                                "allocates a `{name}`, whose width the header carries and \
773                                 the shape does not; a box is allocated by `box`, which \
774                                 knows what is going into it"
775                            ),
776                        );
777                    }
778                }
779                if let Len::Slot(slot) = len {
780                    self.expect(at, slot, &[Repr::Int]);
781                }
782            }
783            Inst::LoadField {
784                dst,
785                obj,
786                at: word,
787                layout,
788            } => {
789                self.expect(at, obj, &[Repr::Ref]);
790                if self.layout_exists(at, layout) {
791                    self.fits(at, dst, layout, "what a field is read into");
792                    self.reaches(at, obj, word, layout, "read");
793                }
794            }
795            Inst::StoreField {
796                obj,
797                at: word,
798                src,
799                layout,
800            } => {
801                self.expect(at, obj, &[Repr::Ref]);
802                if self.layout_exists(at, layout) {
803                    self.fits(at, src, layout, "what a field is written from");
804                    self.reaches(at, obj, word, layout, "written");
805                }
806                self.check_closure_callee(at, obj, word, src);
807            }
808            Inst::LoadElem {
809                dst,
810                obj,
811                index,
812                layout,
813            } => {
814                self.expect(at, obj, &[Repr::Ref]);
815                self.expect(at, index, &[Repr::Int]);
816                if self.layout_exists(at, layout) {
817                    self.fits(at, dst, layout, "what an element is read into");
818                }
819            }
820            Inst::StoreElem {
821                obj,
822                index,
823                src,
824                layout,
825            } => {
826                self.expect(at, obj, &[Repr::Ref]);
827                self.expect(at, index, &[Repr::Int]);
828                if self.layout_exists(at, layout) {
829                    self.fits(at, src, layout, "what an element is written from");
830                }
831            }
832            Inst::ByteAt {
833                dst,
834                obj,
835                at: offset,
836            } => {
837                self.expect(at, obj, &[Repr::Ref]);
838                self.expect(at, offset, &[Repr::Int]);
839                self.expect(at, dst, &[Repr::Int]);
840            }
841            Inst::AllocBytes { dst, len } => {
842                self.expect(at, dst, &[Repr::Ref]);
843                self.expect(at, len, &[Repr::Int]);
844            }
845            Inst::WriteByte {
846                bytes,
847                at: offset,
848                value,
849            } => {
850                self.expect(at, bytes, &[Repr::Ref]);
851                self.expect(at, offset, &[Repr::Int]);
852                self.expect(at, value, &[Repr::Int]);
853            }
854            Inst::CopyBytes { args } => self.check_copy_bytes_args(at, args),
855            Inst::FinishString { dst, bytes } => {
856                self.expect(at, dst, &[Repr::Ref]);
857                self.expect(at, bytes, &[Repr::Ref]);
858            }
859            Inst::AllocBuffer { dst, capacity } => {
860                self.expect(at, dst, &[Repr::Ref]);
861                self.expect(at, capacity, &[Repr::Int]);
862            }
863            Inst::AppendByte { buffer, value } => {
864                self.expect(at, buffer, &[Repr::Ref]);
865                self.expect(at, value, &[Repr::Int]);
866            }
867            Inst::AppendBytes { args } => self.check_append_bytes_args(at, args),
868            Inst::FinishBuffer { dst, buffer } => {
869                self.expect(at, dst, &[Repr::Ref]);
870                self.expect(at, buffer, &[Repr::Ref]);
871            }
872            Inst::Len { dst, obj } => {
873                self.expect(at, obj, &[Repr::Ref]);
874                self.expect(at, dst, &[Repr::Int]);
875            }
876            Inst::LayoutOf { dst, obj } => {
877                self.expect(at, obj, &[Repr::Ref]);
878                self.expect(at, dst, &[Repr::Int]);
879            }
880            Inst::AddrOfSlot { dst, slot } => {
881                self.expect(at, dst, &[Repr::Addr]);
882                self.repr(at, slot);
883            }
884            Inst::AddrOfField { dst, obj, at: word } => {
885                self.expect(at, dst, &[Repr::Addr]);
886                self.expect(at, obj, &[Repr::Ref]);
887                self.reaches_word(at, obj, word, 1, "addressed");
888            }
889            Inst::AddrOfElem {
890                dst,
891                obj,
892                index,
893                layout,
894            } => {
895                self.expect(at, dst, &[Repr::Addr]);
896                self.expect(at, obj, &[Repr::Ref]);
897                self.expect(at, index, &[Repr::Int]);
898                self.layout_exists(at, layout);
899            }
900            // Nothing bounds `at` against the value the address names. A
901            // frame records what each slot *holds* and not how far the value
902            // an address points into reaches, so the extent is a fact about
903            // the instruction that formed the address rather than about this
904            // function — the same limit `Inst::Switch`'s operand is under.
905            Inst::AddrOfPart { dst, addr, .. } => {
906                self.expect(at, dst, &[Repr::Addr]);
907                self.expect(at, addr, &[Repr::Addr]);
908            }
909            Inst::Load { dst, addr, layout } => {
910                self.expect(at, addr, &[Repr::Addr]);
911                if self.layout_exists(at, layout) {
912                    self.fits(at, dst, layout, "what a load answers");
913                }
914            }
915            Inst::Store { addr, src, layout } => {
916                self.expect(at, addr, &[Repr::Addr]);
917                if self.layout_exists(at, layout) {
918                    self.fits(at, src, layout, "what a store writes");
919                }
920            }
921            Inst::Box { dst, src, layout } => {
922                self.expect(at, dst, &[Repr::Ref]);
923                if self.layout_exists(at, layout) {
924                    self.fits(at, src, layout, "what is boxed");
925                }
926            }
927            Inst::Unbox { dst, src, layout } => {
928                self.expect(at, src, &[Repr::Ref]);
929                if self.layout_exists(at, layout) {
930                    self.fits(at, dst, layout, "what a box is opened into");
931                }
932            }
933            // ---- tasks ---------------------------------------------------
934            Inst::ScopeEnter { dst, name } => {
935                self.expect(at, dst, &[Repr::Scope]);
936                self.in_range(at, name.index(), self.program.strings.len(), "string");
937            }
938            Inst::ScopeCancel { scope } => self.expect(at, scope, &[Repr::Scope]),
939            // The error location is the *enclosing* function's `Err`
940            // payload, not the child's answer: what a failing child gives
941            // the scope is a value to pass on, and where it goes is decided
942            // by the function the scope was written in. The machine holds
943            // the child's own layout to this one and refuses a disagreement,
944            // because a run of words copied at the wrong width is the one
945            // fault this crate exists to make loud.
946            Inst::ScopeLeave {
947                scope,
948                failed,
949                error,
950                layout,
951            } => {
952                self.expect(at, scope, &[Repr::Scope]);
953                self.expect(at, failed, &[Repr::Bool]);
954                if self.layout_exists(at, layout) {
955                    self.fits(at, error, layout, "what a failing child leaves");
956                }
957            }
958            Inst::Spawn {
959                dst,
960                scope,
961                closure,
962                answer,
963            } => {
964                self.expect(at, dst, &[Repr::Task]);
965                self.expect(at, scope, &[Repr::Scope]);
966                self.expect(at, closure, &[Repr::Ref]);
967                self.layout_exists(at, answer);
968            }
969            Inst::Await { dst, task, answer } => {
970                self.expect(at, task, &[Repr::Task]);
971                if self.layout_exists(at, answer) {
972                    self.fits(at, dst, answer, "what an await answers");
973                }
974            }
975            Inst::Cancel { task } => self.expect(at, task, &[Repr::Task]),
976            // The words go into an object of the same shape a spawned
977            // task's answer goes into, so the same question is asked of
978            // them: that the location they are read out of is as wide as
979            // the layout says.
980            Inst::Settled { dst, src, answer } => {
981                self.expect(at, dst, &[Repr::Task]);
982                if self.layout_exists(at, answer) {
983                    self.fits(at, src, answer, "what a settled task answers");
984                }
985            }
986
987            // ---- cells ---------------------------------------------------
988            // A cell is an ordinary object in the run's heap, so the operand
989            // is an ordinary `Repr::Ref` word. That the two come in pairs is
990            // not checked here: which cells a path is holding is a fact about
991            // control flow, and this is a fact about one instruction — the
992            // same limit `Inst::ScopeCancel` is under.
993            Inst::SharedLock { cell } | Inst::SharedUnlock { cell } => {
994                self.expect(at, cell, &[Repr::Ref])
995            }
996
997            Inst::Trap { message } => {
998                self.in_range(at, message.index(), self.program.strings.len(), "string");
999            }
1000            Inst::AssertFailed { message } => {
1001                self.expect(at, message, &[Repr::Ref]);
1002            }
1003        }
1004    }
1005
1006    fn numeric(num: Num) -> &'static [Repr] {
1007        match num {
1008            // A `Duration` is nanoseconds, and nanoseconds add like
1009            // integers. Only the boundary cares what the answer is called.
1010            Num::Int => &[Repr::Int, Repr::Duration],
1011            Num::Float => &[Repr::Float],
1012        }
1013    }
1014
1015    /// Whether `layout` names an entry of the program's layout table.
1016    fn layout_exists(&mut self, at: Option<usize>, layout: LayoutId) -> bool {
1017        self.in_range(at, layout.index(), self.program.layouts.len(), "layout")
1018    }
1019
1020    /// Whether the location at `slot` is a value of `layout`: it is inside
1021    /// the frame, and its words are the layout's words in order.
1022    ///
1023    /// This is the check the whole representation turns on. A location is a
1024    /// base slot and a layout, and the frame's per-slot reprs are what a
1025    /// collection reads — so a location whose words disagree with what is
1026    /// being moved into it is a reference the collector will miss or a
1027    /// scalar it will follow.
1028    fn fits(&mut self, at: Option<usize>, slot: Slot, layout: LayoutId, what: &str) -> bool {
1029        let words = self.program.layout(layout).words.clone();
1030        let name = self.program.layout(layout).name.clone();
1031        let size = self.function.frame_size();
1032        if slot as u64 + words.len() as u64 > size as u64 {
1033            self.fault(
1034                at,
1035                format!(
1036                    "{what} is `{name}`, {} words at slot {slot}, and the frame has {size}",
1037                    words.len()
1038                ),
1039            );
1040            return false;
1041        }
1042        for (offset, want) in words.iter().enumerate() {
1043            let found = self.function.reprs[slot as usize + offset];
1044            if found != *want {
1045                self.fault(
1046                    at,
1047                    format!(
1048                        "{what} is `{name}`, whose word {offset} is {want}, but slot {} holds \
1049                         {found}",
1050                        slot as usize + offset
1051                    ),
1052                );
1053                return false;
1054            }
1055        }
1056        true
1057    }
1058
1059    /// What slot `slot` holds, reporting a slot outside the frame.
1060    fn repr(&mut self, at: Option<usize>, slot: Slot) -> Option<Repr> {
1061        match self.function.repr(slot) {
1062            Some(repr) => Some(repr),
1063            None => {
1064                let size = self.function.frame_size();
1065                self.fault(at, format!("names slot {slot}, outside a frame of {size}"));
1066                None
1067            }
1068        }
1069    }
1070
1071    fn expect(&mut self, at: Option<usize>, slot: Slot, want: &[Repr]) {
1072        let Some(found) = self.repr(at, slot) else {
1073            return;
1074        };
1075        if !want.contains(&found) {
1076            let names: Vec<&str> = want.iter().map(|repr| repr.name()).collect();
1077            self.fault(
1078                at,
1079                format!(
1080                    "slot {slot} holds {found}, but this wants {}",
1081                    names.join(" or ")
1082                ),
1083            );
1084        }
1085    }
1086
1087    fn target(&mut self, at: Option<usize>, to: u32) {
1088        if to as usize >= self.function.code.len() {
1089            let len = self.function.code.len();
1090            self.fault(at, format!("jumps to {to}, past the {len} instructions"));
1091        }
1092    }
1093
1094    fn in_range(&mut self, at: Option<usize>, index: usize, len: usize, what: &str) -> bool {
1095        if index >= len {
1096            self.fault(at, format!("names {what} {index}, and there are {len}"));
1097            false
1098        } else {
1099            true
1100        }
1101    }
1102
1103    /// Whether a field access at word `word` of `obj` stays inside the
1104    /// object, where what `obj` holds is a static fact.
1105    ///
1106    /// The width is the layout being moved, so this is the whole run and not
1107    /// only its first word: reading a two-word `Point` out of the last word
1108    /// of an object reads one word of whatever the allocator put after it.
1109    fn reaches(&mut self, at: Option<usize>, obj: Slot, word: u32, layout: LayoutId, what: &str) {
1110        let width = self.program.layout(layout).width();
1111        self.reaches_word(at, obj, word, width, what);
1112    }
1113
1114    /// The same, for a run of a width the caller already knows.
1115    ///
1116    /// Silent where the object's layout is not static, or where it is but the
1117    /// header's `len` is what decides how many payload words it has: a
1118    /// `Shape::Str` or a `Shape::Elements` object is as long as it was
1119    /// allocated, and only the machine has the header to ask. Those are the
1120    /// accesses the machine's own bounds check answers.
1121    fn reaches_word(&mut self, at: Option<usize>, obj: Slot, word: u32, width: u32, what: &str) {
1122        let Some(Some(id)) = self.objects.get(obj as usize).copied() else {
1123            return;
1124        };
1125        let described = self.program.layout(id);
1126        let Some(words) = described.fixed_payload_words(&self.program.layouts) else {
1127            return;
1128        };
1129        if word as u64 + width as u64 > words as u64 {
1130            let name = described.name.clone();
1131            self.fault(
1132                at,
1133                format!("{what} {width} word(s) at word {word} of a `{name}`, which has {words}"),
1134            );
1135        }
1136    }
1137
1138    /// When `obj` is known to be a [`Shape::Closure`] and `word` is its
1139    /// callee field, checks that `src` is a known [`Inst::FuncRef`] naming
1140    /// the same callee the closure's own layout does.
1141    ///
1142    /// This is the comparison the module doc calls out: a closure's callee
1143    /// is carried twice, once in its [`Shape::Closure::function`] and once in
1144    /// the word [`Inst::FuncRef`] writes into its environment, and until this
1145    /// nothing checked the two agreed. It is silent whenever either half is
1146    /// not a static fact — `obj`'s layout from [`Check::objects`], `src`'s
1147    /// callee from [`Check::funcs`] — for the reason [`Check::slot_facts`]
1148    /// declines to guess there: a slot written by more than one thing, or by
1149    /// something this analysis was not taught, answers `None` rather than a
1150    /// wrong guess.
1151    fn check_closure_callee(&mut self, at: Option<usize>, obj: Slot, word: u32, src: Slot) {
1152        let Some(Some(layout_id)) = self.objects.get(obj as usize).copied() else {
1153            return;
1154        };
1155        let described = self.program.layout(layout_id);
1156        let Shape::Closure { function, .. } = &described.shape else {
1157            return;
1158        };
1159        // Payload word 0 is the callee's `FunctionId`; see `Shape::Closure`.
1160        if word != 0 {
1161            return;
1162        }
1163        let Some(Some(callee)) = self.funcs.get(src as usize).copied() else {
1164            return;
1165        };
1166        if callee != *function {
1167            let name = described.name.clone();
1168            // Symbolic, not `FunctionId`'s bare `Display` — the whole point
1169            // issue #275 makes of this message, which is otherwise the last
1170            // place in the crate a diagnostic still named a function by its
1171            // position in `Program::functions`. The fallback to the raw id
1172            // mirrors `print::name_of`'s for a `LayoutId`: this runs over a
1173            // program the verifier has not yet vouched for, so a fault about
1174            // a callee that is itself out of range should say so rather than
1175            // panic indexing into the table it is complaining about.
1176            let named = |id: FunctionId| match self.program.functions.get(id.index()) {
1177                Some(f) => format!("@{}", f.qualified()),
1178                None => id.to_string(),
1179            };
1180            self.fault(
1181                at,
1182                format!(
1183                    "stores {} into the callee field of a `{name}` closure, whose layout \
1184                     names {}",
1185                    named(callee),
1186                    named(*function)
1187                ),
1188            );
1189        }
1190    }
1191
1192    /// Every argument is a value location of the layout it names, and that
1193    /// location is inside the frame.
1194    ///
1195    /// This is what an argument carrying its layout buys the verifier. It
1196    /// used to check only that the slot existed, because a slot was the whole
1197    /// of what an argument was — so a call passing the last slot of a frame
1198    /// as a two-word `Point` was checked by nothing, and the machine read the
1199    /// frame above it.
1200    fn each_arg(&mut self, at: Option<usize>, args: crate::ArgsId) {
1201        if !self.in_range(at, args.index(), self.program.args.len(), "argument list") {
1202            return;
1203        }
1204        for (index, arg) in self.program.arg_list(args).to_vec().into_iter().enumerate() {
1205            if self.layout_exists(at, arg.layout) {
1206                self.fits(at, arg.slot, arg.layout, &format!("argument {index}"));
1207            }
1208        }
1209    }
1210
1211    /// The same, where the callee declares what it takes: each argument's
1212    /// layout is the parameter's, and its location is a value of it.
1213    ///
1214    /// The layouts are compared rather than only the locations' words,
1215    /// because two layouts can have the same words and not be the same
1216    /// family — an `Error` and a `String` are both one `Repr::Ref` — and it
1217    /// is the argument's layout that the machine hands a builtin and a host.
1218    /// The copy into the callee's frame is made at the *parameter's* width:
1219    /// the frame being written is the callee's, and only `Function::params`
1220    /// is a fact about the callee. This is what makes the two agree.
1221    fn check_args(
1222        &mut self,
1223        at: Option<usize>,
1224        args: crate::ArgsId,
1225        want: &[LayoutId],
1226        name: &str,
1227    ) {
1228        if !self.in_range(at, args.index(), self.program.args.len(), "argument list") {
1229            return;
1230        }
1231        let passed = self.program.arg_list(args).to_vec();
1232        if passed.len() != want.len() {
1233            self.fault(
1234                at,
1235                format!(
1236                    "passes {} arguments to `{name}`, which declares {}",
1237                    passed.len(),
1238                    want.len()
1239                ),
1240            );
1241            return;
1242        }
1243        for (index, (arg, layout)) in passed.into_iter().zip(want).enumerate() {
1244            if !self.layout_exists(at, *layout) {
1245                continue;
1246            }
1247            if arg.layout != *layout {
1248                let passed = self.name_of(arg.layout);
1249                let declared = self.program.layout(*layout).name.clone();
1250                self.fault(
1251                    at,
1252                    format!(
1253                        "argument {index} of `{name}` is passed as a `{passed}`, and the \
1254                         parameter is a `{declared}`"
1255                    ),
1256                );
1257                continue;
1258            }
1259            self.fits(
1260                at,
1261                arg.slot,
1262                *layout,
1263                &format!("argument {index} of `{name}`"),
1264            );
1265        }
1266    }
1267
1268    /// [`Inst::CopyBytes`]'s five arguments: `dst`, `dst_at`, `src`,
1269    /// `src_at`, `len`, in that order.
1270    ///
1271    /// Checked by `Repr` rather than by [`Self::check_args`]'s declared
1272    /// [`LayoutId`], because `dst` and `src` do not have one: `src` may be a
1273    /// `String` or another [`crate::Shape::Bytes`] run, and which of the two
1274    /// is a run-time fact rather than something a lowering could declare the
1275    /// way a call declares its parameters. What is static is that both are
1276    /// references and the other three are integers, so that is what this
1277    /// asks.
1278    fn check_copy_bytes_args(&mut self, at: Option<usize>, args: crate::ArgsId) {
1279        if !self.in_range(at, args.index(), self.program.args.len(), "argument list") {
1280            return;
1281        }
1282        const NAMES: [&str; 5] = ["dst", "dst_at", "src", "src_at", "len"];
1283        const WANTS: [Repr; 5] = [Repr::Ref, Repr::Int, Repr::Ref, Repr::Int, Repr::Int];
1284        let passed = self.program.arg_list(args).to_vec();
1285        if passed.len() != NAMES.len() {
1286            self.fault(
1287                at,
1288                format!(
1289                    "copies bytes with {} argument(s), and this needs {} ({})",
1290                    passed.len(),
1291                    NAMES.len(),
1292                    NAMES.join(", ")
1293                ),
1294            );
1295            return;
1296        }
1297        for (arg, want) in passed.iter().zip(WANTS) {
1298            self.expect(at, arg.slot, &[want]);
1299        }
1300    }
1301
1302    /// [`Inst::AppendBytes`]'s four arguments: `buffer`, `src`, `from`, `to`,
1303    /// in that order.
1304    ///
1305    /// Checked by `Repr` rather than by declared [`LayoutId`], for
1306    /// [`Self::check_copy_bytes_args`]'s reason: `src` may be a `String` or a
1307    /// [`crate::Shape::Bytes`] run and which of the two is a run-time fact. So
1308    /// is whether `buffer` names a real owner; what is static is that both are
1309    /// references and both offsets are integers.
1310    fn check_append_bytes_args(&mut self, at: Option<usize>, args: crate::ArgsId) {
1311        if !self.in_range(at, args.index(), self.program.args.len(), "argument list") {
1312            return;
1313        }
1314        const NAMES: [&str; 4] = ["buffer", "src", "from", "to"];
1315        const WANTS: [Repr; 4] = [Repr::Ref, Repr::Ref, Repr::Int, Repr::Int];
1316        let passed = self.program.arg_list(args).to_vec();
1317        if passed.len() != NAMES.len() {
1318            self.fault(
1319                at,
1320                format!(
1321                    "appends bytes with {} argument(s), and this needs {} ({})",
1322                    passed.len(),
1323                    NAMES.len(),
1324                    NAMES.join(", ")
1325                ),
1326            );
1327            return;
1328        }
1329        for (arg, want) in passed.iter().zip(WANTS) {
1330            self.expect(at, arg.slot, &[want]);
1331        }
1332    }
1333
1334    /// What a layout is called, or its id where the table is too short.
1335    fn name_of(&self, layout: LayoutId) -> String {
1336        match self.program.layouts.get(layout.index()) {
1337            Some(held) => held.name.to_string(),
1338            None => layout.to_string(),
1339        }
1340    }
1341}
1342
1343/// The id of the function being checked is carried so that a future fault
1344/// can name it by id as well as by name; nothing reads it yet.
1345impl Check<'_> {
1346    #[allow(dead_code)]
1347    fn id(&self) -> FunctionId {
1348        self.id
1349    }
1350}
1351
1352#[cfg(test)]
1353mod tests {
1354    use std::sync::Arc;
1355
1356    use cove_diag::{FileId, Span};
1357
1358    use super::*;
1359    use crate::inst::{ArithOp, CmpOp, Compare, Inst, Num};
1360    use crate::layout::{Case, Layout, Shape};
1361    use crate::program::{Arg, Function, HostOp, Local, Table, TableId};
1362    use crate::{ArgsId, HostOpId};
1363
1364    const INT: LayoutId = LayoutId(0);
1365    const STR: LayoutId = LayoutId(1);
1366    const POINT: LayoutId = LayoutId(2);
1367    /// `[disc: Int, Ref]`, the shape an `Option<String>` has.
1368    const ANSWER: LayoutId = LayoutId(3);
1369    /// A second two-`Int` struct: the same words as [`POINT`] and a different
1370    /// family, which is what an argument's layout is checked against.
1371    const PAIR: LayoutId = LayoutId(4);
1372    const BOXED: LayoutId = LayoutId(5);
1373    /// A closure over nothing, whose layout says its callee is `FunctionId(1)`
1374    /// — the second function [`program`] is given, in the tests that need
1375    /// one. See [`Check::check_closure_callee`].
1376    const CLOSURE: LayoutId = LayoutId(6);
1377    /// A two-case enum, for the checks a case index needs an enum to make.
1378    const ENUM: LayoutId = LayoutId(7);
1379
1380    fn layouts() -> Vec<Layout> {
1381        vec![
1382            Layout::word("Int", Repr::Int),
1383            Layout::object("String", Shape::Str),
1384            Layout::inline(
1385                "Point",
1386                Shape::Struct {
1387                    fields: Vec::new(),
1388                    opaque: false,
1389                },
1390                vec![Repr::Int, Repr::Int],
1391            ),
1392            Layout::inline(
1393                "Option",
1394                Shape::Enum {
1395                    cases: Vec::new(),
1396                    payload: vec![Repr::Ref],
1397                },
1398                vec![Repr::Int, Repr::Ref],
1399            ),
1400            Layout::inline(
1401                "Pair",
1402                Shape::Struct {
1403                    fields: Vec::new(),
1404                    opaque: false,
1405                },
1406                vec![Repr::Int, Repr::Int],
1407            ),
1408            Layout::object("Any", Shape::Boxed),
1409            Layout::object(
1410                "closure g",
1411                Shape::Closure {
1412                    function: FunctionId(1),
1413                    captures: Vec::new(),
1414                },
1415            ),
1416            Layout::inline(
1417                "m.E",
1418                Shape::Enum {
1419                    cases: vec![
1420                        Case {
1421                            name: Arc::from("A"),
1422                            parts: Vec::new(),
1423                        },
1424                        Case {
1425                            name: Arc::from("B"),
1426                            parts: Vec::new(),
1427                        },
1428                    ],
1429                    payload: vec![Repr::Int],
1430                },
1431                vec![Repr::Tag, Repr::Int],
1432            ),
1433        ]
1434    }
1435
1436    fn span() -> Span {
1437        Span::new(FileId(0), 0, 0)
1438    }
1439
1440    fn function(reprs: Vec<Repr>, returns: LayoutId, code: Vec<Inst>) -> Function {
1441        Function {
1442            module: Arc::from("m"),
1443            name: Arc::from("f"),
1444            params: Vec::new(),
1445            spans: vec![span(); code.len()],
1446            refs: RefMap::of(&reprs),
1447            reprs,
1448            returns,
1449            captures: Vec::new(),
1450            code,
1451            locals: Vec::new(),
1452            inlined: Vec::new(),
1453            span: span(),
1454            is_async: false,
1455            stub: false,
1456        }
1457    }
1458
1459    fn program(functions: Vec<Function>) -> Program {
1460        Program {
1461            functions,
1462            layouts: layouts(),
1463            str_layout: STR,
1464            boxed_layout: BOXED,
1465            ..Program::default()
1466        }
1467    }
1468
1469    fn faults(program: &Program) -> Vec<String> {
1470        match verify(program) {
1471            Ok(()) => Vec::new(),
1472            Err(items) => items.into_iter().map(|item| item.what).collect(),
1473        }
1474    }
1475
1476    /// A resource operation is addressed to a `Repr::Host` word, and the
1477    /// operation it names has to be one a resource answers.
1478    ///
1479    /// Neither is a fact about the *handle*: which resource a word names is
1480    /// the host's business and nothing static can say it. What is static is
1481    /// that the receiver holds a name at all and that the call site settled a
1482    /// resource kind, and both are lowering bugs rather than program faults.
1483    #[test]
1484    fn a_resource_call_is_addressed_to_a_host_word_and_names_a_resource() {
1485        let mut held = program(vec![function(
1486            vec![Repr::Int, Repr::Int],
1487            INT,
1488            vec![
1489                Inst::CallResource {
1490                    dst: 0,
1491                    receiver: 1,
1492                    op: HostOpId(0),
1493                    args: ArgsId(0),
1494                },
1495                Inst::Return { src: 0 },
1496            ],
1497        )]);
1498        held.args.push(Vec::new());
1499        held.host_ops.push(HostOp {
1500            module: Arc::from("files"),
1501            operation: Arc::from("write"),
1502            resource: None,
1503            result: INT,
1504        });
1505        assert_eq!(
1506            faults(&held),
1507            vec![
1508                "slot 1 holds int, but this wants host".to_string(),
1509                "is addressed to a resource, but `files.write` names no resource kind".to_string(),
1510            ]
1511        );
1512    }
1513
1514    /// The same call, well formed.
1515    #[test]
1516    fn a_resource_call_that_names_a_kind_and_a_handle_is_well_formed() {
1517        let mut held = program(vec![function(
1518            vec![Repr::Int, Repr::Host],
1519            INT,
1520            vec![
1521                Inst::CallResource {
1522                    dst: 0,
1523                    receiver: 1,
1524                    op: HostOpId(0),
1525                    args: ArgsId(0),
1526                },
1527                Inst::Return { src: 0 },
1528            ],
1529        )]);
1530        held.args.push(Vec::new());
1531        held.host_ops.push(HostOp {
1532            module: Arc::from("files"),
1533            operation: Arc::from("write"),
1534            resource: Some(Arc::from("Writer")),
1535            result: INT,
1536        });
1537        assert_eq!(faults(&held), Vec::<String>::new());
1538        assert_eq!(held.host_op(HostOpId(0)).qualified(), "files.Writer.write");
1539    }
1540
1541    #[test]
1542    fn a_well_formed_function_has_nothing_to_say_about_it() {
1543        let f = function(
1544            vec![Repr::Int, Repr::Int],
1545            POINT,
1546            vec![Inst::Return { src: 0 }],
1547        );
1548        assert_eq!(faults(&program(vec![f])), Vec::<String>::new());
1549    }
1550
1551    #[test]
1552    fn a_copy_whose_destination_is_not_the_layout_s_words_is_a_fault() {
1553        // The whole representation turns on this: a location is a base slot
1554        // and a layout, and a copy of the wrong width is a reference the
1555        // collector will miss or a scalar it will follow.
1556        let f = function(
1557            vec![Repr::Int, Repr::Ref, Repr::Int, Repr::Int, Repr::Unit],
1558            INT,
1559            vec![
1560                Inst::Copy {
1561                    dst: 0,
1562                    src: 2,
1563                    layout: POINT,
1564                },
1565                Inst::Return { src: 4 },
1566            ],
1567        );
1568        assert_eq!(
1569            faults(&program(vec![f])),
1570            vec![
1571                "the destination of a copy is `Point`, whose word 1 is int, but slot 1 holds ref"
1572                    .to_string(),
1573                "what is returned is `Int`, whose word 0 is int, but slot 4 holds unit".to_string(),
1574            ]
1575        );
1576    }
1577
1578    #[test]
1579    fn a_location_that_runs_off_the_end_of_the_frame_is_a_fault() {
1580        let f = function(
1581            vec![Repr::Int, Repr::Int],
1582            INT,
1583            vec![
1584                Inst::Copy {
1585                    dst: 1,
1586                    src: 0,
1587                    layout: POINT,
1588                },
1589                Inst::Return { src: 0 },
1590            ],
1591        );
1592        assert_eq!(
1593            faults(&program(vec![f])),
1594            vec!["the destination of a copy is `Point`, 2 words at slot 1, and the frame has 2"]
1595        );
1596    }
1597
1598    #[test]
1599    fn a_reference_map_that_disagrees_with_the_reprs_is_a_fault() {
1600        // A collection walks frames using the map, so a lowering that wrote
1601        // a reference into a slot the map calls an `Int` would produce a
1602        // dangling reference at the next collection.
1603        let mut f = function(vec![Repr::Ref], STR, vec![Inst::Return { src: 0 }]);
1604        f.refs = RefMap::of(&[Repr::Int]);
1605        assert_eq!(
1606            faults(&program(vec![f])),
1607            vec![
1608                "reference map disagrees with the frame's reprs, so a collection would scan the \
1609                 wrong slots"
1610            ]
1611        );
1612    }
1613
1614    /// A local names a location and a stretch of code, and both have to be
1615    /// there. Nothing runs one — it is read when a person asks what a frame
1616    /// holds — so the fault it prevents is not a wrong answer at a
1617    /// collection but a debugger reading a slot or an instruction that does
1618    /// not exist.
1619    #[test]
1620    fn a_local_outside_the_frame_or_past_the_last_instruction_is_a_fault() {
1621        let mut f = function(
1622            vec![Repr::Int, Repr::Int],
1623            INT,
1624            vec![Inst::Return { src: 0 }],
1625        );
1626        f.locals = vec![
1627            Local {
1628                name: Arc::from("wide"),
1629                slot: 1,
1630                layout: POINT,
1631                from: 0,
1632                to: 1,
1633            },
1634            Local {
1635                name: Arc::from("late"),
1636                slot: 0,
1637                layout: INT,
1638                from: 0,
1639                to: 4,
1640            },
1641        ];
1642        assert_eq!(
1643            faults(&program(vec![f])),
1644            vec![
1645                "local `wide` is `Point`, 2 words at slot 1, and the frame has 2".to_string(),
1646                "local `late` is live to 4 and the function has 1 instructions".to_string(),
1647            ]
1648        );
1649    }
1650
1651    /// And a range has to be one: `[from, to)` is half-open, so `from > to`
1652    /// is not an empty binding but a table nothing can be read out of.
1653    #[test]
1654    fn a_local_bound_after_it_is_freed_is_a_fault() {
1655        let mut f = function(vec![Repr::Int], INT, vec![Inst::Return { src: 0 }]);
1656        f.locals = vec![Local {
1657            name: Arc::from("backwards"),
1658            slot: 0,
1659            layout: INT,
1660            from: 1,
1661            to: 0,
1662        }];
1663        assert_eq!(
1664            faults(&program(vec![f])),
1665            vec!["local `backwards` is bound at 1 and freed at 0"]
1666        );
1667    }
1668
1669    #[test]
1670    fn a_call_whose_arguments_are_not_the_callee_s_parameters_is_a_fault() {
1671        let mut callee = function(
1672            vec![Repr::Int, Repr::Int, Repr::Int],
1673            INT,
1674            vec![Inst::Return { src: 2 }],
1675        );
1676        callee.params = vec![POINT];
1677        callee.name = Arc::from("g");
1678        let caller = function(
1679            vec![Repr::Int, Repr::Ref, Repr::Int],
1680            INT,
1681            vec![
1682                Inst::Call {
1683                    dst: 0,
1684                    callee: FunctionId(0),
1685                    args: crate::ArgsId(0),
1686                },
1687                Inst::Return { src: 0 },
1688            ],
1689        );
1690        let mut held = program(vec![callee, caller]);
1691        held.args = vec![vec![Arg {
1692            slot: 1,
1693            layout: POINT,
1694        }]];
1695        assert_eq!(
1696            faults(&held),
1697            vec![
1698                "argument 0 of `m.g` is `Point`, whose word 0 is int, but slot 1 holds ref"
1699                    .to_string()
1700            ]
1701        );
1702    }
1703
1704    #[test]
1705    fn a_call_that_passes_the_wrong_number_of_arguments_is_a_fault() {
1706        let mut callee = function(
1707            vec![Repr::Int, Repr::Int],
1708            INT,
1709            vec![Inst::Return { src: 1 }],
1710        );
1711        callee.params = vec![INT];
1712        callee.name = Arc::from("g");
1713        let caller = function(
1714            vec![Repr::Int],
1715            INT,
1716            vec![
1717                Inst::Call {
1718                    dst: 0,
1719                    callee: FunctionId(0),
1720                    args: crate::ArgsId(0),
1721                },
1722                Inst::Return { src: 0 },
1723            ],
1724        );
1725        let mut held = program(vec![callee, caller]);
1726        held.args = vec![Vec::new()];
1727        assert_eq!(
1728            faults(&held),
1729            vec!["passes 0 arguments to `m.g`, which declares 1"]
1730        );
1731    }
1732
1733    /// The whole of why a discriminant is a `Repr` of its own.
1734    ///
1735    /// A tag and an `Int` are the same bits in the same kind of word, and
1736    /// before this they were the same *type*, so nothing stopped an enum's
1737    /// case index being added to. Nothing rejects it at run time either —
1738    /// the machine adds two words — so the only place it can be caught is
1739    /// here.
1740    #[test]
1741    fn a_tag_cannot_be_added_to() {
1742        let f = function(
1743            vec![Repr::Int, Repr::Ref, Repr::Tag],
1744            ANSWER,
1745            vec![
1746                Inst::Arith {
1747                    num: Num::Int,
1748                    op: ArithOp::Add,
1749                    dst: 0,
1750                    a: 2,
1751                    b: 0,
1752                },
1753                Inst::Return { src: 0 },
1754            ],
1755        );
1756        assert_eq!(
1757            faults(&program(vec![f])),
1758            vec!["slot 2 holds tag, but this wants int or duration"]
1759        );
1760    }
1761
1762    /// And cannot be ordered, or compared against an integer.
1763    ///
1764    /// Equality is refused with the rest: two tags are compared by
1765    /// dispatching on one, which is what [`Inst::Switch`] is, and a tag
1766    /// against an `Int` is the confusion this separation exists to name.
1767    #[test]
1768    fn a_tag_cannot_be_ordered_or_compared_as_an_integer() {
1769        let ordered = function(
1770            vec![Repr::Int, Repr::Ref, Repr::Tag, Repr::Bool],
1771            ANSWER,
1772            vec![
1773                Inst::Cmp {
1774                    on: Compare::Int,
1775                    op: CmpOp::Lt,
1776                    dst: 3,
1777                    a: 2,
1778                    b: 0,
1779                },
1780                Inst::Return { src: 0 },
1781            ],
1782        );
1783        assert_eq!(
1784            faults(&program(vec![ordered])),
1785            vec!["slot 2 holds tag, but this wants int or duration"]
1786        );
1787
1788        let equal = function(
1789            vec![Repr::Int, Repr::Ref, Repr::Tag, Repr::Bool],
1790            ANSWER,
1791            vec![
1792                Inst::Cmp {
1793                    on: Compare::Int,
1794                    op: CmpOp::Eq,
1795                    dst: 3,
1796                    a: 2,
1797                    b: 0,
1798                },
1799                Inst::Return { src: 0 },
1800            ],
1801        );
1802        assert_eq!(
1803            faults(&program(vec![equal])),
1804            vec!["slot 2 holds tag, but this wants int or duration"]
1805        );
1806    }
1807
1808    /// What a tag *is* accepted by: the instruction that writes one, and the
1809    /// one that dispatches on it. A test that only showed the refusals would
1810    /// pass if the whole family were rejected.
1811    #[test]
1812    fn a_tag_is_written_and_dispatched_on() {
1813        let f = function(
1814            vec![Repr::Int, Repr::Ref, Repr::Tag],
1815            ANSWER,
1816            vec![
1817                Inst::Tag {
1818                    dst: 2,
1819                    layout: ENUM,
1820                    case: crate::CaseId(1),
1821                },
1822                Inst::Switch {
1823                    on: 2,
1824                    table: TableId(0),
1825                },
1826                Inst::Return { src: 0 },
1827            ],
1828        );
1829        let mut held = program(vec![f]);
1830        held.tables = vec![Table {
1831            targets: vec![2, 2],
1832            default: 2,
1833        }];
1834        assert_eq!(faults(&held), Vec::<String>::new());
1835    }
1836
1837    /// A case index is bounded against the enum the same instruction names,
1838    /// which is the check the untyped integer path had no way to make.
1839    #[test]
1840    fn a_tag_naming_a_case_the_enum_does_not_have_is_a_fault() {
1841        let f = function(
1842            vec![Repr::Int, Repr::Ref, Repr::Tag],
1843            ANSWER,
1844            vec![
1845                Inst::Tag {
1846                    dst: 2,
1847                    layout: ENUM,
1848                    case: crate::CaseId(7),
1849                },
1850                Inst::Return { src: 0 },
1851            ],
1852        );
1853        assert_eq!(
1854            faults(&program(vec![f])),
1855            vec!["names case7 of m.E, which has 2 case(s)"]
1856        );
1857    }
1858
1859    #[test]
1860    fn a_switch_on_something_that_is_not_a_discriminant_word_is_a_fault() {
1861        // The discriminant of an enum location is its first word and is an
1862        // `Int`; so is the layout id a `dyn` dispatch switches on. A slot's
1863        // `Repr` is the strongest thing a static check has to say about
1864        // which word this is.
1865        let f = function(
1866            vec![Repr::Int, Repr::Ref],
1867            ANSWER,
1868            vec![
1869                Inst::Switch {
1870                    on: 1,
1871                    table: TableId(0),
1872                },
1873                Inst::Return { src: 0 },
1874            ],
1875        );
1876        let mut held = program(vec![f]);
1877        held.tables = vec![Table {
1878            targets: vec![1],
1879            default: 1,
1880        }];
1881        assert_eq!(
1882            faults(&held),
1883            vec!["slot 1 holds ref, but this wants tag or int"]
1884        );
1885    }
1886
1887    #[test]
1888    fn a_jump_that_lands_past_the_last_instruction_is_a_fault() {
1889        let f = function(
1890            vec![Repr::Int],
1891            INT,
1892            vec![Inst::Jump { to: 9 }, Inst::Return { src: 0 }],
1893        );
1894        assert_eq!(
1895            faults(&program(vec![f])),
1896            vec!["jumps to 9, past the 2 instructions"]
1897        );
1898    }
1899
1900    #[test]
1901    fn an_id_outside_its_table_is_a_fault() {
1902        let f = function(
1903            vec![Repr::Ref],
1904            STR,
1905            vec![
1906                Inst::Str {
1907                    dst: 0,
1908                    text: crate::StrId(3),
1909                },
1910                Inst::Return { src: 0 },
1911            ],
1912        );
1913        assert_eq!(
1914            faults(&program(vec![f])),
1915            vec!["names string 3, and there are 0"]
1916        );
1917    }
1918
1919    #[test]
1920    fn a_body_whose_last_instruction_falls_through_is_a_fault() {
1921        let f = function(vec![Repr::Int], INT, vec![Inst::Int { dst: 0, value: 1 }]);
1922        assert_eq!(
1923            faults(&program(vec![f])),
1924            vec!["the last instruction can fall through, and there is nothing after it"]
1925        );
1926    }
1927
1928    /// An argument used to be checked only for existing, because it was a
1929    /// slot and a slot cannot run off the end of anything. It carries the
1930    /// layout of the location it names now, so a two-word argument at the
1931    /// last slot of a frame is a fault here rather than a read of the frame
1932    /// above at run time.
1933    #[test]
1934    fn an_argument_that_runs_off_the_end_of_the_frame_is_a_fault() {
1935        let f = function(
1936            vec![Repr::Int, Repr::Int, Repr::Bool],
1937            INT,
1938            vec![
1939                Inst::CallBuiltin {
1940                    dst: 0,
1941                    builtin: crate::BuiltinId(0),
1942                    args: crate::ArgsId(0),
1943                },
1944                Inst::Return { src: 0 },
1945            ],
1946        );
1947        let mut held = program(vec![f]);
1948        held.builtins = vec![crate::Builtin {
1949            receiver: Arc::from("Any"),
1950            operation: Arc::from("equals"),
1951            result: INT,
1952        }];
1953        held.args = vec![vec![Arg {
1954            slot: 2,
1955            layout: POINT,
1956        }]];
1957        assert_eq!(
1958            faults(&held),
1959            vec!["argument 0 is `Point`, 2 words at slot 2, and the frame has 3"]
1960        );
1961    }
1962
1963    /// A closure call's destination is checked like every other call's.
1964    ///
1965    /// Which body the call enters is a run-time fact and how wide its answer
1966    /// is, is not: the checker settled the call against the callee's function
1967    /// type, so `Inst::CallClosure` carries the layout and this asks the same
1968    /// `fits` question of it. Before it did, a two-word answer written into
1969    /// the last slot of a frame was checked by nothing here, and the machine
1970    /// wrote the frame above it.
1971    #[test]
1972    fn a_closure_calls_answer_that_runs_off_the_end_of_the_frame_is_a_fault() {
1973        let mut held = program(vec![function(
1974            vec![Repr::Int, Repr::Ref, Repr::Int],
1975            INT,
1976            vec![
1977                Inst::CallClosure {
1978                    dst: 2,
1979                    closure: 1,
1980                    args: ArgsId(0),
1981                    result: POINT,
1982                },
1983                Inst::Return { src: 0 },
1984            ],
1985        )]);
1986        held.args.push(Vec::new());
1987        assert_eq!(
1988            faults(&held),
1989            vec![
1990                "the answer of a closure call is `Point`, 2 words at slot 2, and the frame has 3"
1991                    .to_string()
1992            ]
1993        );
1994    }
1995
1996    /// And the same call whose destination is a location of that layout has
1997    /// nothing said about it. `Point` is two `Int` words and slots 0 and 1
1998    /// are two.
1999    #[test]
2000    fn a_closure_call_whose_answer_fits_its_destination_is_well_formed() {
2001        let mut held = program(vec![function(
2002            vec![Repr::Int, Repr::Int, Repr::Ref],
2003            INT,
2004            vec![
2005                Inst::CallClosure {
2006                    dst: 0,
2007                    closure: 2,
2008                    args: ArgsId(0),
2009                    result: POINT,
2010                },
2011                Inst::Return { src: 0 },
2012            ],
2013        )]);
2014        held.args.push(Vec::new());
2015        assert_eq!(faults(&held), Vec::<String>::new());
2016    }
2017
2018    /// Two layouts can have the same words and not be the same family, and it
2019    /// is the argument's layout the machine hands a builtin and a host — so
2020    /// the layouts are compared and not only the locations' reprs.
2021    #[test]
2022    fn an_argument_passed_as_another_family_than_the_parameter_is_a_fault() {
2023        let mut callee = function(
2024            vec![Repr::Int, Repr::Int],
2025            INT,
2026            vec![Inst::Return { src: 0 }],
2027        );
2028        callee.params = vec![POINT];
2029        callee.name = Arc::from("g");
2030        let caller = function(
2031            vec![Repr::Int, Repr::Int],
2032            INT,
2033            vec![
2034                Inst::Call {
2035                    dst: 0,
2036                    callee: FunctionId(0),
2037                    args: crate::ArgsId(0),
2038                },
2039                Inst::Return { src: 0 },
2040            ],
2041        );
2042        let mut held = program(vec![callee, caller]);
2043        held.args = vec![vec![Arg {
2044            slot: 0,
2045            layout: PAIR,
2046        }]];
2047        assert_eq!(
2048            faults(&held),
2049            vec!["argument 0 of `m.g` is passed as a `Pair`, and the parameter is a `Point`"]
2050        );
2051    }
2052
2053    /// A box's width is in the header its allocator writes and not in its
2054    /// shape, so `Alloc` would size one by the wrong thing: a box of a
2055    /// two-word value would be a word short and the copy into it would run
2056    /// off the end of the object. `Inst::Box` is the only correct allocator
2057    /// for one, because it is the only one that is told what is going in.
2058    #[test]
2059    fn allocating_a_box_by_its_shape_is_a_fault() {
2060        let f = function(
2061            vec![Repr::Ref],
2062            STR,
2063            vec![
2064                Inst::Alloc {
2065                    dst: 0,
2066                    layout: BOXED,
2067                    len: Len::Fixed,
2068                },
2069                Inst::Return { src: 0 },
2070            ],
2071        );
2072        assert_eq!(
2073            faults(&program(vec![f])),
2074            vec![
2075                "allocates a `Any`, whose width the header carries and the shape does not; a box \
2076                 is allocated by `box`, which knows what is going into it"
2077            ]
2078        );
2079    }
2080
2081    /// A field access is bounded against the object wherever the slot holding
2082    /// it is written by allocations alone, all naming one layout — which is
2083    /// what a lowering that allocates an object and reads its fields does.
2084    /// Without it a `Copy` at the top of a frame reads the frame above and
2085    /// the machine's own header check is the only thing left.
2086    #[test]
2087    fn a_field_past_an_object_of_a_known_layout_is_a_fault() {
2088        let f = function(
2089            vec![Repr::Ref, Repr::Int, Repr::Int],
2090            INT,
2091            vec![
2092                Inst::Alloc {
2093                    dst: 0,
2094                    layout: POINT,
2095                    len: Len::Fixed,
2096                },
2097                Inst::LoadField {
2098                    dst: 1,
2099                    obj: 0,
2100                    at: 1,
2101                    layout: PAIR,
2102                },
2103                Inst::Return { src: 1 },
2104            ],
2105        );
2106        assert_eq!(
2107            faults(&program(vec![f])),
2108            vec!["read 2 word(s) at word 1 of a `Point`, which has 2"]
2109        );
2110    }
2111
2112    /// And it says nothing where it cannot: a slot a copy wrote holds
2113    /// whatever the source held, and a `Shape::Str` object is as long as it
2114    /// was allocated. Both are the machine's to answer, from the header.
2115    #[test]
2116    fn a_field_of_an_object_whose_layout_is_not_static_is_left_to_the_machine() {
2117        let f = function(
2118            vec![Repr::Ref, Repr::Ref, Repr::Int],
2119            INT,
2120            vec![
2121                Inst::Alloc {
2122                    dst: 0,
2123                    layout: POINT,
2124                    len: Len::Fixed,
2125                },
2126                Inst::Copy {
2127                    dst: 1,
2128                    src: 0,
2129                    layout: STR,
2130                },
2131                Inst::LoadField {
2132                    dst: 2,
2133                    obj: 1,
2134                    at: 9,
2135                    layout: INT,
2136                },
2137                Inst::Str {
2138                    dst: 0,
2139                    text: crate::StrId(0),
2140                },
2141                Inst::LoadField {
2142                    dst: 2,
2143                    obj: 0,
2144                    at: 9,
2145                    layout: INT,
2146                },
2147                Inst::Return { src: 2 },
2148            ],
2149        );
2150        let mut held = program(vec![f]);
2151        held.strings = vec![Arc::from("x")];
2152        // The first `LoadField` names a slot two allocations disagree about
2153        // and the second an object whose payload the header decides.
2154        assert_eq!(faults(&held), Vec::<String>::new());
2155    }
2156
2157    /// A closure's callee is carried twice — once in
2158    /// [`Shape::Closure::function`], the typed fact, and once in the word
2159    /// [`Inst::FuncRef`] writes into its environment's callee field — and
2160    /// until [`Check::check_closure_callee`] nothing compared them. `m.g` is
2161    /// what [`CLOSURE`]'s layout says the environment holds; the body writes
2162    /// `m.f` into it instead.
2163    ///
2164    /// [Issue #275](https://github.com/myuon/cove/issues/275) is why the
2165    /// message names them `@m.f` and `@m.g` rather than `fn0` and `fn1`: a
2166    /// program's second function is given a name of its own, `g`, distinct
2167    /// from [`function`]'s hard-coded `f`, purely so this message has two
2168    /// different symbols to tell apart rather than `m.f` disagreeing with
2169    /// itself.
2170    #[test]
2171    fn a_closures_environment_naming_a_different_callee_than_its_layout_is_a_fault() {
2172        let f = function(
2173            vec![Repr::Ref, Repr::Int],
2174            INT,
2175            vec![
2176                Inst::Alloc {
2177                    dst: 0,
2178                    layout: CLOSURE,
2179                    len: Len::Fixed,
2180                },
2181                Inst::FuncRef {
2182                    dst: 1,
2183                    callee: FunctionId(0),
2184                },
2185                Inst::StoreField {
2186                    obj: 0,
2187                    at: 0,
2188                    src: 1,
2189                    layout: INT,
2190                },
2191                Inst::Return { src: 1 },
2192            ],
2193        );
2194        let mut other = function(vec![Repr::Int], INT, vec![Inst::Return { src: 0 }]);
2195        other.name = Arc::from("g");
2196        assert_eq!(
2197            faults(&program(vec![f, other])),
2198            vec![
2199                "stores @m.f into the callee field of a `closure g` closure, whose layout names @m.g"
2200            ]
2201        );
2202    }
2203
2204    /// The same shape, agreeing: `f#0`'s environment says `f#0`.
2205    #[test]
2206    fn a_closures_environment_naming_its_own_layouts_callee_is_well_formed() {
2207        let f = function(
2208            vec![Repr::Ref, Repr::Int],
2209            INT,
2210            vec![
2211                Inst::Alloc {
2212                    dst: 0,
2213                    layout: CLOSURE,
2214                    len: Len::Fixed,
2215                },
2216                Inst::FuncRef {
2217                    dst: 1,
2218                    callee: FunctionId(1),
2219                },
2220                Inst::StoreField {
2221                    obj: 0,
2222                    at: 0,
2223                    src: 1,
2224                    layout: INT,
2225                },
2226                Inst::Return { src: 1 },
2227            ],
2228        );
2229        let other = function(vec![Repr::Int], INT, vec![Inst::Return { src: 0 }]);
2230        assert_eq!(faults(&program(vec![f, other])), Vec::<String>::new());
2231    }
2232
2233    #[test]
2234    fn a_clear_agrees_with_the_layout_it_zeroes() {
2235        let f = function(
2236            vec![Repr::Int, Repr::Ref, Repr::Unit],
2237            INT,
2238            vec![
2239                Inst::Clear {
2240                    slot: 0,
2241                    layout: ANSWER,
2242                },
2243                Inst::Clear {
2244                    slot: 1,
2245                    layout: ANSWER,
2246                },
2247                Inst::Return { src: 0 },
2248            ],
2249        );
2250        // The first is right — `[Int, Ref]` is what an `Option` is — and the
2251        // second names the same layout one word along, where it is not.
2252        assert_eq!(
2253            faults(&program(vec![f])),
2254            vec!["what a clear zeroes is `Option`, whose word 0 is int, but slot 1 holds ref"]
2255        );
2256    }
2257}