Skip to main content

logicaffeine_compile/accel/
region.rs

1//! Regions: the functions an accelerator runs, as control-flow graphs over checked cells.
2//!
3//! A region is lowered from a function of the SAME optimized `CompiledProgram` the host is lowered from, so
4//! its function index is the host's. The lowering is typed: every register's kind (`Int`, `Float`, `Bool`)
5//! is inferred by a forward dataflow over the function's blocks from its declared parameter kinds, and each
6//! op becomes the cell whose checked semantics is the VM kernel's for those kinds. A function the lowering
7//! cannot type, or that does anything besides compute a scalar from scalars (a `Show`, a global, a
8//! collection, a call, a closure), stays on the host with the reason named.
9//!
10//! A region is executed two ways: [`RegionCfg::eval_checked`] runs the cells' 64-bit checked semantics (the
11//! reference), and [`RegionCfg::eval_on_lanes`] runs every cell as its lane routine on a device's lanes (the
12//! device model). Control flow is real, not predicated: only the cells on the path the VM takes execute, so
13//! a divergence is reported exactly where the VM would promote or fail.
14
15use std::collections::{BTreeMap, BTreeSet};
16
17use serde::{Deserialize, Serialize};
18
19use crate::codegen_sva::fabric_rtl::{AluOp, CmpKind, Divergence, FuncUnit};
20use crate::semantics::builtins::BuiltinId;
21use crate::vm::{CompiledProgram, Constant, Op, ParamKind, SlotKind};
22
23use super::cells::{routine, CellOp};
24use super::lane::{join_u64, run, split_u64, LaneCounts, Lanes};
25
26/// A region register: one 64-bit value (an `Int`, a `Bool` as 0/1, a `Float` as its bit pattern).
27pub type VReg = u16;
28
29/// The value kinds a region carries across its boundary and between its cells.
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
31pub enum Kind {
32    Int,
33    Float,
34    Bool,
35}
36
37impl Kind {
38    /// The native-boundary slot this kind rides in.
39    pub fn slot(self) -> SlotKind {
40        match self {
41            Kind::Int => SlotKind::Int,
42            Kind::Float => SlotKind::Float,
43            Kind::Bool => SlotKind::Bool,
44        }
45    }
46}
47
48/// One statement of a region block.
49#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
50pub enum RStmt {
51    /// `dst = value`.
52    Const { dst: VReg, value: i64 },
53    /// `dst = src`.
54    Copy { dst: VReg, src: VReg },
55    /// `dst = op(a, b)` under the op's checked semantics; a divergence ends the region.
56    Cell { dst: VReg, op: CellOp, a: VReg, b: VReg },
57    /// `dst = cmp(a, b) ? t : e` over integers.
58    Select { dst: VReg, cmp: CmpKind, a: VReg, b: VReg, t: VReg, e: VReg },
59}
60
61/// Where a block goes.
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
63pub enum RTerm {
64    Jump(u32),
65    /// `cond != 0 ? then_ : else_`.
66    Branch { cond: VReg, then_: u32, else_: u32 },
67    Return(VReg),
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
71pub struct RBlock {
72    pub stmts: Vec<RStmt>,
73    pub term: RTerm,
74}
75
76/// A function as a region: its ports, its register file size, its blocks (block 0 is the entry).
77#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
78pub struct RegionCfg {
79    pub name: String,
80    pub func: u16,
81    pub params: Vec<Kind>,
82    pub ret: Kind,
83    pub regs: u16,
84    pub blocks: Vec<RBlock>,
85}
86
87/// Why a region run stopped short of a value.
88#[derive(Clone, Debug, PartialEq, Eq)]
89pub enum Halt {
90    /// A cell diverged from the VM: the call goes back to the host. The op and its operands are the evidence.
91    Declined { divergence: Divergence, op: CellOp, a: i64, b: i64 },
92    /// The run exceeded the block budget it was given.
93    OutOfFuel,
94}
95
96/// A function the region lowering keeps on the host, and why.
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct HostReason {
99    pub func: u16,
100    pub name: String,
101    pub reason: String,
102}
103
104/// What discovery made of one function.
105#[derive(Clone, Debug, PartialEq, Eq)]
106pub enum Discovered {
107    Region(RegionCfg),
108    Host(HostReason),
109}
110
111/// A register's inferred kind at a program point.
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113enum Ak {
114    Int,
115    Float,
116    Bool,
117    /// A range built by the `NewRange` at this pc.
118    Range(usize),
119    /// Different kinds on different paths.
120    Conflict,
121}
122
123impl Ak {
124    fn of(k: Kind) -> Ak {
125        match k {
126            Kind::Int => Ak::Int,
127            Kind::Float => Ak::Float,
128            Kind::Bool => Ak::Bool,
129        }
130    }
131    fn scalar(self) -> Option<Kind> {
132        match self {
133            Ak::Int => Some(Kind::Int),
134            Ak::Float => Some(Kind::Float),
135            Ak::Bool => Some(Kind::Bool),
136            _ => None,
137        }
138    }
139}
140
141/// The dataflow state at a block boundary: each VM register's kind (absent = no path defines it) and the
142/// stack of live range iterators (by the pc of the `IterPrepare` that pushed each).
143#[derive(Clone, Debug, PartialEq, Eq)]
144struct State {
145    kinds: BTreeMap<u16, Ak>,
146    iters: Vec<usize>,
147}
148
149fn join(a: &State, b: &State) -> Result<State, String> {
150    if a.iters != b.iters {
151        return Err("paths reach one point with different live loops".to_string());
152    }
153    let mut kinds = a.kinds.clone();
154    for (r, k) in &b.kinds {
155        kinds
156            .entry(*r)
157            .and_modify(|cur| {
158                if *cur != *k {
159                    *cur = Ak::Conflict
160                }
161            })
162            .or_insert(*k);
163    }
164    // A register only one path defines is undefined on the other: absent, not its kind.
165    let both: BTreeSet<u16> = a.kinds.keys().filter(|r| b.kinds.contains_key(r)).copied().collect();
166    kinds.retain(|r, _| both.contains(r));
167    Ok(State { kinds, iters: a.iters.clone() })
168}
169
170/// The pcs of `[entry, end)` that start a block.
171fn leaders(code: &[Op], entry: usize, end: usize) -> Result<BTreeSet<usize>, String> {
172    let mut set = BTreeSet::new();
173    set.insert(entry);
174    for pc in entry..end {
175        let within = |t: usize| -> Result<usize, String> {
176            if t < entry || t >= end {
177                Err(format!("a jump at pc {pc} leaves the function (to {t})"))
178            } else {
179                Ok(t)
180            }
181        };
182        match code[pc] {
183            Op::Jump { target } => {
184                set.insert(within(target)?);
185                set.insert(pc + 1);
186            }
187            Op::JumpIfFalse { target, .. } | Op::JumpIfTrue { target, .. } => {
188                set.insert(within(target)?);
189                set.insert(pc + 1);
190            }
191            Op::IterNext { exit, .. } => {
192                set.insert(within(exit)?);
193                set.insert(pc + 1);
194            }
195            Op::Return { .. } | Op::ReturnNothing => {
196                set.insert(pc + 1);
197            }
198            _ => {}
199        }
200    }
201    set.retain(|p| *p < end);
202    Ok(set)
203}
204
205/// The kinds of a function's parameters, or why one is not a region port.
206fn param_kinds(program: &CompiledProgram, fi: usize) -> Result<Vec<Kind>, String> {
207    let f = &program.functions[fi];
208    (0..f.param_count as usize)
209        .map(|i| match f.param_kinds.get(i).copied().flatten() {
210            Some(ParamKind::Scalar(SlotKind::Int)) => Ok(Kind::Int),
211            Some(ParamKind::Scalar(SlotKind::Float)) => Ok(Kind::Float),
212            Some(ParamKind::Scalar(SlotKind::Bool)) => Ok(Kind::Bool),
213            other => Err(format!("parameter {} is not an Int, Float or Bool ({other:?})", i + 1)),
214        })
215        .collect()
216}
217
218/// The code range a function occupies: its entry up to the next function's entry, or the end of the code.
219fn function_range(program: &CompiledProgram, fi: usize) -> (usize, usize) {
220    let entry = program.functions[fi].entry_pc;
221    let end = program.functions.iter().map(|g| g.entry_pc).filter(|&e| e > entry).min().unwrap_or(program.code.len());
222    (entry, end)
223}
224
225/// Lower function `fi` of `program` to a region, or name why it stays on the host.
226pub fn lower(program: &CompiledProgram, fi: usize, name: &str) -> Result<RegionCfg, HostReason> {
227    let host = |reason: String| HostReason { func: fi as u16, name: name.to_string(), reason };
228    let f = &program.functions[fi];
229    if !f.captures.is_empty() {
230        return Err(host("a closure body (it captures variables)".to_string()));
231    }
232    if program.code.iter().any(|op| matches!(op, Op::MakeClosure { func, .. } if *func as usize == fi)) {
233        return Err(host("a function the program also uses as a closure value".to_string()));
234    }
235    let params = param_kinds(program, fi).map_err(host)?;
236    let (entry, end) = function_range(program, fi);
237    let code = &program.code;
238    let starts = leaders(code, entry, end).map_err(host)?;
239    let starts: Vec<usize> = starts.into_iter().collect();
240    let block_of: BTreeMap<usize, usize> = starts.iter().enumerate().map(|(i, p)| (*p, i)).collect();
241    let block_end = |i: usize| starts.get(i + 1).copied().unwrap_or(end);
242    // ── Kinds: a forward dataflow to a fixpoint. ──
243    let mut entry_state: Vec<Option<State>> = vec![None; starts.len()];
244    entry_state[0] = Some(State {
245        kinds: params.iter().enumerate().map(|(i, k)| (i as u16, Ak::of(*k))).collect(),
246        iters: Vec::new(),
247    });
248    let mut work: Vec<usize> = vec![0];
249    let mut guard = 0usize;
250    while let Some(bi) = work.pop() {
251        guard += 1;
252        if guard > 100_000 {
253            return Err(host("the kind analysis did not settle".to_string()));
254        }
255        let mut st = entry_state[bi].clone().expect("a reached block has a state");
256        let mut succ: Vec<usize> = Vec::new();
257        let mut falls = true;
258        for pc in starts[bi]..block_end(bi) {
259            match transfer(program, pc, &mut st).map_err(host)? {
260                Flow::Next => {}
261                Flow::Jump(t) => {
262                    succ.push(t);
263                    falls = false;
264                }
265                Flow::Branch(t) => succ.push(t),
266                Flow::End => falls = false,
267            }
268        }
269        if falls && block_end(bi) < end {
270            succ.push(block_end(bi));
271        }
272        for t in succ {
273            let ti = *block_of.get(&t).ok_or_else(|| host(format!("pc {t} does not start a block")))?;
274            let merged = match &entry_state[ti] {
275                None => st.clone(),
276                Some(cur) => join(cur, &st).map_err(host)?,
277            };
278            if entry_state[ti].as_ref() != Some(&merged) {
279                entry_state[ti] = Some(merged);
280                work.push(ti);
281            }
282        }
283    }
284    // ── Translation. ──
285    let mut tr = Translator::new(program, f.register_count.max(params.len()));
286    let region_blocks: Vec<u32> = (0..starts.len()).map(|_| tr.new_block()).collect();
287    let mut ret: Option<Kind> = None;
288    for bi in 0..starts.len() {
289        let Some(mut st) = entry_state[bi].clone() else { continue };
290        let at = region_blocks[bi];
291        tr.at = at;
292        let mut ended = false;
293        for pc in starts[bi]..block_end(bi) {
294            let before = st.clone();
295            transfer(program, pc, &mut st).map_err(host)?;
296            let term = tr.op(pc, &before, &region_blocks, &block_of, &mut ret).map_err(host)?;
297            if term {
298                ended = true;
299                break;
300            }
301        }
302        if !ended {
303            let next = block_end(bi);
304            if next >= end {
305                return Err(host("control falls off the end of the function".to_string()));
306            }
307            tr.terminate(RTerm::Jump(region_blocks[block_of[&next]]));
308        }
309    }
310    let ret = ret.ok_or_else(|| host("no path returns a value".to_string()))?;
311    let regs = u16::try_from(tr.next_reg).map_err(|_| host("more registers than a region holds".to_string()))?;
312    Ok(RegionCfg { name: name.to_string(), func: fi as u16, params, ret, regs, blocks: tr.finish() })
313}
314
315/// Every function of `program`, each a region or a host reason; `names[i]` is function `i`'s name.
316pub fn discover(program: &CompiledProgram, names: &[String]) -> Vec<Discovered> {
317    (0..program.functions.len())
318        .map(|fi| match lower(program, fi, &names[fi]) {
319            Ok(r) => Discovered::Region(r),
320            Err(h) => Discovered::Host(h),
321        })
322        .collect()
323}
324
325/// What an op does to control.
326enum Flow {
327    Next,
328    /// Unconditional transfer (no fall-through).
329    Jump(usize),
330    /// A possible transfer beside the fall-through.
331    Branch(usize),
332    /// A return.
333    End,
334}
335
336fn scalar(st: &State, r: u16, pc: usize) -> Result<Kind, String> {
337    match st.kinds.get(&r) {
338        Some(k) => k.scalar().ok_or_else(|| format!("pc {pc}: register {r} is not a scalar here ({k:?})")),
339        None => Err(format!("pc {pc}: register {r} is read where no path defines it")),
340    }
341}
342
343/// The kind an arithmetic op on `(a, b)` produces: Int × Int stays Int, a Float operand makes it Float.
344fn numeric(a: Kind, b: Kind, pc: usize, what: &str) -> Result<Kind, String> {
345    match (a, b) {
346        (Kind::Int, Kind::Int) => Ok(Kind::Int),
347        (Kind::Float, Kind::Float) | (Kind::Int, Kind::Float) | (Kind::Float, Kind::Int) => Ok(Kind::Float),
348        _ => Err(format!("pc {pc}: {what} of {a:?} and {b:?}")),
349    }
350}
351
352/// Advance the dataflow state over the op at `pc`.
353fn transfer(program: &CompiledProgram, pc: usize, st: &mut State) -> Result<Flow, String> {
354    let set = |st: &mut State, r: u16, k: Ak| {
355        st.kinds.insert(r, k);
356    };
357    match program.code[pc] {
358        Op::LoadConst { dst, idx } => {
359            let k = match &program.constants[idx as usize] {
360                Constant::Int(_) => Ak::Int,
361                Constant::Float(_) => Ak::Float,
362                Constant::Bool(_) => Ak::Bool,
363                other => return Err(format!("pc {pc}: a {} constant", constant_kind(other))),
364            };
365            set(st, dst, k);
366        }
367        Op::Move { dst, src } => {
368            let k = *st.kinds.get(&src).ok_or_else(|| format!("pc {pc}: register {src} is read where no path defines it"))?;
369            set(st, dst, k);
370        }
371        Op::Add { dst, lhs, rhs } | Op::Sub { dst, lhs, rhs } | Op::Mul { dst, lhs, rhs } | Op::Div { dst, lhs, rhs } => {
372            let k = numeric(scalar(st, lhs, pc)?, scalar(st, rhs, pc)?, pc, program.code[pc].name())?;
373            set(st, dst, Ak::of(k));
374        }
375        Op::AddAssign { dst, src } => {
376            let k = numeric(scalar(st, dst, pc)?, scalar(st, src, pc)?, pc, "AddAssign")?;
377            set(st, dst, Ak::of(k));
378        }
379        Op::FloorDiv { dst, lhs, rhs } | Op::Mod { dst, lhs, rhs } | Op::Pow { dst, lhs, rhs } | Op::Shl { dst, lhs, rhs } | Op::Shr { dst, lhs, rhs } => {
380            let (a, b) = (scalar(st, lhs, pc)?, scalar(st, rhs, pc)?);
381            if (a, b) != (Kind::Int, Kind::Int) {
382                return Err(format!("pc {pc}: {} of {a:?} and {b:?}", program.code[pc].name()));
383            }
384            set(st, dst, Ak::Int);
385        }
386        Op::DivPow2 { dst, lhs, .. } | Op::MagicDivU { dst, lhs, .. } => {
387            if scalar(st, lhs, pc)? != Kind::Int {
388                return Err(format!("pc {pc}: {} of a non-Int", program.code[pc].name()));
389            }
390            set(st, dst, Ak::Int);
391        }
392        Op::Lt { dst, lhs, rhs } | Op::Gt { dst, lhs, rhs } | Op::LtEq { dst, lhs, rhs } | Op::GtEq { dst, lhs, rhs } => {
393            let (a, b) = (scalar(st, lhs, pc)?, scalar(st, rhs, pc)?);
394            if a != b || a == Kind::Bool {
395                return Err(format!("pc {pc}: an ordering of {a:?} and {b:?}"));
396            }
397            set(st, dst, Ak::Bool);
398        }
399        Op::Eq { dst, lhs, rhs } | Op::NotEq { dst, lhs, rhs } => {
400            let (a, b) = (scalar(st, lhs, pc)?, scalar(st, rhs, pc)?);
401            if a != b {
402                return Err(format!("pc {pc}: an equality of {a:?} and {b:?}"));
403            }
404            set(st, dst, Ak::Bool);
405        }
406        Op::ApproxEq { dst, lhs, rhs } => {
407            let (a, b) = (scalar(st, lhs, pc)?, scalar(st, rhs, pc)?);
408            if (a, b) != (Kind::Int, Kind::Int) {
409                return Err(format!("pc {pc}: `is approximately` of {a:?} and {b:?}"));
410            }
411            set(st, dst, Ak::Bool);
412        }
413        Op::Not { dst, src } => {
414            scalar(st, src, pc)?;
415            set(st, dst, Ak::Bool);
416        }
417        Op::BitXor { dst, lhs, rhs } | Op::BitAnd { dst, lhs, rhs } | Op::BitOr { dst, lhs, rhs } => {
418            let (a, b) = (scalar(st, lhs, pc)?, scalar(st, rhs, pc)?);
419            let k = match (a, b) {
420                (Kind::Int, Kind::Int) => Ak::Int,
421                (Kind::Bool, Kind::Bool) => Ak::Bool,
422                _ => return Err(format!("pc {pc}: {} of {a:?} and {b:?}", program.code[pc].name())),
423            };
424            set(st, dst, k);
425        }
426        Op::CallBuiltin { dst, builtin, args_start, arg_count } => {
427            let k = builtin_kind(st, builtin, args_start, arg_count, pc)?;
428            set(st, dst, Ak::of(k));
429        }
430        Op::Jump { target } => return Ok(Flow::Jump(target)),
431        Op::JumpIfFalse { cond, target } | Op::JumpIfTrue { cond, target } => {
432            scalar(st, cond, pc)?;
433            return Ok(Flow::Branch(target));
434        }
435        Op::Return { src } => {
436            scalar(st, src, pc)?;
437            return Ok(Flow::End);
438        }
439        Op::ReturnNothing => return Err(format!("pc {pc}: a path returns nothing")),
440        Op::NewRange { dst, start, end } => {
441            if scalar(st, start, pc)? != Kind::Int || scalar(st, end, pc)? != Kind::Int {
442                return Err(format!("pc {pc}: a range over non-Int bounds"));
443            }
444            set(st, dst, Ak::Range(pc));
445        }
446        Op::IterPrepare { iterable } => match st.kinds.get(&iterable) {
447            Some(Ak::Range(_)) => st.iters.push(pc),
448            other => return Err(format!("pc {pc}: iterating over {other:?}, not a range")),
449        },
450        Op::IterNext { dst, exit } => {
451            if st.iters.is_empty() {
452                return Err(format!("pc {pc}: IterNext with no live loop"));
453            }
454            set(st, dst, Ak::Int);
455            return Ok(Flow::Branch(exit));
456        }
457        Op::IterPop => {
458            st.iters.pop().ok_or_else(|| format!("pc {pc}: IterPop with no live loop"))?;
459        }
460        ref other => return Err(format!("pc {pc}: `{}` is not scalar region computation", other.name())),
461    }
462    Ok(Flow::Next)
463}
464
465fn constant_kind(c: &Constant) -> &'static str {
466    match c {
467        Constant::Int(_) => "Int",
468        Constant::Float(_) => "Float",
469        Constant::Bool(_) => "Bool",
470        Constant::Text(_) => "Text",
471        Constant::Char(_) => "Char",
472        Constant::Nothing => "Nothing",
473        Constant::Duration(_) => "Duration",
474        Constant::Date(_) => "Date",
475        Constant::Moment(_) => "Moment",
476        Constant::Span { .. } => "Span",
477        Constant::Time(_) => "Time",
478    }
479}
480
481/// The builtins a region computes: `sqrt` (a Float), `floor` (an Int), `count_ones` (an Int).
482fn builtin_kind(st: &State, b: BuiltinId, start: u16, count: u16, pc: usize) -> Result<Kind, String> {
483    if count != 1 {
484        return Err(format!("pc {pc}: builtin {b:?} with {count} arguments"));
485    }
486    let a = scalar(st, start, pc)?;
487    match (b, a) {
488        (BuiltinId::Sqrt, Kind::Int | Kind::Float) => Ok(Kind::Float),
489        (BuiltinId::Floor, Kind::Int | Kind::Float) => Ok(Kind::Int),
490        (BuiltinId::CountOnes, Kind::Int) => Ok(Kind::Int),
491        _ => Err(format!("pc {pc}: builtin {b:?} of {a:?}")),
492    }
493}
494
495/// The region under construction.
496struct Translator<'a> {
497    program: &'a CompiledProgram,
498    next_reg: usize,
499    blocks: Vec<RBlock>,
500    open: Vec<Vec<RStmt>>,
501    terms: Vec<Option<RTerm>>,
502    at: u32,
503    consts: BTreeMap<i64, VReg>,
504    const_stmts: Vec<RStmt>,
505    /// `NewRange` pc → the registers holding its start and end.
506    ranges: BTreeMap<usize, (VReg, VReg)>,
507    /// `IterPrepare` pc → its cursor, end and done registers.
508    iters: BTreeMap<usize, (VReg, VReg, VReg)>,
509}
510
511impl<'a> Translator<'a> {
512    fn new(program: &'a CompiledProgram, vm_regs: usize) -> Self {
513        Translator {
514            program,
515            next_reg: vm_regs,
516            blocks: Vec::new(),
517            open: Vec::new(),
518            terms: Vec::new(),
519            at: 0,
520            consts: BTreeMap::new(),
521            const_stmts: Vec::new(),
522            ranges: BTreeMap::new(),
523            iters: BTreeMap::new(),
524        }
525    }
526
527    fn reg(&mut self) -> VReg {
528        let r = self.next_reg as VReg;
529        self.next_reg += 1;
530        r
531    }
532
533    fn new_block(&mut self) -> u32 {
534        self.open.push(Vec::new());
535        self.terms.push(None);
536        (self.open.len() - 1) as u32
537    }
538
539    fn push(&mut self, s: RStmt) {
540        self.open[self.at as usize].push(s);
541    }
542
543    fn terminate(&mut self, t: RTerm) {
544        self.terms[self.at as usize] = Some(t);
545    }
546
547    /// A register holding `v`, set once at the region's entry.
548    fn k(&mut self, v: i64) -> VReg {
549        if let Some(r) = self.consts.get(&v) {
550            return *r;
551        }
552        let r = self.reg();
553        self.const_stmts.push(RStmt::Const { dst: r, value: v });
554        self.consts.insert(v, r);
555        r
556    }
557
558    fn cell(&mut self, op: CellOp, a: VReg, b: VReg) -> VReg {
559        let dst = self.reg();
560        self.push(RStmt::Cell { dst, op, a, b });
561        dst
562    }
563
564    /// `x` as a Float: an Int is promoted by the kernel's `as f64`.
565    fn float(&mut self, x: VReg, k: Kind) -> VReg {
566        match k {
567            Kind::Float => x,
568            _ => {
569                let zero = self.k(0);
570                self.cell(CellOp::Unit(FuncUnit::IntToF), x, zero)
571            }
572        }
573    }
574
575    /// A branch condition's truth as a 0/1 register: `Int`/`Bool` are truthy when nonzero, a `Float` when
576    /// it is not ±0 (NaN is truthy).
577    fn truthy(&mut self, c: VReg, k: Kind) -> VReg {
578        match k {
579            Kind::Int | Kind::Bool => c,
580            Kind::Float => {
581                let zero = self.k(0);
582                self.cell(CellOp::Unit(FuncUnit::FNeF), c, zero)
583            }
584        }
585    }
586
587    fn finish(mut self) -> Vec<RBlock> {
588        let mut entry_consts = std::mem::take(&mut self.const_stmts);
589        let mut blocks = Vec::with_capacity(self.open.len());
590        for (i, stmts) in self.open.into_iter().enumerate() {
591            let term = self.terms[i].unwrap_or(RTerm::Jump(i as u32));
592            let stmts = if i == 0 {
593                let mut s = std::mem::take(&mut entry_consts);
594                s.extend(stmts);
595                s
596            } else {
597                stmts
598            };
599            blocks.push(RBlock { stmts, term });
600        }
601        self.blocks = blocks;
602        self.blocks
603    }
604
605    /// Translate the op at `pc`; `before` is the dataflow state entering it. Returns whether the op
606    /// terminated the block.
607    fn op(
608        &mut self,
609        pc: usize,
610        before: &State,
611        region_blocks: &[u32],
612        block_of: &BTreeMap<usize, usize>,
613        ret: &mut Option<Kind>,
614    ) -> Result<bool, String> {
615        let blk = |p: usize| -> Result<u32, String> {
616            block_of.get(&p).map(|i| region_blocks[*i]).ok_or_else(|| format!("pc {p} does not start a block"))
617        };
618        let kind = |r: u16| scalar(before, r, pc);
619        match self.program.code[pc] {
620            Op::LoadConst { dst, idx } => {
621                let v = match &self.program.constants[idx as usize] {
622                    Constant::Int(n) => *n,
623                    Constant::Bool(b) => i64::from(*b),
624                    Constant::Float(f) => f.to_bits() as i64,
625                    other => return Err(format!("pc {pc}: a {} constant", constant_kind(other))),
626                };
627                let c = self.k(v);
628                self.push(RStmt::Copy { dst, src: c });
629            }
630            Op::Move { dst, src } => match before.kinds.get(&src) {
631                Some(Ak::Range(_)) => {}
632                _ => self.push(RStmt::Copy { dst, src }),
633            },
634            Op::Add { dst, lhs, rhs } => self.arith(dst, lhs, rhs, kind(lhs)?, kind(rhs)?, AluOp::Add, FuncUnit::FAddF),
635            Op::AddAssign { dst, src } => self.arith(dst, dst, src, kind(dst)?, kind(src)?, AluOp::Add, FuncUnit::FAddF),
636            Op::Sub { dst, lhs, rhs } => self.arith(dst, lhs, rhs, kind(lhs)?, kind(rhs)?, AluOp::Sub, FuncUnit::FSubF),
637            Op::Mul { dst, lhs, rhs } => self.arith(dst, lhs, rhs, kind(lhs)?, kind(rhs)?, AluOp::Mul, FuncUnit::FMulF),
638            Op::Div { dst, lhs, rhs } => {
639                let (a, b) = (kind(lhs)?, kind(rhs)?);
640                if (a, b) == (Kind::Int, Kind::Int) {
641                    let v = self.cell(CellOp::Unit(FuncUnit::DivS), lhs, rhs);
642                    self.push(RStmt::Copy { dst, src: v });
643                } else {
644                    let (x, y) = (self.float(lhs, a), self.float(rhs, b));
645                    let v = self.cell(CellOp::Unit(FuncUnit::FDivF), x, y);
646                    self.push(RStmt::Copy { dst, src: v });
647                }
648            }
649            Op::FloorDiv { dst, lhs, rhs } => self.int_unit(dst, lhs, rhs, FuncUnit::FloorDivS),
650            Op::Mod { dst, lhs, rhs } => self.int_unit(dst, lhs, rhs, FuncUnit::ModS),
651            Op::Pow { dst, lhs, rhs } => self.int_unit(dst, lhs, rhs, FuncUnit::Pow),
652            Op::Shl { dst, lhs, rhs } => self.alu(dst, lhs, rhs, AluOp::Shl),
653            Op::Shr { dst, lhs, rhs } => self.alu(dst, lhs, rhs, AluOp::Shr),
654            Op::BitXor { dst, lhs, rhs } => self.alu(dst, lhs, rhs, AluOp::Xor),
655            Op::BitAnd { dst, lhs, rhs } => self.alu(dst, lhs, rhs, AluOp::And),
656            Op::BitOr { dst, lhs, rhs } => self.alu(dst, lhs, rhs, AluOp::Or),
657            Op::DivPow2 { dst, lhs, k } => {
658                let d = self.k(1i64 << k);
659                self.int_unit(dst, lhs, d, FuncUnit::DivS);
660            }
661            Op::MagicDivU { dst, lhs, mul_back, divisor, .. } => {
662                let d = self.k(divisor);
663                self.int_unit(dst, lhs, d, if mul_back == 0 { FuncUnit::DivS } else { FuncUnit::ModS });
664            }
665            Op::Lt { dst, lhs, rhs } => self.compare(dst, lhs, rhs, kind(lhs)?, CmpKind::Lt, FuncUnit::FLtF),
666            Op::Gt { dst, lhs, rhs } => self.compare(dst, lhs, rhs, kind(lhs)?, CmpKind::Gt, FuncUnit::FGtF),
667            Op::LtEq { dst, lhs, rhs } => self.compare(dst, lhs, rhs, kind(lhs)?, CmpKind::Le, FuncUnit::FLeF),
668            Op::GtEq { dst, lhs, rhs } => self.compare(dst, lhs, rhs, kind(lhs)?, CmpKind::Ge, FuncUnit::FGeF),
669            Op::Eq { dst, lhs, rhs } => self.compare(dst, lhs, rhs, kind(lhs)?, CmpKind::Eq, FuncUnit::FEqF),
670            Op::NotEq { dst, lhs, rhs } => self.compare(dst, lhs, rhs, kind(lhs)?, CmpKind::Ne, FuncUnit::FNeF),
671            Op::ApproxEq { dst, lhs, rhs } => {
672                let v = self.cell(CellOp::Unit(FuncUnit::ApproxEqF), lhs, rhs);
673                self.push(RStmt::Copy { dst, src: v });
674            }
675            Op::Not { dst, src } => {
676                let t = self.truthy(src, kind(src)?);
677                let (zero, one) = (self.k(0), self.k(1));
678                self.push(RStmt::Select { dst, cmp: CmpKind::Eq, a: t, b: zero, t: one, e: zero });
679            }
680            Op::CallBuiltin { dst, builtin, args_start, .. } => {
681                let a = kind(args_start)?;
682                match builtin {
683                    BuiltinId::Sqrt => {
684                        let x = self.float(args_start, a);
685                        let zero = self.k(0);
686                        let v = self.cell(CellOp::Unit(FuncUnit::FSqrtF), x, zero);
687                        self.push(RStmt::Copy { dst, src: v });
688                    }
689                    BuiltinId::Floor if a == Kind::Int => self.push(RStmt::Copy { dst, src: args_start }),
690                    BuiltinId::Floor => {
691                        let zero = self.k(0);
692                        let v = self.cell(CellOp::Unit(FuncUnit::FFloorF), args_start, zero);
693                        self.push(RStmt::Copy { dst, src: v });
694                    }
695                    BuiltinId::CountOnes => {
696                        let zero = self.k(0);
697                        let v = self.cell(CellOp::Unit(FuncUnit::PopCount), args_start, zero);
698                        self.push(RStmt::Copy { dst, src: v });
699                    }
700                    other => return Err(format!("pc {pc}: builtin {other:?}")),
701                }
702            }
703            Op::Jump { target } => {
704                self.terminate(RTerm::Jump(blk(target)?));
705                return Ok(true);
706            }
707            Op::JumpIfFalse { cond, target } => {
708                let c = self.truthy(cond, kind(cond)?);
709                self.terminate(RTerm::Branch { cond: c, then_: blk(pc + 1)?, else_: blk(target)? });
710                return Ok(true);
711            }
712            Op::JumpIfTrue { cond, target } => {
713                let c = self.truthy(cond, kind(cond)?);
714                self.terminate(RTerm::Branch { cond: c, then_: blk(target)?, else_: blk(pc + 1)? });
715                return Ok(true);
716            }
717            Op::Return { src } => {
718                let k = kind(src)?;
719                match ret {
720                    None => *ret = Some(k),
721                    Some(r) if *r == k => {}
722                    Some(r) => return Err(format!("pc {pc}: returns a {k:?} where another path returns a {r:?}")),
723                }
724                self.terminate(RTerm::Return(src));
725                return Ok(true);
726            }
727            Op::NewRange { start, end, .. } => {
728                let (s, e) = (self.reg(), self.reg());
729                self.push(RStmt::Copy { dst: s, src: start });
730                self.push(RStmt::Copy { dst: e, src: end });
731                self.ranges.insert(pc, (s, e));
732            }
733            Op::IterPrepare { iterable } => {
734                let Some(Ak::Range(rp)) = before.kinds.get(&iterable).copied() else {
735                    return Err(format!("pc {pc}: iterating over a non-range"));
736                };
737                let (s, e) = *self.ranges.get(&rp).ok_or_else(|| format!("pc {pc}: the range's NewRange was not translated first"))?;
738                let (cur, end, done) = (self.reg(), self.reg(), self.reg());
739                let (zero, one) = (self.k(0), self.k(1));
740                self.push(RStmt::Copy { dst: cur, src: s });
741                self.push(RStmt::Copy { dst: end, src: e });
742                self.push(RStmt::Select { dst: done, cmp: CmpKind::Gt, a: cur, b: end, t: one, e: zero });
743                self.iters.insert(pc, (cur, end, done));
744            }
745            Op::IterNext { dst, exit } => {
746                let q = *before.iters.last().ok_or_else(|| format!("pc {pc}: IterNext with no live loop"))?;
747                let (cur, end, done) = *self.iters.get(&q).ok_or_else(|| format!("pc {pc}: the loop's IterPrepare was not translated first"))?;
748                let body = blk(pc + 1)?;
749                let exit_b = blk(exit)?;
750                let step = self.new_block();
751                let last = self.new_block();
752                let inc = self.new_block();
753                self.terminate(RTerm::Branch { cond: done, then_: exit_b, else_: step });
754                let (zero, one) = (self.k(0), self.k(1));
755                self.at = step;
756                self.push(RStmt::Copy { dst, src: cur });
757                let is_last = self.reg();
758                self.push(RStmt::Select { dst: is_last, cmp: CmpKind::Eq, a: cur, b: end, t: one, e: zero });
759                self.terminate(RTerm::Branch { cond: is_last, then_: last, else_: inc });
760                self.at = last;
761                self.push(RStmt::Copy { dst: done, src: one });
762                self.terminate(RTerm::Jump(body));
763                self.at = inc;
764                let next = self.cell(CellOp::Alu(AluOp::Add), cur, one);
765                self.push(RStmt::Copy { dst: cur, src: next });
766                self.terminate(RTerm::Jump(body));
767                return Ok(true);
768            }
769            Op::IterPop => {}
770            ref other => return Err(format!("pc {pc}: `{}` is not scalar region computation", other.name())),
771        }
772        Ok(false)
773    }
774
775    fn arith(&mut self, dst: VReg, lhs: VReg, rhs: VReg, a: Kind, b: Kind, int: AluOp, float: FuncUnit) {
776        let v = if (a, b) == (Kind::Int, Kind::Int) {
777            self.cell(CellOp::Alu(int), lhs, rhs)
778        } else {
779            let (x, y) = (self.float(lhs, a), self.float(rhs, b));
780            self.cell(CellOp::Unit(float), x, y)
781        };
782        self.push(RStmt::Copy { dst, src: v });
783    }
784
785    fn int_unit(&mut self, dst: VReg, lhs: VReg, rhs: VReg, unit: FuncUnit) {
786        let v = self.cell(CellOp::Unit(unit), lhs, rhs);
787        self.push(RStmt::Copy { dst, src: v });
788    }
789
790    fn alu(&mut self, dst: VReg, lhs: VReg, rhs: VReg, op: AluOp) {
791        let v = self.cell(CellOp::Alu(op), lhs, rhs);
792        self.push(RStmt::Copy { dst, src: v });
793    }
794
795    fn compare(&mut self, dst: VReg, lhs: VReg, rhs: VReg, k: Kind, cmp: CmpKind, float: FuncUnit) {
796        if k == Kind::Float {
797            let v = self.cell(CellOp::Unit(float), lhs, rhs);
798            self.push(RStmt::Copy { dst, src: v });
799        } else {
800            let (zero, one) = (self.k(0), self.k(1));
801            self.push(RStmt::Select { dst, cmp, a: lhs, b: rhs, t: one, e: zero });
802        }
803    }
804}
805
806impl RegionCfg {
807    /// Run the region under the cells' 64-bit checked semantics: the reference every device model is held
808    /// to. `fuel` bounds the blocks executed.
809    pub fn eval_checked(&self, args: &[i64], fuel: u64) -> Result<i64, Halt> {
810        assert_eq!(args.len(), self.params.len(), "{}: arity", self.name);
811        let mut regs = vec![0i64; self.regs as usize];
812        regs[..args.len()].copy_from_slice(args);
813        let mut block = 0usize;
814        let mut left = fuel;
815        loop {
816            if left == 0 {
817                return Err(Halt::OutOfFuel);
818            }
819            left -= 1;
820            let b = &self.blocks[block];
821            for s in &b.stmts {
822                match *s {
823                    RStmt::Const { dst, value } => regs[dst as usize] = value,
824                    RStmt::Copy { dst, src } => regs[dst as usize] = regs[src as usize],
825                    RStmt::Cell { dst, op, a, b } => {
826                        let (x, y) = (regs[a as usize], regs[b as usize]);
827                        let r = match op {
828                            CellOp::Alu(alu) => alu.eval_checked(x, y),
829                            CellOp::Unit(u) => u.eval_checked(x, y),
830                            CellOp::Select(c) => Ok(i64::from(c.eval(x, y))),
831                        };
832                        regs[dst as usize] = r.map_err(|d| Halt::Declined { divergence: d, op, a: x, b: y })?;
833                    }
834                    RStmt::Select { dst, cmp, a, b, t, e } => {
835                        regs[dst as usize] = if cmp.eval(regs[a as usize], regs[b as usize]) { regs[t as usize] } else { regs[e as usize] };
836                    }
837                }
838            }
839            match b.term {
840                RTerm::Jump(t) => block = t as usize,
841                RTerm::Branch { cond, then_, else_ } => block = if regs[cond as usize] != 0 { then_ } else { else_ } as usize,
842                RTerm::Return(v) => return Ok(regs[v as usize]),
843            }
844        }
845    }
846
847    /// Run the region on a device's lanes: every value in the device's words, every cell its lane routine.
848    /// The result, and the lane operations it cost.
849    pub fn eval_on_lanes(&self, lanes: &mut dyn Lanes, args: &[i64], fuel: u64) -> Result<(i64, LaneCounts), Halt> {
850        assert_eq!(args.len(), self.params.len(), "{}: arity", self.name);
851        let shape = lanes.shape();
852        let n = shape.words_for(64);
853        let mut regs: Vec<Vec<u32>> = vec![vec![0; n]; self.regs as usize];
854        for (i, a) in args.iter().enumerate() {
855            regs[i] = split_u64(shape, *a as u64, n);
856        }
857        let mut counts = LaneCounts::default();
858        let mut block = 0usize;
859        let mut left = fuel;
860        let lane_fault = |e: super::lane::LaneFault| -> Halt { panic!("{}: a lane routine refused its inputs: {e}", self.name) };
861        loop {
862            if left == 0 {
863                return Err(Halt::OutOfFuel);
864            }
865            left -= 1;
866            let b = &self.blocks[block];
867            for s in &b.stmts {
868                match *s {
869                    RStmt::Const { dst, value } => regs[dst as usize] = split_u64(shape, value as u64, n),
870                    RStmt::Copy { dst, src } => regs[dst as usize] = regs[src as usize].clone(),
871                    RStmt::Cell { dst, op, a, b } => {
872                        let r = routine(shape, op);
873                        let mut input = regs[a as usize].clone();
874                        input.extend_from_slice(&regs[b as usize]);
875                        let (out, c) = run(&r, lanes, &input).map_err(lane_fault)?;
876                        counts.absorb(&c);
877                        let value = out[..n].to_vec();
878                        if op.faults() {
879                            let code = join_u64(shape, &out[n..]) as u32;
880                            if code != 0 {
881                                let divergence = Divergence::from_code(code).unwrap_or_else(|| panic!("{}: fault code {code}", self.name));
882                                let (x, y) = (join_u64(shape, &regs[a as usize]) as i64, join_u64(shape, &regs[b as usize]) as i64);
883                                return Err(Halt::Declined { divergence, op, a: x, b: y });
884                            }
885                        }
886                        regs[dst as usize] = value;
887                    }
888                    RStmt::Select { dst, cmp, a, b, t, e } => {
889                        let r = routine(shape, CellOp::Select(cmp));
890                        let mut input = regs[a as usize].clone();
891                        input.extend_from_slice(&regs[b as usize]);
892                        input.extend_from_slice(&regs[t as usize]);
893                        input.extend_from_slice(&regs[e as usize]);
894                        let (out, c) = run(&r, lanes, &input).map_err(lane_fault)?;
895                        counts.absorb(&c);
896                        regs[dst as usize] = out;
897                    }
898                }
899            }
900            match b.term {
901                RTerm::Jump(t) => block = t as usize,
902                RTerm::Branch { cond, then_, else_ } => {
903                    block = if regs[cond as usize].iter().any(|w| *w != 0) { then_ } else { else_ } as usize
904                }
905                RTerm::Return(v) => return Ok((join_u64(shape, &regs[v as usize]) as i64, counts)),
906            }
907        }
908    }
909}