Skip to main content

rucc_opt/
switch_conv.rs

1//! A `switch` whose arms are a function of the label, which is arithmetic and not branches.
2//!
3//! Design: `spec/optimizer/24-switch-lowering.md` section 24.1, which is the transformation the GCC
4//! file `tree-switch-conversion.cc` is named after, and section 24.4, which puts it in the middle
5//! end rather than in the lowering and says why: what it produces is ordinary arithmetic that every
6//! pass after it optimizes, and what it needs to see is arms whose constancy earlier passes made
7//! visible.
8//!
9//! # The shape
10//!
11//! ```c
12//! switch (x) { case 0: return 1; case 1: return 2; case 2: return 3; case 3: return 4; }
13//! return 0;
14//! ```
15//!
16//! Four labels, four arms, and the arm for label `k` gives `k + 1`. The labels run consecutively
17//! and the answers run consecutively with them, so the whole statement is one range check and one
18//! addition. gcc reduces thirty three labels of this to a comparison and a `lea`, which is
19//! tamnd/rucc#728, and rucc emitted a comparison and a jump per label.
20//!
21//! Where the answers are an affine function of the label, `a * x + b`, there is nothing to look
22//! up. That covers the shape above with `a` of one and `b` of one, the shape where every arm gives
23//! the same answer with `a` of zero, and the scaled ones in between.
24//!
25//! Where they are not, the answers are a table. The arm for label `k` gives the constant in cell
26//! `k - low` of a read only array, and every arm becomes one load from it. That is section 24.4's
27//! other half, and it is what gcc calls a `CSWTCH` array. The array is asked for through
28//! `crate::readonly`, because a pass is handed one function and the array is the module's.
29//!
30//! # What it rewrites and what it leaves
31//!
32//! The `switch` stays a `switch`. Every case edge is pointed at one new block, which works the
33//! answer out and hands it on, and the default edge is not touched at all. What that buys is that
34//! the range check is not written here: a `switch` whose cases are consecutive and all go to one
35//! place is exactly `crates/rucc-codegen/src/switch.rs`'s `Cluster::Run`, which is one subtraction
36//! and one unsigned comparison however long the run is, and which already gets the modular
37//! arithmetic and the run that covers a whole type right. Writing a second range check here would
38//! be a second place for section 24.6's overflow to be got wrong.
39//!
40//! The default is untouched for the reason section 24.6 gives, which is that the default is never
41//! dropped. A value that matches no case went to the default before this ran and goes to the same
42//! place afterwards, because the edge it goes down is the same edge.
43//!
44//! # What has to be true
45//!
46//! For arithmetic, the labels are consecutive. A hole in the labels is a stretch the lowering
47//! would then have to cut the run at, and one comparison becomes several for a function that was
48//! only fitted to the labels either side of it.
49//!
50//! For a table, the labels may have holes. Where the default hands on what the arms hand on with a
51//! constant in the answer's place, which is `default: return 0;` and `default: y = 0; break;`, a
52//! hole's cell is that constant and the hole is given a case of its own going to the load, as gcc's
53//! `gather_default_values` does. The labels are then one run, which the lowering checks with one
54//! comparison, where a run with holes in it is a comparison and a bit test, and the bit test is a
55//! branch a stream of values mispredicts. Where the default does anything else, the `switch` still
56//! sends a value in a hole to the default, the hole's cell is never read, and it is written as zero
57//! only because an array has to have something there. What bounds the holes is size: the table spans at most eight cells for every label it replaces, which is gcc's
58//! `switch-conversion-max-branch-ratio`, so a `switch` over three labels a thousand apart stays a
59//! `switch`.
60//!
61//! Every arm is a block nothing else reaches, holding nothing but the constants it hands on, and
62//! ending the same way as every other arm. The same way means a jump to the same block, or a
63//! return, and in either case with the same values in every position but one. That one is the
64//! answer. Section 24.5 gives up on arms that assign more than one thing and so does this.
65//!
66//! The answers are `a * label + b` at every label, checked at every label rather than fitted to two
67//! of them and believed. The check is done in the answer's own width with wrapping, because that is
68//! what the arithmetic this writes will do, and the arithmetic is written with no flags on it so
69//! that wrapping is what it is allowed to do.
70//!
71//! For arithmetic, the label and the answer are the same width. A `switch` on an `int` whose arms
72//! give a `long` is the same transformation with a widening in front of the multiply, and which
73//! widening it is depends on how the label is read, which is a question this would have to answer
74//! and currently declines to ask. A table does not have that question, because the label only
75//! picks a cell and the cell is already as wide as the answer, so a table may be any whole number
76//! of bytes wide up to eight whatever the label is. A label wider than a word gets no table, since
77//! the index into one is a word.
78//!
79//! # Why three labels and not two
80//!
81//! Two labels and a default is a shape `phiopt` already has something to say about, and what it
82//! says is a `select` between two constants that cost nothing to materialize. The arithmetic this
83//! writes is a multiply and an add against a range check, which is not obviously better than that
84//! and is worse when `a` is not one. From three labels up the chain being replaced is at least six
85//! instructions and what replaces it is at most five, so it is a win at three and grows from there.
86//!
87//! A table is held to the same three. What replaces the chain is a subtraction, the range check,
88//! a widening and a load, which is five again, and the load is from a line the program keeps
89//! reading if the `switch` is hot.
90//!
91//! # Where the table goes
92//!
93//! The array is internal, constant and aligned to its cell, which puts it in `.rodata`. It is
94//! named `CSWTCH.` and a number, as gcc names it, which nothing written in C can spell.
95//!
96//! When the goal is size a cell is as narrow as the answers allow, and the load is widened back to
97//! the answer's width with a sign or without one, whichever holds every answer. gcc 16 does the
98//! same at `-Os` and not at `-O2`, where the widening is an instruction on the path and the bytes
99//! it saves are data rather than code. Three `int` answers under ten are twelve bytes at `-O2` and
100//! three at `-Os`, in gcc and here.
101//!
102//! # A table of where the answers are
103//!
104//! An arm may give an address rather than a number, which is `case 0: return "zero";`. gcc 16
105//! makes those a `CSWTCH` array of pointers only under `-fno-pic`. In a position independent
106//! executable it keeps the compares, because every cell would be an address the loader has to
107//! write at startup, and the array would have to be in `.data.rel.ro` to be written at all. rucc
108//! only builds position independent code, so it does what clang does instead: a cell is four
109//! bytes holding how far the answer is from the table, which the linker works out once and the
110//! loader never touches. The answer is the table's address plus the cell, so the array stays in
111//! `.rodata` and what replaces the chain is one load and one addition.
112//!
113//! The distance has to be a number once the program is linked, so every answer has to be read
114//! only data this file defines and no other object can replace, which is what
115//! `crate::image::Images` holds, or a constant number of bytes into it, which is `&table[2]` and
116//! is the same distance with the bytes added. And it needs a four byte relocation measured from where it is
117//! written, which x86-64 ELF has and `crate::ReadOnly` says so. Anywhere else the `switch` stays
118//! a `switch`.
119//!
120//! The sum is made as an integer and turned back into a pointer, rather than added to the table's
121//! address, because the answer is not in the table and `crate::alias::origin` would say it was.
122
123use std::cmp::Ordering;
124use std::collections::HashSet;
125
126use rucc_base::Symbol;
127use rucc_ir::{
128    Block, BlockCall, Builder, Def, Extra, Flags, Func, Imm, Inst, InstData, MemInfo, MemOrder,
129    Opcode, Restrict, Type, Value,
130};
131
132use rucc_cost::Goal;
133use rucc_cost::heuristics::SWITCH_CONVERSION_MAX_GROWTH;
134
135use crate::cfg::Cfg;
136use crate::{Analyses, Fuel, Pass, Preserved, ReadOnly, Stats};
137
138/// What is reported when a `switch` becomes arithmetic.
139const CONVERTED: &str = "switch replaced by a range check and the arithmetic its arms were doing";
140
141/// What is reported when a `switch` becomes a load from a table of what its arms gave.
142const TABLED: &str = "switch replaced by a range check and a load from a table of its answers";
143
144/// What is reported when a `switch` becomes a load from a table of how far its answers are.
145const PLACED: &str =
146    "switch replaced by a range check and a load from a table of how far away its answers are";
147
148/// What is reported when the pass ran out of fuel with a `switch` it was about to convert.
149const NO_FUEL: &str = "switch left alone, the pass ran out of fuel";
150
151/// What is reported for a `switch` with too few labels to pay for the arithmetic.
152const TOO_FEW: &str = "switch left alone, it has too few labels for arithmetic to be cheaper";
153
154/// What is reported for a `switch` whose labels have holes in them.
155const NOT_CONSECUTIVE: &str = "switch left alone, its labels are not consecutive";
156
157/// What is reported for a `switch` with an arm that is not a block of its own.
158const ARM_IS_SHARED: &str = "switch left alone, an arm is reached from somewhere other than it";
159
160/// What is reported for a `switch` with an arm that does something.
161const ARM_DOES_WORK: &str = "switch left alone, an arm does more than work out a constant";
162
163/// What is reported for a `switch` whose arms do not end alike.
164const ARMS_DIFFER: &str = "switch left alone, its arms do not all hand on the same thing";
165
166/// What is reported for a `switch` whose answers are not a line.
167const NOT_AFFINE: &str = "switch left alone, its answers are not a fixed multiple of the label \
168                          plus a constant";
169
170/// What is reported for a `switch` whose answers are a different width from its labels.
171const WIDTHS_DIFFER: &str = "switch left alone, its answers are not as wide as its labels";
172
173/// What is reported for a `switch` whose labels are too far apart for a table of them.
174const TOO_SPARSE: &str = "switch left alone, a table of its answers would be mostly holes";
175
176/// What is reported for a `switch` whose label is wider than an index into a table.
177const LABEL_TOO_WIDE: &str = "switch left alone, its label is wider than a word";
178
179/// What is reported for a `switch` whose answers are not something a table cell holds.
180const CELL_IS_ODD: &str =
181    "switch left alone, its answers are not a whole number of bytes of integer";
182
183/// What is reported for a `switch` whose answers are addresses a table cannot say where are.
184const PLACE_IS_ODD: &str =
185    "switch left alone, its answers are not all addresses of read only data this file defines";
186
187/// The fewest labels worth converting, per the module documentation.
188const LABELS: usize = 3;
189
190/// How many cells a table may have for every label it stands for, per the module documentation.
191const GROWTH: i128 = SWITCH_CONVERSION_MAX_GROWTH as i128;
192
193/// The pass.
194#[derive(Debug)]
195pub struct SwitchConv;
196
197impl Pass for SwitchConv {
198    fn name(&self) -> &'static str {
199        "switch-conv"
200    }
201
202    fn describe(&self) -> &'static str {
203        "a switch whose arms give constants becomes a range check and arithmetic or a table load"
204    }
205
206    fn preserves(&self) -> Preserved {
207        // A block appears, the arms go, and every case edge moves.
208        Preserved::NONE
209    }
210
211    fn run(&self, func: &mut Func, an: &mut Analyses, fuel: &mut Fuel) -> Stats {
212        convert(func, an, fuel, None)
213    }
214
215    fn run_emitting(
216        &self,
217        func: &mut Func,
218        an: &mut Analyses,
219        fuel: &mut Fuel,
220        data: &mut ReadOnly<'_>,
221    ) -> Stats {
222        convert(func, an, fuel, Some(data))
223    }
224}
225
226/// The pass, with somewhere to put a table or without one.
227///
228/// Without one is what a caller that is not the pipeline gets, and it is arithmetic or nothing.
229fn convert(
230    func: &mut Func,
231    an: &mut Analyses,
232    fuel: &mut Fuel,
233    mut data: Option<&mut ReadOnly<'_>>,
234) -> Stats {
235    let mut stats = Stats::new();
236    if func.entry().is_none() {
237        return stats;
238    }
239    let cfg = an.cfg(func);
240    let found: Vec<Inst> = func
241        .blocks()
242        .filter_map(|block| func.terminator(block))
243        .filter(|&inst| func[inst].opcode == Opcode::Switch)
244        .collect();
245
246    let index_bits = data.as_ref().map(|data| data.pointer_bits());
247    let near = near(func, an, data.as_deref());
248    let small = an.machine().goal() == Goal::Size;
249    let mut plans = Vec::new();
250    for inst in found {
251        match plan(func, cfg, inst, index_bits, small, &near) {
252            Ok(plan) => plans.push(plan),
253            Err(why) => stats.missed(why),
254        }
255    }
256
257    let mut changed = false;
258    for plan in plans {
259        if !fuel.take() {
260            stats.missed(NO_FUEL);
261            continue;
262        }
263        let table = match (&plan.how, data.as_deref_mut()) {
264            (How::Table { cell, cells, .. }, Some(data)) => {
265                Some(data.table(cell.ty, cells.clone()))
266            }
267            (How::Distances { to, .. }, Some(data)) => Some(data.distances(to)),
268            _ => None,
269        };
270        stats.optimized(match (&plan.how, table) {
271            (_, None) => CONVERTED,
272            (How::Distances { .. }, Some(_)) => PLACED,
273            (_, Some(_)) => TABLED,
274        });
275        apply(func, &plan, table);
276        changed = true;
277    }
278    if changed {
279        an.clear();
280    }
281    stats
282}
283
284/// The names an answer may be the address of, when a table may hold how far away they are.
285///
286/// Empty when it may not, which is on a target with no four byte distance or for a caller with
287/// nowhere to put a table. Only the names this function takes the address of are asked about,
288/// since no other name can be an answer.
289fn near(func: &Func, an: &Analyses, data: Option<&ReadOnly<'_>>) -> HashSet<Symbol> {
290    if !data.is_some_and(ReadOnly::measures) {
291        return HashSet::new();
292    }
293    let images = an.images();
294    func.blocks()
295        .flat_map(|block| func.insts(block))
296        .filter_map(|inst| match func[inst] {
297            InstData { opcode: Opcode::GlobalAddr, extra: Extra::Symbol(name), .. } => Some(name),
298            _ => None,
299        })
300        .filter(|&name| images.holds(name))
301        .collect()
302}
303
304/// How the arms of one `switch` hand their answer on.
305#[derive(Clone, Copy, Debug, PartialEq, Eq)]
306enum Hands {
307    /// To this block, as one of its parameters.
308    On(Block),
309    /// Out of the function, as one of its results.
310    Back,
311}
312
313/// One `switch` and what it is about to become.
314#[derive(Debug)]
315struct Plan {
316    /// The `switch` itself.
317    inst: Inst,
318    /// What it switches on, which is what the answer is a function of.
319    value: Value,
320    /// The width of the label.
321    ty: Type,
322    /// Where the answer goes.
323    hands: Hands,
324    /// What every arm handed on, with the answer's position holding whatever the first arm had
325    /// there. That position is rewritten and the rest are passed on as they were.
326    args: Vec<Value>,
327    /// Which of `args` is the answer.
328    answer: usize,
329    /// How the answer is worked out from the label.
330    how: How,
331    /// The blocks the arms were, which nothing reaches once the case edges have moved.
332    arms: Vec<Block>,
333    /// Values between two labels that get a case of their own going to the load, because the
334    /// default gives what their cell holds.
335    holes: Vec<i128>,
336}
337
338/// How the answer is worked out from the label.
339#[derive(Debug)]
340enum How {
341    /// As `scale * label + offset`, at the label's width.
342    Line {
343        /// The multiple of the label.
344        scale: i128,
345        /// What is added to it.
346        offset: i128,
347    },
348    /// As cell `label - low` of a table.
349    Table {
350        /// The lowest label, which is cell zero.
351        low: i128,
352        /// The width of the answer.
353        ty: Type,
354        /// What a cell is, which is the answer unless the goal is size.
355        cell: Cell,
356        /// Every cell, with zero in the holes.
357        cells: Vec<i128>,
358        /// The width of an index into the table, which is a word on the target.
359        index_bits: u32,
360    },
361    /// As the table's address plus cell `label - low`, which is how far the answer is from it.
362    Distances {
363        /// The lowest label, which is cell zero.
364        low: i128,
365        /// What each cell is the distance to, as a name and how many bytes into it, with `None` in
366        /// a hole nothing reads.
367        to: Vec<Option<(Symbol, i128)>>,
368        /// The width of an index into the table, which is a word on the target.
369        index_bits: u32,
370    },
371}
372
373/// How a cell of a table is held, and how it is made an answer again.
374#[derive(Clone, Copy, Debug, PartialEq, Eq)]
375struct Cell {
376    /// The width of a cell.
377    ty: Type,
378    /// Whether a cell narrower than the answer is widened with its sign.
379    signed: bool,
380}
381
382/// What one `switch` becomes, or why it stays as it is.
383///
384/// `index_bits` is the width of an address when a table may be made and `None` when it may not,
385/// `small` is whether the goal is size, which is what narrows a cell, and `near` is the names a
386/// table may hold the distance to.
387fn plan(
388    func: &Func,
389    cfg: &Cfg,
390    inst: Inst,
391    index_bits: Option<u32>,
392    small: bool,
393    near: &HashSet<Symbol>,
394) -> Result<Plan, &'static str> {
395    let Extra::Switch(info) = func[inst].extra else { return Err(ARMS_DIFFER) };
396    let info = func[info];
397    let Some(&value) = func[func[inst].args].first() else { return Err(ARMS_DIFFER) };
398    let ty = func[value].ty;
399    if !ty.is_int() {
400        return Err(WIDTHS_DIFFER);
401    }
402    let calls: Vec<BlockCall> = func[info.targets].to_vec();
403    let labels: Vec<i128> = func[info.cases].iter().map(|imm| imm.signed(ty)).collect();
404    let Some((&default, arms)) = calls.split_first() else { return Err(ARMS_DIFFER) };
405    if arms.len() != labels.len() || arms.len() < LABELS {
406        return Err(TOO_FEW);
407    }
408    // A block that is both an arm and the default is not an arm this may take away, and it looks
409    // like one from here: the predecessor count below says one, because one block reaching another
410    // down two edges is one predecessor, and the arm being removed would take the default with it.
411    if arms.iter().any(|call| call.block == default.block) {
412        return Err(ARM_IS_SHARED);
413    }
414
415    // Consecutive and ascending. The front end sorts nothing, so this is asked of the list as it
416    // arrived rather than of a sorted copy: what is wanted is that the labels are a run, and a run
417    // read out of order is still a run only if it is sorted first, which is work this declines to
418    // do before it knows the answers are a line. Only a line asks, since a table indexes by the
419    // label whatever order the labels came in.
420    let consecutive = labels.windows(2).all(|pair| pair[1].checked_sub(pair[0]) == Some(1));
421    if !consecutive && index_bits.is_none() {
422        return Err(NOT_CONSECUTIVE);
423    }
424
425    // Every arm is a block of its own that works out constants and hands them on, and the way it
426    // hands them on is the way every other arm does.
427    let mut hands = None;
428    let mut shared: Option<Vec<Value>> = None;
429    let mut answer = None;
430    let mut handed = Vec::new();
431    for call in arms {
432        if !call.args.is_empty() {
433            return Err(ARM_DOES_WORK);
434        }
435        if cfg.predecessors(call.block).len() != 1 {
436            return Err(ARM_IS_SHARED);
437        }
438        // And a block an image holds the address of is shared whatever the graph says, because what
439        // arrives there is a `goto *p` that can be in another function.
440        if func.block_name(call.block).is_some() {
441            return Err(ARM_IS_SHARED);
442        }
443        let (way, args) = tail(func, call.block)?;
444        if *hands.get_or_insert(way) != way {
445            return Err(ARMS_DIFFER);
446        }
447        let previous = shared.get_or_insert_with(|| args.clone());
448        if previous.len() != args.len() {
449            return Err(ARMS_DIFFER);
450        }
451        // The one position they disagree about is the answer, and it is the same position every
452        // time. The first arm sets nothing, since it agrees with itself everywhere.
453        for (index, (&mine, &theirs)) in previous.iter().zip(&args).enumerate() {
454            if mine == theirs {
455                continue;
456            }
457            if *answer.get_or_insert(index) != index {
458                return Err(ARMS_DIFFER);
459            }
460        }
461        handed.push(args);
462    }
463    let (Some(hands), Some(args)) = (hands, shared) else { return Err(ARMS_DIFFER) };
464    let answer = answer.ok_or(NOT_AFFINE)?;
465    let kind = func[args[answer]].ty;
466    // Read once the position is known and not while it was being found. Until the second arm
467    // disagrees with the first nobody knows which position the answer is in, and reading the first
468    // arm's answer from a guess of the first position would take whatever was there, which is a
469    // constant every arm passed if the arms pass one, and a line fitted through that is wrong at
470    // the first label.
471    let (how, holes) = if kind.is_ptr() {
472        let index_bits = index_bits.ok_or(PLACE_IS_ODD)?;
473        let mut places = Vec::with_capacity(handed.len());
474        for args in &handed {
475            places.push(place(func, args[answer], near).ok_or(PLACE_IS_ODD)?);
476        }
477        let fill = fallback(func, default, hands, &args, answer)
478            .and_then(|given| place(func, given, near));
479        distances(&labels, &places, ty, index_bits, fill)?
480    } else {
481        let mut answers = Vec::with_capacity(handed.len());
482        for args in &handed {
483            let Some(number) = constant(func, args[answer]) else { return Err(NOT_AFFINE) };
484            answers.push(number);
485        }
486        let line = if consecutive && kind == ty { line(&labels, &answers, ty) } else { None };
487        match (line, index_bits) {
488            (Some((scale, offset)), _) => (How::Line { scale, offset }, Vec::new()),
489            (None, Some(index_bits)) => {
490                let fill = fallback(func, default, hands, &args, answer)
491                    .and_then(|given| constant(func, given));
492                let shape = Shape { ty, kind, index_bits, small };
493                table(&labels, &answers, shape, fill)?
494            }
495            (None, None) if kind != ty => return Err(WIDTHS_DIFFER),
496            (None, None) => return Err(NOT_AFFINE),
497        }
498    };
499    Ok(Plan {
500        inst,
501        value,
502        ty,
503        hands,
504        args,
505        answer,
506        how,
507        arms: arms.iter().map(|call| call.block).collect(),
508        holes,
509    })
510}
511
512/// What the default gives in the answer's place, when that is all it does differently from an arm.
513///
514/// Two shapes of default qualify. One is a block of its own that works out constants and hands
515/// them on the way the arms do, which is `default: return 0;`. The other is an edge straight to
516/// where the arms hand their answer, carrying the answer itself, which is what is left of
517/// `default: y = 0; break;` once the empty block is gone. Either way every position but the answer
518/// has to be what the arms pass, since a hole given a case is about to pass that instead. The
519/// default block is only read here and never taken away, so it may be shared.
520///
521/// What comes back is the value in the answer's place, and whether it is a number or an address a
522/// table can hold is for the caller to ask.
523fn fallback(
524    func: &Func,
525    default: BlockCall,
526    hands: Hands,
527    args: &[Value],
528    answer: usize,
529) -> Option<Value> {
530    let theirs = if default.args.is_empty() {
531        let (way, theirs) = tail(func, default.block).ok()?;
532        if way != hands {
533            return None;
534        }
535        theirs
536    } else if hands == Hands::On(default.block) {
537        func[default.args].to_vec()
538    } else {
539        return None;
540    };
541    if theirs.len() != args.len() {
542        return None;
543    }
544    let agrees =
545        args.iter().zip(&theirs).enumerate().all(|(at, (mine, it))| at == answer || mine == it);
546    if !agrees {
547        return None;
548    }
549    Some(theirs[answer])
550}
551
552/// What a table is made for: the label's width, the answer's, an index's, and whether the goal is
553/// size.
554#[derive(Clone, Copy, Debug)]
555struct Shape {
556    /// The width of the label.
557    ty: Type,
558    /// The width of the answer.
559    kind: Type,
560    /// The width of an index into the table.
561    index_bits: u32,
562    /// Whether a cell may be narrower than the answer.
563    small: bool,
564}
565
566/// The table the answers make when one is worth making, and the holes that get a case of their own.
567///
568/// A hole gets one only when `fill` is what the default gives, and then its cell is that. The
569/// labels are read with their own sign and so are ordered that way, which is only a question
570/// of which one is cell zero. What the index is at run time is the label less the lowest one at the
571/// label's width, and for a label that is a case that difference is the distance between the two
572/// however the bits are read, because the table is short and the distance fits.
573fn table(
574    labels: &[i128],
575    answers: &[i128],
576    shape: Shape,
577    fill: Option<i128>,
578) -> Result<(How, Vec<i128>), &'static str> {
579    let Shape { ty, kind, index_bits, small } = shape;
580    if ty.bits() > 64 {
581        return Err(LABEL_TOO_WIDE);
582    }
583    if !kind.is_int() || !matches!(kind.bits(), 8 | 16 | 32 | 64) {
584        return Err(CELL_IS_ODD);
585    }
586    let (low, cells) = spread(labels, answers)?;
587    let holes = if fill.is_some() { holes(low, &cells) } else { Vec::new() };
588    let cells: Vec<i128> = cells.into_iter().map(|cell| cell.or(fill).unwrap_or(0)).collect();
589    let cell = if small { narrowest(&cells, kind) } else { Cell { ty: kind, signed: false } };
590    Ok((How::Table { low, ty: kind, cell, cells, index_bits }, holes))
591}
592
593/// The table of distances the answers make when one is worth making, and the holes that get a case
594/// of their own.
595///
596/// The same as [`table`] but for what a cell is, which is four bytes whatever the label is. It is
597/// how far the answer is from the table, and nothing in one program is four gigabytes from
598/// anything else in it. A hole with nothing to fill it is `None`, a cell nothing reads.
599fn distances(
600    labels: &[i128],
601    places: &[(Symbol, i128)],
602    ty: Type,
603    index_bits: u32,
604    fill: Option<(Symbol, i128)>,
605) -> Result<(How, Vec<i128>), &'static str> {
606    if ty.bits() > 64 {
607        return Err(LABEL_TOO_WIDE);
608    }
609    let (low, to) = spread(labels, places)?;
610    let holes = if fill.is_some() { holes(low, &to) } else { Vec::new() };
611    let to = to.into_iter().map(|cell| cell.or(fill)).collect();
612    Ok((How::Distances { low, to, index_bits }, holes))
613}
614
615/// The answers laid out by label from the lowest one, with `None` in the holes, and the lowest
616/// label.
617///
618/// Refused when the labels are too far apart for the table to be worth its size, which is the
619/// growth limit in the module documentation.
620fn spread<T: Copy>(labels: &[i128], answers: &[T]) -> Result<(i128, Vec<Option<T>>), &'static str> {
621    let (Some(&low), Some(&high)) = (labels.iter().min(), labels.iter().max()) else {
622        return Err(TOO_FEW);
623    };
624    let span = high - low + 1;
625    if span > GROWTH * labels.len() as i128 {
626        return Err(TOO_SPARSE);
627    }
628    let mut cells = vec![None; usize::try_from(span).map_err(|_| TOO_SPARSE)?];
629    for (&label, &answer) in labels.iter().zip(answers) {
630        let at = usize::try_from(label - low).map_err(|_| TOO_SPARSE)?;
631        cells[at] = Some(answer);
632    }
633    Ok((low, cells))
634}
635
636/// The labels between the lowest and the highest that are not cases, from a spread of the answers.
637fn holes<T>(low: i128, cells: &[Option<T>]) -> Vec<i128> {
638    (low..).zip(cells).filter(|(_, cell)| cell.is_none()).map(|(label, _)| label).collect()
639}
640
641/// The narrowest cell every answer fits in, read back to the answer's width.
642///
643/// With a sign first, because an answer below zero only fits that way, and then without one, which
644/// is what fits a `200` in a byte. An answer that fits neither way at a width is an answer that
645/// needs the next one, and the answer's own width always fits.
646fn narrowest(answers: &[i128], kind: Type) -> Cell {
647    let whole = 1i128 << kind.bits();
648    for bits in [8u32, 16, 32] {
649        if bits >= kind.bits() {
650            break;
651        }
652        let half = 1i128 << (bits - 1);
653        if answers.iter().all(|&answer| (-half..half).contains(&answer)) {
654            return Cell { ty: Type::int(bits), signed: true };
655        }
656        if answers.iter().all(|&answer| answer.rem_euclid(whole) < half * 2) {
657            return Cell { ty: Type::int(bits), signed: false };
658        }
659    }
660    Cell { ty: kind, signed: false }
661}
662
663/// What a block hands on, when handing something on is the whole of what it does.
664///
665/// Every instruction in it but the last has to be a constant or an address, because the last one
666/// is about to be written somewhere else and anything the block worked out for it would be left
667/// behind. A constant is the exception because a constant is rewritten rather than moved, and an
668/// address is too, since what a table holds is how far away it is. Which addresses a table can
669/// hold is for [`place`] to say, and an arm that hands on one it cannot is refused there.
670fn tail(func: &Func, block: Block) -> Result<(Hands, Vec<Value>), &'static str> {
671    let Some(last) = func.terminator(block) else { return Err(ARM_DOES_WORK) };
672    for inst in func.insts(block) {
673        let opcode = func[inst].opcode;
674        if inst != last && !matches!(opcode, Opcode::IConst | Opcode::GlobalAddr | Opcode::PtrAdd) {
675            return Err(ARM_DOES_WORK);
676        }
677    }
678    let args: Vec<Value> = match func[last].opcode {
679        Opcode::Jump => {
680            let Some(call) = func.successors(last).next() else { return Err(ARM_DOES_WORK) };
681            let args = func[call.args].to_vec();
682            return Ok((Hands::On(call.block), args));
683        }
684        Opcode::Return => func[func[last].args].to_vec(),
685        _ => return Err(ARM_DOES_WORK),
686    };
687    Ok((Hands::Back, args))
688}
689
690/// The answer as `scale * label + offset`.
691fn arithmetic(builder: &mut Builder<'_>, plan: &Plan, scale: i128, offset: i128) -> Value {
692    let scaled = match scale {
693        0 => builder.iconst(plan.ty, offset),
694        1 => plan.value,
695        scale => {
696            let by = builder.iconst(plan.ty, scale);
697            builder.binary(Opcode::Mul, plan.value, by, Flags::NONE)
698        }
699    };
700    if offset == 0 || scale == 0 {
701        scaled
702    } else {
703        let by = builder.iconst(plan.ty, offset);
704        builder.binary(Opcode::Add, scaled, by, Flags::NONE)
705    }
706}
707
708/// The address of the table called `name`, and cell `label - low` of it.
709///
710/// The subtraction is at the label's width and wraps, and then the difference is made a word. Only
711/// a label that is a case gets here, so the difference is below the table's length and the
712/// widening is the same with or without a sign, and it is written without one.
713fn look_up(
714    builder: &mut Builder<'_>,
715    plan: &Plan,
716    name: Symbol,
717    low: i128,
718    ty: Type,
719    index_bits: u32,
720) -> (Value, Value) {
721    let from = if low == 0 {
722        plan.value
723    } else {
724        let by = builder.iconst(plan.ty, low);
725        builder.binary(Opcode::Sub, plan.value, by, Flags::NONE)
726    };
727    let word = Type::int(index_bits);
728    let index = match plan.ty.bits().cmp(&index_bits) {
729        Ordering::Less => builder.unary(Opcode::ZExt, from, word),
730        Ordering::Greater => builder.unary(Opcode::Trunc, from, word),
731        Ordering::Equal => from,
732    };
733    let bytes = ty.bits() / 8;
734    let distance = if bytes == 1 {
735        index
736    } else {
737        let by = builder.iconst(word, i128::from(bytes));
738        builder.binary(Opcode::Mul, index, by, Flags::NONE)
739    };
740    let base = builder.value(
741        InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) },
742        Type::PTR,
743    );
744    let cell = builder.binary(Opcode::PtrAdd, base, distance, Flags::NONE);
745    let info = MemInfo {
746        size: u64::from(bytes),
747        align: bytes,
748        order: MemOrder::NotAtomic,
749        tbaa: None,
750        owns: 0,
751        restrict: Restrict::NONE,
752    };
753    (base, builder.load(ty, cell, info, Flags::NONE))
754}
755
756/// The answer as the address of the table called `name` plus cell `label - low` of it.
757///
758/// The cell is four bytes with a sign, since an answer can be on either side of the table. The sum
759/// is worked out on integers and made a pointer at the end, so that where the answer came from is
760/// somewhere nobody can follow back, which is true, and not the table, which is not.
761fn far(builder: &mut Builder<'_>, plan: &Plan, name: Symbol, low: i128, index_bits: u32) -> Value {
762    let (base, read) = look_up(builder, plan, name, low, Type::int(32), index_bits);
763    let word = Type::int(index_bits);
764    let wider = index_bits > 32; // not a threshold: the cell is 32 bits
765    let away = if wider { builder.unary(Opcode::SExt, read, word) } else { read };
766    let start = builder.unary(Opcode::PtrToInt, base, word);
767    let at = builder.binary(Opcode::Add, start, away, Flags::NONE);
768    builder.unary(Opcode::IntToPtr, at, Type::PTR)
769}
770
771/// The name an answer is the address of and how many bytes into it, when it is one a table may
772/// hold the distance to.
773///
774/// The bytes are held to what four bytes can say, since they are added to the distance in the
775/// cell and a cell is four bytes.
776fn place(func: &Func, value: Value, near: &HashSet<Symbol>) -> Option<(Symbol, i128)> {
777    let Def::Result { inst, .. } = func[value].def else { return None };
778    let data = func[inst];
779    match (data.opcode, data.extra) {
780        (Opcode::GlobalAddr, Extra::Symbol(name)) if near.contains(&name) => Some((name, 0)),
781        (Opcode::PtrAdd, _) => {
782            let args = &func[data.args];
783            let (name, bytes) = place(func, args[0], near)?;
784            let bytes = bytes.checked_add(constant(func, args[1])?)?;
785            i32::try_from(bytes).is_ok().then_some((name, bytes))
786        }
787        _ => None,
788    }
789}
790
791/// The value of an integer constant, read with its own sign.
792fn constant(func: &Func, value: Value) -> Option<i128> {
793    crate::discharge::constant(func, value)
794}
795
796/// The multiple and the offset that give every answer from its label, when one pair does.
797///
798/// Fitted to the first two labels, which is exact because they are one apart, and then checked at
799/// every label including those two. Checked rather than trusted because the arithmetic that is
800/// about to be written wraps at the type's width, and a fit that is right about the numbers and
801/// wrong about the wrapping is a miscompile that only shows up at the ends of the range.
802fn line(labels: &[i128], answers: &[i128], ty: Type) -> Option<(i128, i128)> {
803    let [first, second, ..] = *labels else { return None };
804    let [low, high, ..] = *answers else { return None };
805    debug_assert_eq!(second - first, 1, "the labels were checked to be consecutive");
806    let scale = high.checked_sub(low)?;
807    let offset = low.checked_sub(scale.checked_mul(first)?)?;
808    for (&label, &answer) in labels.iter().zip(answers) {
809        let want = scale.checked_mul(label)?.checked_add(offset)?;
810        if wrap(want, ty) != answer {
811            return None;
812        }
813    }
814    Some((scale, offset))
815}
816
817/// A number as the machine will hold it at that width, read back with its own sign.
818///
819/// An immediate is stored in exactly the width its type has, so building one and reading it back is
820/// the truncation, and it is the same one every other part of the compiler uses.
821fn wrap(value: i128, ty: Type) -> i128 {
822    Imm::int(value, ty).signed(ty)
823}
824
825/// Writes the block the arms become and points every case edge at it.
826///
827/// `table` is the name of the table a plan for one was given, and `None` for a line.
828fn apply(func: &mut Func, plan: &Plan, table: Option<Symbol>) {
829    let span = func.span(plan.inst);
830    let hit = func.create_block();
831    let mut builder = Builder::new(func, hit).at(span);
832    let answer = match (&plan.how, table) {
833        (&How::Line { scale, offset }, _) => arithmetic(&mut builder, plan, scale, offset),
834        (&How::Table { low, ty, cell, index_bits, .. }, Some(name)) => {
835            let (_, read) = look_up(&mut builder, plan, name, low, cell.ty, index_bits);
836            match (cell.ty == ty, cell.signed) {
837                (true, _) => read,
838                (false, true) => builder.unary(Opcode::SExt, read, ty),
839                (false, false) => builder.unary(Opcode::ZExt, read, ty),
840            }
841        }
842        (&How::Distances { low, index_bits, .. }, Some(name)) => {
843            far(&mut builder, plan, name, low, index_bits)
844        }
845        (How::Table { .. } | How::Distances { .. }, None) => {
846            unreachable!("a table was planned with nowhere to put it")
847        }
848    };
849    let mut args = plan.args.clone();
850    args[plan.answer] = answer;
851    match plan.hands {
852        Hands::On(block) => builder.jump(block, &args),
853        Hands::Back => builder.ret(&args),
854    };
855
856    // Every case edge, and only the case edges: the default is the first target and stays where it
857    // was pointing.
858    let Extra::Switch(info) = func[plan.inst].extra else { return };
859    let empty = func.push_values(&[]);
860    let mut calls: Vec<BlockCall> = func[func[info].targets].to_vec();
861    for call in &mut calls[1..] {
862        // No hint: the cases that had one had one each, and a single edge standing for all of them
863        // cannot carry a number that was true of one arm.
864        *call = BlockCall::new(hit, empty);
865    }
866    let mut cases: Vec<Imm> = func[func[info].cases].to_vec();
867    for &hole in &plan.holes {
868        calls.push(BlockCall::new(hit, empty));
869        cases.push(Imm::int(hole, plan.ty));
870    }
871    let targets = func.push_block_calls(&calls);
872    let cases = func.push_imms(&cases);
873    let info = func.add_switch(rucc_ir::SwitchInfo { targets, cases });
874    func[plan.inst].extra = Extra::Switch(info);
875
876    // The arms are unreachable now. Two labels sharing one arm is a shape that survives the checks
877    // above only when the answer does not depend on the label, so the same block can be here twice.
878    let mut gone = HashSet::new();
879    for &arm in &plan.arms {
880        if gone.insert(arm) {
881            func.remove_block(arm);
882        }
883    }
884}
885
886#[cfg(test)]
887mod tests {
888    use std::collections::{HashMap, HashSet};
889
890    use std::sync::Arc;
891
892    use rucc_base::{Interner, Symbol};
893    use rucc_cost::Goal;
894    use rucc_ir::{
895        Block, Builder, Datum, Extra, Flags, Func, Global, InstData, Linkage, Module, Opcode, Pic,
896        Signature, Type, Value,
897    };
898    use rucc_target::{TargetInfo, Triple};
899
900    use super::{PLACE_IS_ODD, SwitchConv};
901    use crate::image::Images;
902    use crate::stats::Kind;
903    use crate::{Fuel, Pass, ReadOnly, Stats, Table};
904
905    /// The width everything here switches on and answers in, unless a test says otherwise.
906    fn i32() -> Type {
907        Type::int(32)
908    }
909
910    /// Runs the pass with as much fuel as it wants.
911    fn convert(func: &mut Func) -> Stats {
912        SwitchConv.run(func, &mut crate::machine::fixtures::analyses(), &mut Fuel::unlimited())
913    }
914
915    /// Runs the pass the way the pipeline does, with a place for tables, and hands back the
916    /// tables it asked for.
917    fn tabled(func: &mut Func) -> (Stats, Vec<Table>) {
918        tabled_for(func, Goal::Speed)
919    }
920
921    /// The same for a goal, which is what decides how wide a cell is.
922    fn tabled_for(func: &mut Func, goal: Goal) -> (Stats, Vec<Table>) {
923        let mut names = Interner::new();
924        let taken = HashSet::new();
925        let mut data = ReadOnly::new(&mut names, &taken, 64, 0);
926        let mut an = crate::Analyses::new(crate::Machine::with(None, goal));
927        let stats = SwitchConv.run_emitting(func, &mut an, &mut Fuel::unlimited(), &mut data);
928        (stats, data.into_tables())
929    }
930
931    /// A function that switches on its parameter and returns a constant per label.
932    ///
933    /// The default returns a constant of its own that is not on any line these tests fit, so a
934    /// test that says the pass fired is saying it fired on the cases and not on the whole thing.
935    fn returning(ty: Type, labels: &[i128], answers: &[i128]) -> Func {
936        let mut names = Interner::new();
937        let mut func = Func::new(names.intern("f"), Signature::new());
938        let head = func.create_block();
939        let value = func.append_param(head, ty);
940        let default = func.create_block();
941        let arms: Vec<Block> = answers.iter().map(|_| func.create_block()).collect();
942        for (&arm, &answer) in arms.iter().zip(answers) {
943            let mut build = Builder::new(&mut func, arm);
944            let it = build.iconst(ty, answer);
945            build.ret(&[it]);
946        }
947        let mut build = Builder::new(&mut func, default);
948        let it = build.iconst(ty, 999);
949        build.ret(&[it]);
950        let cases: Vec<(i128, Block)> = labels.iter().copied().zip(arms.iter().copied()).collect();
951        Builder::new(&mut func, head).switch(value, default, &cases);
952        func
953    }
954
955    /// The blocks every case edge goes to, which is one block when the pass has fired.
956    fn cases(func: &Func) -> Vec<usize> {
957        let head = func.entry().expect("a function with blocks in it");
958        let term = func.terminator(head).expect("a head block has one");
959        func.successors(term).skip(1).map(|call| call.block.index()).collect()
960    }
961
962    /// The block every case edge goes to, when there is exactly one of them.
963    fn arm(func: &Func) -> Block {
964        let blocks = cases(func);
965        let first = blocks[0];
966        assert!(blocks.iter().all(|&block| block == first), "the case edges did not all move");
967        Block::from_usize(first)
968    }
969
970    /// The opcodes a block holds, in order.
971    fn opcodes(func: &Func, block: Block) -> Vec<Opcode> {
972        func.insts(block).map(|inst| func[inst].opcode).collect()
973    }
974
975    /// What the block the case edges go to answers, given that label.
976    ///
977    /// An interpreter of exactly the three instructions this pass writes, because what the pass
978    /// has to get right is the number and not the shape. Anything else in the block is a test
979    /// that has drifted away from what it is testing, so it stops rather than guesses.
980    fn answer(func: &Func, block: Block, label: i128) -> i128 {
981        looked_up(func, block, label, &[])
982    }
983
984    /// What the block the case edges go to answers, given that label and the tables it may read.
985    ///
986    /// The address of a table is its cell zero counted in bytes, which is all a load from one
987    /// needs, and a load stops the test if it is not on a cell or not inside the table.
988    fn looked_up(func: &Func, block: Block, label: i128, tables: &[Table]) -> i128 {
989        let head = func.entry().expect("a function with blocks in it");
990        let mut values: HashMap<Value, i128> = HashMap::new();
991        values.insert(func[head].params[0], label);
992        for inst in func.insts(block) {
993            let data = func[inst];
994            let Some(result) = data.first_result else {
995                let args = func[data.args].to_vec();
996                let handed = match data.opcode {
997                    Opcode::Return => args[0],
998                    Opcode::Jump => {
999                        func[func.successors(inst).next().expect("a jump goes").args][0]
1000                    }
1001                    other => panic!("a block this pass wrote ends in {other:?}"),
1002                };
1003                return values[&handed];
1004            };
1005            let args: Vec<i128> = func[data.args].iter().map(|arg| values[arg]).collect();
1006            let it = match data.opcode {
1007                Opcode::IConst => {
1008                    let (imm, ty) = crate::fold::constant(func, result).expect("a constant is one");
1009                    imm.signed(ty)
1010                }
1011                Opcode::Mul => args[0].wrapping_mul(args[1]),
1012                Opcode::Add => args[0].wrapping_add(args[1]),
1013                Opcode::Sub => args[0].wrapping_sub(args[1]),
1014                // Unsigned, which is what the widening is, and then the width it widens to.
1015                Opcode::ZExt => {
1016                    let from = func[func[data.args][0]].ty;
1017                    super::wrap(args[0], from).rem_euclid(1 << from.bits())
1018                }
1019                Opcode::SExt | Opcode::PtrToInt | Opcode::IntToPtr => args[0],
1020                Opcode::GlobalAddr => 0,
1021                Opcode::PtrAdd => args[0] + args[1],
1022                // A cell that is how far a name is from the table is where the name is, since the
1023                // table is at zero.
1024                Opcode::Load => {
1025                    assert_eq!(tables.len(), 1, "a load with no single table to read");
1026                    let table = &tables[0];
1027                    let bytes = i128::from(table.ty.bits() / 8);
1028                    assert_eq!(args[0] % bytes, 0, "a load between two cells");
1029                    let at = usize::try_from(args[0] / bytes).expect("a load before the table");
1030                    let cell = *table.cells.get(at).expect("a load after the table");
1031                    cell + table.to.get(at).copied().flatten().map_or(0, spot)
1032                }
1033                other => panic!("this pass does not write {other:?}"),
1034            };
1035            // An address is a number of bytes into a table and has no width to wrap at.
1036            let ty = func[result].ty;
1037            values.insert(result, if ty.is_int() { super::wrap(it, ty) } else { it });
1038        }
1039        panic!("a block with no terminator");
1040    }
1041
1042    /// Where the tests say a name is, which is somewhere a distance from a table at zero shows.
1043    fn spot(name: Symbol) -> i128 {
1044        1000 * (i128::from(name.raw()) + 1)
1045    }
1046
1047    /// Whether the pass says it changed the function.
1048    fn fired(stats: &Stats) -> bool {
1049        stats.total(Kind::Optimized) > 0
1050    }
1051
1052    #[test]
1053    fn labels_that_run_with_their_answers_become_one_addition() {
1054        let mut func = returning(i32(), &[0, 1, 2, 3], &[1, 2, 3, 4]);
1055        assert!(fired(&convert(&mut func)));
1056        let arm = arm(&func);
1057        assert_eq!(opcodes(&func, arm), [Opcode::IConst, Opcode::Add, Opcode::Return]);
1058        for label in 0..4 {
1059            assert_eq!(answer(&func, arm, label), label + 1);
1060        }
1061    }
1062
1063    #[test]
1064    fn answers_that_are_a_multiple_of_the_label_become_a_multiplication() {
1065        let mut func = returning(i32(), &[3, 4, 5, 6], &[30, 40, 50, 60]);
1066        assert!(fired(&convert(&mut func)));
1067        let arm = arm(&func);
1068        assert_eq!(opcodes(&func, arm), [Opcode::IConst, Opcode::Mul, Opcode::Return]);
1069        for label in 3..7 {
1070            assert_eq!(answer(&func, arm, label), label * 10);
1071        }
1072    }
1073
1074    #[test]
1075    fn answers_that_are_all_the_same_become_the_constant_they_all_were() {
1076        let mut func = returning(i32(), &[7, 8, 9, 10], &[9, 9, 9, 9]);
1077        assert!(fired(&convert(&mut func)));
1078        let arm = arm(&func);
1079        assert_eq!(opcodes(&func, arm), [Opcode::IConst, Opcode::Return]);
1080        assert_eq!(answer(&func, arm, 8), 9);
1081    }
1082
1083    #[test]
1084    fn labels_that_run_below_zero_are_a_run_like_any_other() {
1085        let mut func = returning(i32(), &[-2, -1, 0, 1], &[-4, -2, 0, 2]);
1086        assert!(fired(&convert(&mut func)));
1087        let arm = arm(&func);
1088        for label in -2..2 {
1089            assert_eq!(answer(&func, arm, label), label * 2);
1090        }
1091    }
1092
1093    /// The line has to hold at the type's width and not at the arithmetic's.
1094    ///
1095    /// A hundred times two is two hundred, which is not a number an `i8` holds, and the answer the
1096    /// program gave at that label is what two hundred comes to there. The pass writes a
1097    /// multiplication with no flags on it, which wraps the same way, so this is a fit and not a
1098    /// refusal, and the number is the point of the test.
1099    #[test]
1100    fn a_line_that_only_holds_by_wrapping_still_holds() {
1101        let ty = Type::int(8);
1102        let mut func = returning(ty, &[0, 1, 2], &[0, 100, -56]);
1103        assert!(fired(&convert(&mut func)));
1104        let arm = arm(&func);
1105        assert_eq!(answer(&func, arm, 2), -56);
1106    }
1107
1108    #[test]
1109    fn labels_with_a_hole_in_them_are_left_alone_where_no_table_can_be_made() {
1110        let mut func = returning(i32(), &[0, 1, 3], &[1, 2, 4]);
1111        assert!(!fired(&convert(&mut func)));
1112        assert_eq!(cases(&func).len(), 3);
1113    }
1114
1115    #[test]
1116    fn answers_that_are_not_a_line_are_left_alone_where_no_table_can_be_made() {
1117        let mut func = returning(i32(), &[0, 1, 2], &[5, 9, 2]);
1118        assert!(!fired(&convert(&mut func)));
1119    }
1120
1121    #[test]
1122    fn two_labels_are_not_enough_to_pay_for_the_arithmetic() {
1123        let mut func = returning(i32(), &[0, 1], &[1, 2]);
1124        assert!(!fired(&convert(&mut func)));
1125    }
1126
1127    #[test]
1128    fn an_answer_wider_than_its_label_is_left_alone_where_no_table_can_be_made() {
1129        let mut names = Interner::new();
1130        let mut func = Func::new(names.intern("f"), Signature::new());
1131        let head = func.create_block();
1132        let value = func.append_param(head, i32());
1133        let default = func.create_block();
1134        let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
1135        for (index, &arm) in arms.iter().enumerate() {
1136            let mut build = Builder::new(&mut func, arm);
1137            let it = build.iconst(Type::int(64), index as i128 + 1);
1138            build.ret(&[it]);
1139        }
1140        let mut build = Builder::new(&mut func, default);
1141        let it = build.iconst(Type::int(64), 0);
1142        build.ret(&[it]);
1143        let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
1144        Builder::new(&mut func, head).switch(value, default, &cases);
1145        assert!(!fired(&convert(&mut func)));
1146    }
1147
1148    #[test]
1149    fn an_arm_something_else_reaches_is_left_alone() {
1150        let mut func = returning(i32(), &[0, 1, 2], &[1, 2, 3]);
1151        // The default jumps into the first arm instead of returning, so the arm is a block two
1152        // edges arrive at and is not one this may take away.
1153        let default = Block::from_usize(1);
1154        let arm = Block::from_usize(2);
1155        let term = func.terminator(default).expect("the default returns");
1156        func.remove_inst(term);
1157        Builder::new(&mut func, default).jump(arm, &[]);
1158        assert!(!fired(&convert(&mut func)));
1159    }
1160
1161    #[test]
1162    fn an_arm_that_is_also_the_default_is_left_alone() {
1163        let mut names = Interner::new();
1164        let mut func = Func::new(names.intern("f"), Signature::new());
1165        let head = func.create_block();
1166        let value = func.append_param(head, i32());
1167        let shared = func.create_block();
1168        let mut build = Builder::new(&mut func, shared);
1169        let it = build.iconst(i32(), 1);
1170        build.ret(&[it]);
1171        let others: Vec<Block> = (0..2).map(|_| func.create_block()).collect();
1172        for (index, &arm) in others.iter().enumerate() {
1173            let mut build = Builder::new(&mut func, arm);
1174            let it = build.iconst(i32(), index as i128 + 2);
1175            build.ret(&[it]);
1176        }
1177        let cases = [(0, shared), (1, others[0]), (2, others[1])];
1178        Builder::new(&mut func, head).switch(value, shared, &cases);
1179        assert!(!fired(&convert(&mut func)));
1180    }
1181
1182    #[test]
1183    fn arms_that_join_keep_what_they_pass_beside_the_answer() {
1184        let mut names = Interner::new();
1185        let mut func = Func::new(names.intern("f"), Signature::new());
1186        let head = func.create_block();
1187        let value = func.append_param(head, i32());
1188        let alongside = func.append_param(head, i32());
1189        let join = func.create_block();
1190        let handed = func.append_param(join, i32());
1191        let carried = func.append_param(join, i32());
1192        Builder::new(&mut func, join).ret(&[handed, carried]);
1193        let default = func.create_block();
1194        let mut build = Builder::new(&mut func, default);
1195        let it = build.iconst(i32(), 999);
1196        build.jump(join, &[it, alongside]);
1197        let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
1198        for (index, &arm) in arms.iter().enumerate() {
1199            let mut build = Builder::new(&mut func, arm);
1200            let it = build.iconst(i32(), index as i128 + 1);
1201            build.jump(join, &[it, alongside]);
1202        }
1203        let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
1204        Builder::new(&mut func, head).switch(value, default, &cases);
1205        assert!(fired(&convert(&mut func)));
1206
1207        let arm = arm(&func);
1208        assert_eq!(answer(&func, arm, 2), 3);
1209        // The second argument is what it always was, which is the parameter every arm passed.
1210        let term = func.terminator(arm).expect("the block ends in a jump");
1211        let call = func.successors(term).next().expect("a jump goes somewhere");
1212        assert_eq!(func[call.args][1], alongside);
1213    }
1214
1215    #[test]
1216    fn arms_that_hand_on_two_different_things_are_left_alone() {
1217        let mut names = Interner::new();
1218        let mut func = Func::new(names.intern("f"), Signature::new());
1219        let head = func.create_block();
1220        let value = func.append_param(head, i32());
1221        let join = func.create_block();
1222        let first = func.append_param(join, i32());
1223        let second = func.append_param(join, i32());
1224        Builder::new(&mut func, join).ret(&[first, second]);
1225        let default = func.create_block();
1226        let mut build = Builder::new(&mut func, default);
1227        let it = build.iconst(i32(), 999);
1228        build.jump(join, &[it, it]);
1229        let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
1230        for (index, &arm) in arms.iter().enumerate() {
1231            let mut build = Builder::new(&mut func, arm);
1232            let one = build.iconst(i32(), index as i128 + 1);
1233            let two = build.iconst(i32(), index as i128 + 10);
1234            build.jump(join, &[one, two]);
1235        }
1236        let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
1237        Builder::new(&mut func, head).switch(value, default, &cases);
1238        assert!(!fired(&convert(&mut func)));
1239    }
1240
1241    #[test]
1242    fn an_arm_that_does_something_is_left_alone() {
1243        let mut names = Interner::new();
1244        let mut func = Func::new(names.intern("f"), Signature::new());
1245        let head = func.create_block();
1246        let value = func.append_param(head, i32());
1247        let default = func.create_block();
1248        let mut build = Builder::new(&mut func, default);
1249        let it = build.iconst(i32(), 999);
1250        build.ret(&[it]);
1251        let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
1252        for (index, &arm) in arms.iter().enumerate() {
1253            let mut build = Builder::new(&mut func, arm);
1254            let it = build.iconst(i32(), index as i128 + 1);
1255            // An addition the arm did, which is work the answer would have been left without.
1256            let sum = build.binary(Opcode::Add, it, value, Flags::NONE);
1257            build.ret(&[sum]);
1258        }
1259        let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
1260        Builder::new(&mut func, head).switch(value, default, &cases);
1261        assert!(!fired(&convert(&mut func)));
1262    }
1263
1264    #[test]
1265    fn the_default_goes_where_it_went() {
1266        let mut func = returning(i32(), &[0, 1, 2, 3], &[1, 2, 3, 4]);
1267        let head = func.entry().expect("a function with blocks in it");
1268        let before = func.terminator(head).expect("a head block has one");
1269        let was = func.successors(before).next().expect("a switch has a default").block;
1270        assert!(fired(&convert(&mut func)));
1271        let after = func.terminator(head).expect("a head block has one");
1272        let now = func.successors(after).next().expect("a switch has a default").block;
1273        assert_eq!(was, now, "the default moved");
1274    }
1275
1276    /// The opcodes of a block that answers from a table, from the label at zero.
1277    const LOOKUP: [Opcode; 6] = [
1278        Opcode::ZExt,
1279        Opcode::IConst,
1280        Opcode::Mul,
1281        Opcode::GlobalAddr,
1282        Opcode::PtrAdd,
1283        Opcode::Load,
1284    ];
1285
1286    #[test]
1287    fn answers_that_are_not_a_line_are_one_load_from_a_table() {
1288        let mut func = returning(i32(), &[0, 1, 2, 3], &[5, 9, 2, 7]);
1289        let (stats, tables) = tabled(&mut func);
1290        assert!(fired(&stats));
1291        assert_eq!(tables.len(), 1);
1292        assert_eq!(tables[0].ty, i32());
1293        assert_eq!(tables[0].cells, [5, 9, 2, 7]);
1294        let arm = arm(&func);
1295        let mut want = LOOKUP.to_vec();
1296        want.push(Opcode::Return);
1297        assert_eq!(opcodes(&func, arm), want);
1298        for (label, answer) in [(0, 5), (1, 9), (2, 2), (3, 7)] {
1299            assert_eq!(looked_up(&func, arm, label, &tables), answer);
1300        }
1301    }
1302
1303    /// A hole gets the default's answer and a case of its own, when the default only gives one.
1304    ///
1305    /// The default here returns 999 and nothing else, so the value in the hole reads 999 out of
1306    /// the table and gets what it got before, and the labels are one run with no hole in it.
1307    #[test]
1308    fn a_hole_is_filled_with_what_a_default_that_only_answers_gives() {
1309        let mut func = returning(i32(), &[1, 2, 4, 5], &[10, 20, 40, 55]);
1310        let head = func.entry().expect("a function with blocks in it");
1311        let before = func.terminator(head).expect("a head block has one");
1312        let default = func.successors(before).next().expect("a switch has a default").block;
1313        let (stats, tables) = tabled(&mut func);
1314        assert!(fired(&stats));
1315        assert_eq!(tables[0].cells, [10, 20, 999, 40, 55]);
1316        assert_eq!(cases(&func).len(), 5, "the hole was not given a case");
1317        let after = func.terminator(head).expect("a head block has one");
1318        assert_eq!(func.successors(after).next().map(|call| call.block), Some(default));
1319        let arm = arm(&func);
1320        for (label, answer) in [(1, 10), (2, 20), (3, 999), (4, 40), (5, 55)] {
1321            assert_eq!(looked_up(&func, arm, label, &tables), answer);
1322        }
1323    }
1324
1325    /// A hole is a cell nothing reads when the default does something other than answer.
1326    ///
1327    /// This default returns the label, which is no constant, so the value in the hole has to
1328    /// keep going to it down the edge it always went down.
1329    #[test]
1330    fn a_hole_still_goes_to_a_default_that_does_more_than_answer() {
1331        let mut func = returning(i32(), &[1, 2, 4, 5], &[10, 20, 40, 55]);
1332        let head = func.entry().expect("a function with blocks in it");
1333        let before = func.terminator(head).expect("a head block has one");
1334        let default = func.successors(before).next().expect("a switch has a default").block;
1335        let label = func[func[before].args][0];
1336        let ret = func.terminator(default).expect("the default returns");
1337        func.remove_inst(ret);
1338        Builder::new(&mut func, default).ret(&[label]);
1339        let (stats, tables) = tabled(&mut func);
1340        assert!(fired(&stats));
1341        assert_eq!(tables[0].cells, [10, 20, 0, 40, 55]);
1342        assert_eq!(cases(&func).len(), 4, "a hole was given a case");
1343        let after = func.terminator(head).expect("a head block has one");
1344        assert_eq!(func.successors(after).next().map(|call| call.block), Some(default));
1345        let arm = arm(&func);
1346        for (label, answer) in [(1, 10), (2, 20), (4, 40), (5, 55)] {
1347            assert_eq!(looked_up(&func, arm, label, &tables), answer);
1348        }
1349    }
1350
1351    /// Cell zero is the lowest label, which here is below zero and reached by wrapping.
1352    ///
1353    /// Every label of a `signed char` is tried, which is the whole of what can reach the block
1354    /// and the whole of what can go wrong with the subtraction and the widening after it.
1355    #[test]
1356    fn labels_below_zero_index_from_the_lowest_of_them() {
1357        let ty = Type::int(8);
1358        let labels = [-128, -3, -1, 0, 2, 127];
1359        let answers = [7, -5, 11, 3, -100, 42];
1360        let mut func = returning(ty, &labels, &answers);
1361        // Far apart at the ends, so a table is only allowed because the ratio is eight to one and
1362        // there are six labels: two hundred and fifty six cells is more than forty eight.
1363        let (stats, _) = tabled(&mut func);
1364        assert!(!fired(&stats), "a table of mostly holes was made");
1365
1366        let labels = [-3, -2, -1, 0, 2];
1367        let answers = [7, -5, 11, 3, -100];
1368        let mut func = returning(ty, &labels, &answers);
1369        let (stats, tables) = tabled(&mut func);
1370        assert!(fired(&stats));
1371        // The default returns 999, which is -25 as a `signed char`, and the hole at 1 is given it.
1372        assert_eq!(tables[0].cells, [7, -5, 11, 3, -25, -100]);
1373        let arm = arm(&func);
1374        assert_eq!(opcodes(&func, arm)[..2], [Opcode::IConst, Opcode::Sub]);
1375        for (&label, &answer) in labels.iter().zip(&answers).chain([(&1, &-25)]) {
1376            assert_eq!(looked_up(&func, arm, label, &tables), answer);
1377        }
1378    }
1379
1380    /// An `int` label and a `long` answer, which a line declines and a table does not mind.
1381    #[test]
1382    fn an_answer_wider_than_its_label_is_a_table_of_the_wider_type() {
1383        let mut names = Interner::new();
1384        let answers = [1i128 << 40, 3, -1, 1 << 33];
1385        let mut func = Func::new(names.intern("f"), Signature::new());
1386        let head = func.create_block();
1387        let value = func.append_param(head, i32());
1388        let default = func.create_block();
1389        let arms: Vec<Block> = answers.iter().map(|_| func.create_block()).collect();
1390        for (&arm, &answer) in arms.iter().zip(&answers) {
1391            let mut build = Builder::new(&mut func, arm);
1392            let it = build.iconst(Type::int(64), answer);
1393            build.ret(&[it]);
1394        }
1395        let mut build = Builder::new(&mut func, default);
1396        let it = build.iconst(Type::int(64), 0);
1397        build.ret(&[it]);
1398        let cases: Vec<(i128, Block)> = (10..14).zip(arms.iter().copied()).collect();
1399        Builder::new(&mut func, head).switch(value, default, &cases);
1400        let (stats, tables) = tabled(&mut func);
1401        assert!(fired(&stats));
1402        assert_eq!(tables[0].ty, Type::int(64));
1403        let arm = arm(&func);
1404        for (label, &answer) in (10..14).zip(&answers) {
1405            assert_eq!(looked_up(&func, arm, label, &tables), answer);
1406        }
1407    }
1408
1409    #[test]
1410    fn labels_too_far_apart_for_a_table_are_left_alone() {
1411        let mut func = returning(i32(), &[0, 100, 200], &[1, 5, 3]);
1412        let (stats, tables) = tabled(&mut func);
1413        assert!(!fired(&stats));
1414        assert!(tables.is_empty());
1415    }
1416
1417    #[test]
1418    fn a_line_is_still_arithmetic_where_a_table_could_be_made() {
1419        let mut func = returning(i32(), &[0, 1, 2, 3], &[1, 2, 3, 4]);
1420        let (stats, tables) = tabled(&mut func);
1421        assert!(fired(&stats));
1422        assert!(tables.is_empty(), "a table was made for a line");
1423    }
1424
1425    #[test]
1426    fn a_label_wider_than_a_word_gets_no_table() {
1427        let mut func = returning(Type::int(128), &[0, 1, 2, 3], &[5, 9, 2, 7]);
1428        let (stats, tables) = tabled(&mut func);
1429        assert!(!fired(&stats));
1430        assert!(tables.is_empty());
1431    }
1432
1433    /// The answer is in the second place and the first is a constant every arm passes.
1434    ///
1435    /// The first arm's answer used to be read from the first place, because which place the
1436    /// answer is in is not known until a second arm disagrees. Here that reads one at the first
1437    /// label, and one, two and three are a line, so the pass returned one where the program said
1438    /// ten. What it must do is see ten, two and three, which is not a line.
1439    #[test]
1440    fn the_answer_is_read_from_the_place_the_arms_disagree_about() {
1441        let mut names = Interner::new();
1442        let mut func = Func::new(names.intern("f"), Signature::new());
1443        let head = func.create_block();
1444        let value = func.append_param(head, i32());
1445        let join = func.create_block();
1446        let first = func.append_param(join, i32());
1447        let second = func.append_param(join, i32());
1448        Builder::new(&mut func, join).ret(&[second, first]);
1449        let default = func.create_block();
1450        let arms: Vec<Block> = (0..3).map(|_| func.create_block()).collect();
1451        let mut build = Builder::new(&mut func, head);
1452        let one = build.iconst(i32(), 1);
1453        let cases: Vec<(i128, Block)> = (0..3).zip(arms.iter().copied()).collect();
1454        build.switch(value, default, &cases);
1455        let mut build = Builder::new(&mut func, default);
1456        let it = build.iconst(i32(), 999);
1457        build.jump(join, &[one, it]);
1458        for (&arm, answer) in arms.iter().zip([10, 2, 3]) {
1459            let mut build = Builder::new(&mut func, arm);
1460            let it = build.iconst(i32(), answer);
1461            build.jump(join, &[one, it]);
1462        }
1463        assert!(!fired(&convert(&mut func)), "ten, two and three were taken for a line");
1464        let (stats, tables) = tabled(&mut func);
1465        assert!(fired(&stats));
1466        assert_eq!(tables[0].cells, [10, 2, 3]);
1467    }
1468
1469    /// At the size goal a cell is a byte when every answer is one, and the load is widened back.
1470    ///
1471    /// Below zero is widened with a sign and two hundred without one, so both are tried, and the
1472    /// speed goal keeps the answer's own width, which is what gcc 16 does at the two levels.
1473    #[test]
1474    fn a_table_for_size_has_cells_as_narrow_as_its_answers() {
1475        let mut func = returning(i32(), &[0, 1, 2, 3], &[5, -9, 2, 7]);
1476        let (stats, tables) = tabled_for(&mut func, Goal::Size);
1477        assert!(fired(&stats));
1478        assert_eq!(tables[0].ty, Type::int(8));
1479        let at = arm(&func);
1480        assert!(opcodes(&func, at).contains(&Opcode::SExt));
1481        for (label, answer) in [(0, 5), (1, -9), (2, 2), (3, 7)] {
1482            assert_eq!(looked_up(&func, at, label, &tables), answer);
1483        }
1484
1485        let mut func = returning(i32(), &[0, 1, 2, 3], &[5, 200, 2, 255]);
1486        let (_, tables) = tabled_for(&mut func, Goal::Size);
1487        assert_eq!(tables[0].ty, Type::int(8));
1488        let at = arm(&func);
1489        assert!(opcodes(&func, at).contains(&Opcode::ZExt));
1490        for (label, answer) in [(0, 5), (1, 200), (2, 2), (3, 255)] {
1491            assert_eq!(looked_up(&func, at, label, &tables), answer);
1492        }
1493
1494        let mut func = returning(i32(), &[0, 1, 2, 3], &[5, -300, 2, 40000]);
1495        let (_, tables) = tabled_for(&mut func, Goal::Size);
1496        assert_eq!(tables[0].ty, i32(), "a cell narrower than an answer that needs all of it");
1497
1498        let mut func = returning(i32(), &[0, 1, 2, 3], &[5, -9, 2, 7]);
1499        let (_, tables) = tabled_for(&mut func, Goal::Speed);
1500        assert_eq!(tables[0].ty, i32());
1501    }
1502
1503    /// A function whose arms return addresses, the module that defines what they are the
1504    /// addresses of, and the names of those, the default's last.
1505    struct Pointing {
1506        names: Interner,
1507        module: Module,
1508        func: Func,
1509        places: Vec<Symbol>,
1510    }
1511
1512    /// A function that switches on its parameter and returns the address of a name per label,
1513    /// with the module that defines the names.
1514    ///
1515    /// The names are `s0` and on, one per label, and the default returns the address of one more.
1516    /// Each is read only and `static` unless `written` names it, and a name that is written to
1517    /// is not somewhere a table may hold the distance to. The arm for the `k`th label returns an
1518    /// address `k * into` bytes into its name, which is `&s[k]` for an array of `into` bytes.
1519    fn pointing(labels: &[i128], written: Option<usize>, into: i128) -> Pointing {
1520        let mut names = Interner::new();
1521        let target = TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().unwrap());
1522        let mut module = Module::new(names.intern("t.c"), &target);
1523        let places: Vec<Symbol> =
1524            (0..=labels.len()).map(|k| names.intern(&format!("s{k}"))).collect();
1525        for (k, &name) in places.iter().enumerate() {
1526            let mut global = Global::new(name, 4, 1);
1527            global.linkage = Linkage::Internal;
1528            global.constant = written != Some(k);
1529            global.init = Some(module.push_data(&[Datum::Zero(4)]));
1530            module.add_global(global);
1531        }
1532        let mut func = Func::new(names.intern("f"), Signature::new());
1533        let head = func.create_block();
1534        let value = func.append_param(head, i32());
1535        let blocks: Vec<Block> = places.iter().map(|_| func.create_block()).collect();
1536        for ((k, &block), &name) in blocks.iter().enumerate().zip(&places) {
1537            let mut build = Builder::new(&mut func, block);
1538            let data = InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) };
1539            let mut it = build.value(data, Type::PTR);
1540            if into != 0 && k < labels.len() {
1541                let bytes = build.iconst(Type::int(64), into * k as i128);
1542                it = build.binary(Opcode::PtrAdd, it, bytes, Flags::NONE);
1543            }
1544            build.ret(&[it]);
1545        }
1546        let (&default, arms) = blocks.split_last().expect("a default");
1547        let cases: Vec<(i128, Block)> = labels.iter().copied().zip(arms.iter().copied()).collect();
1548        Builder::new(&mut func, head).switch(value, default, &cases);
1549        Pointing { names, module, func, places }
1550    }
1551
1552    /// Runs the pass the way the pipeline does on x86-64 ELF when `measures` is true, and on a
1553    /// target with no four byte distance when it is not.
1554    fn placed(pointing: &mut Pointing, measures: bool) -> (Stats, Vec<Table>) {
1555        let taken = HashSet::new();
1556        let mut data = ReadOnly::new(&mut pointing.names, &taken, 64, 0).measuring(measures);
1557        let images = Arc::new(Images::of(&pointing.module, Pic::Executable));
1558        let mut an = crate::Analyses::new(crate::Machine::with(None, Goal::Speed)).reading(images);
1559        let stats =
1560            SwitchConv.run_emitting(&mut pointing.func, &mut an, &mut Fuel::unlimited(), &mut data);
1561        (stats, data.into_tables())
1562    }
1563
1564    #[test]
1565    fn answers_that_are_addresses_are_a_table_of_how_far_they_are_from_it() {
1566        let mut pointing = pointing(&[0, 1, 2, 3], None, 0);
1567        let (stats, tables) = placed(&mut pointing, true);
1568        assert!(fired(&stats));
1569        assert_eq!(tables.len(), 1);
1570        assert_eq!(tables[0].ty, i32());
1571        assert_eq!(tables[0].cells, [0, 0, 0, 0]);
1572        let want: Vec<Option<Symbol>> = pointing.places[..4].iter().copied().map(Some).collect();
1573        assert_eq!(tables[0].to, want);
1574        assert_eq!(tables[0].cells, [0, 0, 0, 0]);
1575        let func = &pointing.func;
1576        let arm = arm(func);
1577        let mut want = LOOKUP.to_vec();
1578        want.extend([
1579            Opcode::SExt,
1580            Opcode::PtrToInt,
1581            Opcode::Add,
1582            Opcode::IntToPtr,
1583            Opcode::Return,
1584        ]);
1585        assert_eq!(opcodes(func, arm), want);
1586        for label in 0..4 {
1587            let place = pointing.places[label as usize];
1588            assert_eq!(looked_up(func, arm, label, &tables), spot(place));
1589        }
1590    }
1591
1592    /// `&s[k]` for each label `k`, where the cell is the distance to the name with the bytes into
1593    /// it added.
1594    #[test]
1595    fn an_address_part_way_into_a_name_is_the_distance_to_it_and_the_bytes_in() {
1596        let mut pointing = pointing(&[0, 1, 2, 3], None, 4);
1597        let (stats, tables) = placed(&mut pointing, true);
1598        assert!(fired(&stats));
1599        assert_eq!(tables[0].cells, [0, 4, 8, 12]);
1600        let arm = arm(&pointing.func);
1601        for label in 0..4 {
1602            let place = pointing.places[label as usize];
1603            let got = looked_up(&pointing.func, arm, label, &tables);
1604            assert_eq!(got, spot(place) + 4 * label, "{label}");
1605        }
1606    }
1607
1608    /// The default returns the address of `s4`, which is somewhere a table can say it is, so the
1609    /// hole reads that and gets what it got before.
1610    #[test]
1611    fn a_hole_in_a_table_of_addresses_is_where_the_default_points() {
1612        let mut pointing = pointing(&[1, 2, 4, 5], None, 0);
1613        let (stats, tables) = placed(&mut pointing, true);
1614        assert!(fired(&stats));
1615        let places = &pointing.places;
1616        let want = [places[0], places[1], places[4], places[2], places[3]].map(Some);
1617        assert_eq!(tables[0].to, want);
1618        assert_eq!(cases(&pointing.func).len(), 5, "the hole was not given a case");
1619        let arm = arm(&pointing.func);
1620        for (label, place) in [(1, 0), (2, 1), (3, 4), (4, 2), (5, 3)] {
1621            let got = looked_up(&pointing.func, arm, label, &tables);
1622            assert_eq!(got, spot(places[place]), "{label}");
1623        }
1624    }
1625
1626    /// A name the program writes to is still a name this file defines, but it is not in the images,
1627    /// and the images are what says a name is somewhere nothing else can move.
1628    #[test]
1629    fn an_answer_that_is_not_read_only_data_keeps_its_switch() {
1630        let mut pointing = pointing(&[0, 1, 2, 3], Some(2), 0);
1631        let (stats, tables) = placed(&mut pointing, true);
1632        assert!(!fired(&stats));
1633        assert!(tables.is_empty());
1634        assert_eq!(stats.count(Kind::Missed, PLACE_IS_ODD), 1, "{stats:?}");
1635    }
1636
1637    #[test]
1638    fn a_target_with_no_four_byte_distance_keeps_its_switch() {
1639        let mut pointing = pointing(&[0, 1, 2, 3], None, 0);
1640        let (stats, tables) = placed(&mut pointing, false);
1641        assert!(!fired(&stats));
1642        assert!(tables.is_empty());
1643    }
1644}