#![allow(dead_code)]
use super::super::{BinCode, UnCode};
use super::super::{DimI, DimU};
use smallvec::SmallVec;
pub(crate) type SlotId = u32;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub(crate) enum Cadence {
Const = 0,
Segment = 1,
Continuous = 2,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Operand {
Slot(SlotId),
Lit(f64),
Param(u32),
Time,
State(u32),
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum SrcRef {
Slot(SlotId),
State(u32),
}
impl From<SrcRef> for Operand {
fn from(src: SrcRef) -> Self {
match src {
SrcRef::Slot(s) => Operand::Slot(s),
SrcRef::State(i) => Operand::State(i),
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum Instr {
Bin {
op: BinCode,
a: Operand,
b: Operand,
out: SlotId,
},
Un { op: UnCode, a: Operand, out: SlotId },
Neg { a: Operand, out: SlotId },
Select {
cond: Operand,
a: Operand,
b: Operand,
out: SlotId,
},
Gather { src: SrcRef, plan: u32, out: SlotId },
LoadElem {
src: SrcRef,
idx: SmallVec<[usize; 4]>,
out: SlotId,
},
Ramp { axis: u8, lo: i64, out: SlotId },
Fill { v: Operand, out: SlotId },
Copy { a: Operand, out: SlotId },
Region {
base: SlotId,
src: Operand,
region: u32,
out: SlotId,
},
Assemble { table: u32, out: SlotId },
Interp {
table: u32,
x: Operand,
y: Option<Operand>,
out: SlotId,
},
Calendar { func: u8, a: Operand, out: SlotId },
ConstArray { data: u32, out: SlotId },
LoadForcing { forcing: u32, out: SlotId },
Reduce {
op: BinCode,
init: f64,
src: SrcRef,
axes: SmallVec<[u8; 4]>,
reverse: bool,
src_shape: DimU,
out: SlotId,
},
Scan {
op: BinCode,
init: f64,
src: SrcRef,
axis: u8,
inclusive: bool,
reverse: bool,
src_shape: DimU,
out: SlotId,
},
PolyArea {
a: SrcRef,
b: SrcRef,
geom: u32,
out: SlotId,
},
IndexGather {
src: SrcRef,
idx: Operand,
spec: u32,
out: SlotId,
},
TableGather {
src: SrcRef,
table: u32,
out: SlotId,
},
DataGather {
src: SrcRef,
spec: u32,
out: SlotId,
faults: bool,
},
SegReduce {
op: BinCode,
init: f64,
src: SlotId,
mask: Option<SlotId>,
table: u32,
out: SlotId,
},
Reshape { src: SrcRef, out: SlotId },
Fault { fault: u32 },
FaultScan { spec: u32 },
Sweep { spec: u32 },
ScalarRead { src: SrcRef, spec: u32, out: SlotId },
JmpIfZero {
cond: Operand,
n_true: u32,
n_false: u32,
},
Export { slot: SlotId, export: u32 },
DyWrite { write: u32 },
Fused { spec: u32 },
Lanes { spec: u32 },
}
impl Instr {
pub(crate) fn out(&self) -> Option<SlotId> {
match self {
Instr::Bin { out, .. }
| Instr::Un { out, .. }
| Instr::Neg { out, .. }
| Instr::Select { out, .. }
| Instr::Gather { out, .. }
| Instr::LoadElem { out, .. }
| Instr::Ramp { out, .. }
| Instr::Fill { out, .. }
| Instr::Copy { out, .. }
| Instr::Region { out, .. }
| Instr::Assemble { out, .. }
| Instr::ConstArray { out, .. }
| Instr::LoadForcing { out, .. }
| Instr::Interp { out, .. }
| Instr::Calendar { out, .. }
| Instr::Reduce { out, .. }
| Instr::Scan { out, .. }
| Instr::PolyArea { out, .. }
| Instr::IndexGather { out, .. }
| Instr::TableGather { out, .. }
| Instr::DataGather { out, .. }
| Instr::SegReduce { out, .. }
| Instr::Reshape { out, .. }
| Instr::ScalarRead { out, .. } => Some(*out),
Instr::JmpIfZero { .. }
| Instr::Sweep { .. }
| Instr::Fault { .. }
| Instr::FaultScan { .. }
| Instr::Export { .. }
| Instr::DyWrite { .. }
| Instr::Fused { .. }
| Instr::Lanes { .. } => None,
}
}
pub(crate) fn for_each_def(&self, t: &SlotTables, mut f: impl FnMut(SlotId)) {
match self {
Instr::Sweep { spec } => {
let sw = &t.sweeps[*spec as usize];
f(sw.out);
for &c in &sw.coords {
f(c);
}
}
Instr::Lanes { spec } => {
for w in &t.lanes[*spec as usize].writes {
if let LaneDst::Slot(s) = w.dst {
f(s);
}
}
}
Instr::Fused { spec } => {
let fs = &t.fused[*spec as usize];
for &(_, slot) in &fs.outputs {
f(slot);
}
if let Some(r) = &fs.reduce {
f(r.out);
}
if let Some(p) = &fs.pre {
f(p.out);
}
}
other => {
if let Some(o) = other.out() {
f(o);
}
}
}
}
pub(crate) fn for_each_read(&self, t: &SlotTables, mut f: impl FnMut(SlotId)) {
let mut op = |o: &Operand| {
if let Operand::Slot(s) = o {
f(*s);
}
};
match self {
Instr::Bin { a, b, .. } => {
op(a);
op(b);
}
Instr::Un { a, .. } | Instr::Neg { a, .. } | Instr::Calendar { a, .. } => op(a),
Instr::Select { cond, a, b, .. } => {
op(cond);
op(a);
op(b);
}
Instr::Gather { src, .. }
| Instr::LoadElem { src, .. }
| Instr::Reduce { src, .. }
| Instr::Scan { src, .. }
| Instr::TableGather { src, .. }
| Instr::Reshape { src, .. } => {
if let SrcRef::Slot(s) = src {
f(*s);
}
}
Instr::PolyArea { a, b, .. } => {
for src in [a, b] {
if let SrcRef::Slot(s) = src {
f(*s);
}
}
}
Instr::IndexGather { src, idx, .. } => {
if let SrcRef::Slot(s) = src {
op(&Operand::Slot(*s));
}
op(idx);
}
Instr::SegReduce { src, mask, .. } => {
f(*src);
if let Some(m) = mask {
f(*m);
}
}
Instr::Ramp { .. } | Instr::ConstArray { .. } | Instr::LoadForcing { .. } => {}
Instr::Lanes { spec } => {
let ls = &t.lanes[*spec as usize];
for inp in &ls.inputs {
if inp.kind == LaneKind::Slot {
for l in 0..ls.lanes as usize {
op(&Operand::Slot(inp.ix.at(l)));
}
}
}
for sc in &ls.scalars {
op(sc);
}
}
Instr::Interp { x, y, .. } => {
op(x);
if let Some(y) = y {
op(y);
}
}
Instr::Fill { v, .. } => op(v),
Instr::Copy { a, .. } => op(a),
Instr::Region { base, src, .. } => {
op(src);
op(&Operand::Slot(*base));
}
Instr::Assemble { table, .. } => {
for (src, _) in &t.assemblies[*table as usize].parts {
op(src);
}
}
Instr::DataGather { src, spec, .. } => {
if let SrcRef::Slot(s) = src {
op(&Operand::Slot(*s));
}
for s in &t.data_gathers[*spec as usize].subs {
op(s);
}
}
Instr::ScalarRead { src, spec, .. } => {
if let SrcRef::Slot(s) = src {
op(&Operand::Slot(*s));
}
let sp = &t.scalar_reads[*spec as usize];
for s in &sp.subs {
op(s);
}
if let ScalarReadKind::SelfRead { sweep } = sp.kind {
for &c in &t.sweeps[sweep as usize].coords {
op(&Operand::Slot(c));
}
}
}
Instr::JmpIfZero { cond, .. } => op(cond),
Instr::Sweep { spec } => op(&t.sweeps[*spec as usize].result),
Instr::Fault { .. } => {}
Instr::FaultScan { spec } => {
for c in &t.fault_scans[*spec as usize].cands {
for pc in &c.path {
op(&pc.op);
}
if let FaultWhat::ConstOob { dims, .. } = &c.what {
for od in dims {
if let OobSub::Data { op: o, .. } = &od.sub {
op(o);
}
}
}
}
}
Instr::Export { slot, .. } => f(*slot),
Instr::DyWrite { write } => f(t.dy_writes[*write as usize].slot),
Instr::Fused { spec } => {
let fs = &t.fused[*spec as usize];
for inp in &fs.inputs {
if let SrcRef::Slot(s) = inp.src {
op(&Operand::Slot(s));
}
}
for sc in &fs.scalars {
op(sc);
}
if let Some(PreReduce {
src: SrcRef::Slot(s),
..
}) = &fs.pre
{
op(&Operand::Slot(*s));
}
}
}
}
pub(crate) fn opcode(&self) -> &'static str {
match self {
Instr::Bin { .. } => "Bin",
Instr::Un { .. } => "Un",
Instr::Neg { .. } => "Neg",
Instr::Select { .. } => "Select",
Instr::Gather { .. } => "Gather",
Instr::LoadElem { .. } => "LoadElem",
Instr::Ramp { .. } => "Ramp",
Instr::Fill { .. } => "Fill",
Instr::Copy { .. } => "Copy",
Instr::Region { .. } => "Region",
Instr::Assemble { .. } => "Assemble",
Instr::ConstArray { .. } => "ConstArray",
Instr::LoadForcing { .. } => "LoadForcing",
Instr::Interp { .. } => "Interp",
Instr::Calendar { .. } => "Calendar",
Instr::Reduce { .. } => "Reduce",
Instr::Scan { .. } => "Scan",
Instr::PolyArea { .. } => "PolyArea",
Instr::IndexGather { .. } => "IndexGather",
Instr::TableGather { .. } => "TableGather",
Instr::DataGather { .. } => "DataGather",
Instr::SegReduce { .. } => "SegReduce",
Instr::Reshape { .. } => "Reshape",
Instr::Fault { .. } => "Fault",
Instr::FaultScan { .. } => "FaultScan",
Instr::Sweep { .. } => "Sweep",
Instr::ScalarRead { .. } => "ScalarRead",
Instr::JmpIfZero { .. } => "JmpIfZero",
Instr::Export { .. } => "Export",
Instr::DyWrite { .. } => "DyWrite",
Instr::Fused { .. } => "Fused",
Instr::Lanes { .. } => "Lanes",
}
}
}
pub(crate) type GroupIx = u16;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum MRef {
Reg(GroupIx),
In(GroupIx),
Scal(GroupIx),
}
#[derive(Clone, Debug)]
pub(crate) enum MicroOp {
Bin {
op: BinCode,
a: MRef,
b: MRef,
out: GroupIx,
},
Un {
op: UnCode,
a: MRef,
out: GroupIx,
},
Neg {
a: MRef,
out: GroupIx,
},
Select {
cond: MRef,
a: MRef,
b: MRef,
out: GroupIx,
},
Mov {
a: MRef,
out: GroupIx,
},
Bin2 {
op1: BinCode,
a: MRef,
b: MRef,
op2: BinCode,
c: MRef,
swap: bool,
out: GroupIx,
},
Bin3 {
op1: BinCode,
a: MRef,
b: MRef,
op2: BinCode,
c: MRef,
swap2: bool,
op3: BinCode,
d: MRef,
swap3: bool,
out: GroupIx,
},
Scan {
op: BinCode,
a: MRef,
init: f64,
inclusive: bool,
reverse: bool,
row: u32,
post: u32,
carry: u32,
out: GroupIx,
},
Load {
a: MRef,
src: GroupIx,
out: GroupIx,
},
}
#[derive(Clone, Debug)]
pub(crate) struct FusedInput {
pub src: SrcRef,
pub shifted_ix: Option<GroupIx>,
pub src_shape: DimU,
pub elem_stride: i64,
pub load_reg: GroupIx,
pub index: Option<FoldedIndex>,
pub gather: Option<Box<ChunkGather>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct FoldedIndex {
pub by: GroupIx,
pub n: usize,
pub s_data: usize,
pub base: usize,
pub s_inner: usize,
pub period: usize,
pub src_len: usize,
}
impl FoldedIndex {
pub(crate) const IN_GROUP: GroupIx = GroupIx::MAX;
pub(crate) fn preloaded(&self) -> bool {
self.by != Self::IN_GROUP
}
#[inline]
pub(crate) fn at(&self, p: usize, k: usize) -> usize {
p * self.s_data + self.base + self.s_inner * (k % self.period)
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ChunkGather {
pub segs: SmallVec<[SmallVec<[(usize, usize, usize); 2]>; 4]>,
pub strides: SmallVec<[i64; 4]>,
pub shape: DimU,
pub whole: bool,
}
impl ChunkGather {
#[inline(always)]
fn coord(&self, a: usize, i: usize) -> Option<usize> {
self.segs[a]
.iter()
.find(|&&(o, l, _)| o <= i && i < o + l)
.map(|&(o, _, so)| so + (i - o))
}
pub(crate) fn src_offset(&self, flat: usize) -> Option<i64> {
let mut rest = flat;
let mut off = 0i64;
for a in (0..self.shape.len()).rev() {
let i = rest % self.shape[a];
rest /= self.shape[a];
off += self.strides[a] * self.coord(a, i)? as i64;
}
Some(off)
}
#[inline(always)]
pub(crate) unsafe fn fill(&self, src: *const f64, at: usize, c: usize, dst: *mut f64) {
if let Some(delta) = self.flat_shift() {
unsafe { self.fill_shifted(delta, src, at, c, dst) };
return;
}
let nd = self.shape.len();
let last = self.shape[nd - 1];
let s_last = self.strides[nd - 1];
let mut idx: SmallVec<[usize; 4]> = SmallVec::from_elem(0, nd - 1);
let mut part: SmallVec<[Option<i64>; 4]> = SmallVec::from_elem(None, nd - 1);
let mut rest = at / last;
for a in (0..nd - 1).rev() {
idx[a] = rest % self.shape[a];
rest /= self.shape[a];
part[a] = self.coord(a, idx[a]).map(|x| self.strides[a] * x as i64);
}
let mut col = at % last;
let mut k = 0usize;
while k < c {
let len = (c - k).min(last - col);
let off = part.iter().try_fold(0i64, |o, p| p.map(|p| o + p));
unsafe {
let d = std::slice::from_raw_parts_mut(dst.add(k), len);
match off {
None => d.fill(0.0),
Some(off) => {
let mut pos = col;
for &(o, l, so) in &self.segs[nd - 1] {
let lo = o.max(col);
let hi = (o + l).min(col + len);
if lo >= hi {
continue;
}
if lo > pos {
d[pos - col..lo - col].fill(0.0);
}
let s = src.offset((off + s_last * (so + lo - o) as i64) as isize);
let out = &mut d[lo - col..hi - col];
if s_last == 1 {
out.copy_from_slice(std::slice::from_raw_parts(s, hi - lo));
} else {
for (j, x) in out.iter_mut().enumerate() {
*x = *s.offset(j as isize * s_last as isize);
}
}
pos = pos.max(hi);
}
if pos < col + len {
d[pos - col..].fill(0.0);
}
}
}
}
k += len;
col = 0;
let mut a = nd - 1;
while a > 0 {
a -= 1;
idx[a] += 1;
if idx[a] < self.shape[a] {
part[a] = self.coord(a, idx[a]).map(|x| self.strides[a] * x as i64);
break;
}
idx[a] = 0;
part[a] = self.coord(a, 0).map(|x| self.strides[a] * x as i64);
}
}
}
#[inline(always)]
fn flat_shift(&self) -> Option<i64> {
let mut stride = 1i64;
let mut delta = 0i64;
for a in (0..self.shape.len()).rev() {
let &[(o, l, so)] = &self.segs[a][..] else {
return None;
};
if self.strides[a] != stride || l == 0 {
return None;
}
delta += stride * (so as i64 - o as i64);
stride *= self.shape[a] as i64;
}
Some(delta)
}
#[inline(always)]
unsafe fn fill_shifted(&self, delta: i64, src: *const f64, at: usize, c: usize, dst: *mut f64) {
let nd = self.shape.len();
let (mut first, mut last, mut stride) = (0usize, 0usize, 1usize);
for a in (0..nd).rev() {
let (o, l, _) = self.segs[a][0];
first += stride * o;
last += stride * (o + l - 1);
stride *= self.shape[a];
}
let end = at + c;
let (lo, hi) = (at.max(first), end.min(last + 1));
if lo < hi {
unsafe {
std::ptr::copy_nonoverlapping(
src.offset(lo as isize + delta as isize),
dst.add(lo - at),
hi - lo,
)
};
}
let mut inner = 1usize;
for a in (0..nd).rev() {
let (o, l, _) = self.segs[a][0];
let n = self.shape[a];
let blk = n * inner;
if o > 0 || o + l < n {
let gaps = [(0, o * inner), ((o + l) * inner, blk)];
let mut q = at - at % blk;
while q < end {
for &(g0, g1) in &gaps {
let (s, e) = ((q + g0).max(at), (q + g1).min(end));
if s >= e {
continue;
}
unsafe {
if e - s == 1 {
*dst.add(s - at) = 0.0;
} else {
std::slice::from_raw_parts_mut(dst.add(s - at), e - s).fill(0.0);
}
}
}
q += blk;
}
}
inner = blk;
}
}
}
pub(crate) const GHOST_OFF: i64 = i64::MIN;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct FusedRun {
pub out_off: u32,
pub len: u32,
pub in_off: SmallVec<[i64; 2]>,
}
impl FusedRun {
pub(crate) fn stepped(&self, k: i64, out_step: i64, in_step: &[i64]) -> FusedRun {
FusedRun {
out_off: (self.out_off as i64 + k * out_step) as u32,
len: self.len,
in_off: self
.in_off
.iter()
.zip(in_step)
.map(|(&o, &s)| if o == GHOST_OFF { o } else { o + k * s })
.collect(),
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum RunNode {
Run(FusedRun),
Repeat {
count: u32,
body: u32,
out_step: i64,
in_step: SmallVec<[i64; 2]>,
},
}
#[derive(Clone, Debug)]
pub(crate) struct RunSchedule {
pub nodes: Vec<RunNode>,
pub n_runs: usize,
pub depth: usize,
}
impl RunSchedule {
pub(crate) fn whole(n_elems: usize) -> Self {
RunSchedule {
nodes: vec![RunNode::Run(FusedRun {
out_off: 0,
len: n_elems as u32,
in_off: SmallVec::new(),
})],
n_runs: 1,
depth: 0,
}
}
pub(crate) fn for_each_run(&self, mut f: impl FnMut(&FusedRun)) {
fn walk(nodes: &[RunNode], out_d: i64, in_d: &[i64], f: &mut impl FnMut(&FusedRun)) {
let mut i = 0usize;
while i < nodes.len() {
match &nodes[i] {
RunNode::Run(r) => {
f(&r.stepped(1, out_d, in_d));
i += 1;
}
RunNode::Repeat {
count,
body,
out_step,
in_step,
} => {
let body_nodes = &nodes[i + 1..i + 1 + *body as usize];
for k in 0..*count as i64 {
let d: SmallVec<[i64; 2]> =
in_d.iter().zip(in_step).map(|(&a, &s)| a + k * s).collect();
walk(body_nodes, out_d + k * out_step, &d, f);
}
i += 1 + *body as usize;
}
}
}
}
let n_in = self.nodes.iter().find_map(|n| match n {
RunNode::Run(r) => Some(r.in_off.len()),
RunNode::Repeat { .. } => None,
});
let zeros: SmallVec<[i64; 2]> = SmallVec::from_elem(0, n_in.unwrap_or(0));
walk(&self.nodes, 0, &zeros, &mut f);
}
pub(crate) fn expanded(&self) -> Vec<FusedRun> {
let mut v = Vec::with_capacity(self.n_runs);
self.for_each_run(|r| v.push(r.clone()));
v
}
}
#[derive(Clone, Debug)]
pub(crate) struct FusedSpec {
pub shape: DimU,
pub inputs: Vec<FusedInput>,
pub scalars: Vec<Operand>,
pub micro: Vec<MicroOp>,
pub n_regs: GroupIx,
pub n_load_regs: GroupIx,
pub n_splat_regs: GroupIx,
pub outputs: SmallVec<[(GroupIx, SlotId); 2]>,
pub direct: SmallVec<[(u32, u32); 2]>,
pub scan_fuse: SmallVec<[ScanFuse; 1]>,
pub schedule: RunSchedule,
pub reduce: Option<FusedReduce>,
pub interleave: Option<Vec<FusedRun>>,
pub pre: Option<PreReduce>,
pub n_fused_instrs: u32,
pub n_folded_gathers: u32,
}
#[derive(Clone, Debug)]
pub(crate) struct PreReduce {
pub op: BinCode,
pub init: f64,
pub src: SrcRef,
pub rows: usize,
pub n_inner: usize,
pub out: SlotId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ScanFuse {
pub scan: u32,
pub pre: bool,
pub post: bool,
pub keep: bool,
}
#[derive(Clone, Debug)]
pub(crate) struct FusedReduce {
pub reg: GroupIx,
pub op: BinCode,
pub init: f64,
pub out: SlotId,
pub n_inner: usize,
}
impl FusedSpec {
pub(crate) fn n_elems(&self) -> usize {
self.shape.iter().product::<usize>().max(1)
}
pub(crate) fn rows_reversed(&self) -> Option<(usize, usize)> {
self.micro.iter().find_map(|m| match m {
MicroOp::Scan {
reverse: true,
row,
post,
..
} => Some((*row as usize, *post as usize)),
_ => None,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct FuseStats {
pub instrs_before: usize,
pub instrs_after: usize,
pub n_groups: usize,
pub n_member_instrs: usize,
pub n_gathers_folded: usize,
pub n_gathers_kept: usize,
pub n_reduces_folded: usize,
pub n_scans_folded: usize,
pub group_size_hist: [usize; 6],
pub micro_hist: Vec<(String, usize)>,
pub adj_hist: Vec<(String, usize)>,
pub chain_hist: Vec<(usize, usize)>,
}
#[derive(Clone, Debug)]
pub(crate) struct SlotDesc {
pub shape: DimU,
pub origin: DimI,
pub scalar: bool,
pub cadence: Cadence,
pub storage: u32,
}
impl SlotDesc {
pub(crate) fn elems(&self) -> usize {
if self.scalar {
1
} else {
self.shape.iter().copied().product::<usize>().max(1)
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct GatherPlan {
pub fixed_desc: SmallVec<[(usize, usize); 4]>,
pub perm: SmallVec<[usize; 4]>,
pub mapped: SmallVec<[bool; 4]>,
pub segs: SmallVec<[SmallVec<[(usize, usize, usize); 2]>; 4]>,
pub shape: DimU,
pub origin: DimI,
pub src_shape: DimU,
pub src_origin: DimI,
}
#[derive(Clone, Debug)]
pub(crate) struct ConstArrayData {
pub shape: DimU,
pub values: Vec<f64>,
}
pub(crate) const GATHER_GHOST: u32 = u32::MAX;
#[derive(Clone, Debug)]
pub(crate) struct GatherTable {
pub src_shape: DimU,
pub pos: Vec<u32>,
}
#[derive(Clone, Debug)]
pub(crate) struct SegTable {
pub rows: Vec<u32>,
}
#[derive(Clone, Debug)]
pub(crate) struct SweepSpec {
pub var: String,
pub out: SlotId,
pub coords: SmallVec<[SlotId; 4]>,
pub shape: DimU,
pub axis: u8,
pub body_len: u32,
pub result: Operand,
}
impl SweepSpec {
pub(crate) fn n_cells(&self) -> usize {
self.shape.iter().product()
}
fn order(&self) -> impl DoubleEndedIterator<Item = usize> + '_ {
let axis = self.axis as usize;
std::iter::once(axis).chain((0..self.shape.len()).filter(move |&d| d != axis))
}
pub(crate) fn advance(&self, cell: &mut [usize]) -> bool {
for d in self.order().rev() {
cell[d] += 1;
if cell[d] < self.shape[d] {
return true;
}
cell[d] = 0;
}
false
}
pub(crate) fn ordinal(&self, cell: &[usize]) -> usize {
self.order().fold(0, |o, d| o * self.shape[d] + cell[d])
}
pub(crate) fn flat(&self, cell: &[usize]) -> usize {
(0..self.shape.len()).fold(0, |o, d| o * self.shape[d] + cell[d])
}
}
pub(crate) type FaultOrderKey = SmallVec<[i64; 12]>;
#[derive(Clone, Debug)]
pub(crate) struct FaultScanSpec {
pub rule: u32,
pub cands: Vec<FaultCand>,
}
#[derive(Clone, Debug)]
pub(crate) struct FaultCand {
pub prefix: FaultOrderKey,
pub cells: bool,
pub shape: DimU,
pub lo: DimI,
pub visit: SmallVec<[u8; 4]>,
pub cvals: SmallVec<[i64; 4]>,
pub seq: i64,
pub path: Vec<PathCond>,
pub what: FaultWhat,
}
#[derive(Clone, Debug)]
pub(crate) struct PathCond {
pub op: Operand,
pub want: bool,
pub array: bool,
}
#[derive(Clone, Debug)]
pub(crate) enum FaultWhat {
Every(u32),
ConstOob {
name: String,
dims: Vec<OobDim>,
},
}
#[derive(Clone, Debug)]
pub(crate) struct OobDim {
pub n: i64,
pub error: bool,
pub sub: OobSub,
}
#[derive(Clone, Debug)]
pub(crate) enum OobSub {
Fixed(i64),
Axis { axis: u8, raw: Vec<i64> },
Data { op: Operand, array: bool },
}
impl OobDim {
fn raw_at(
&self,
shape: &[usize],
cell: &[usize],
read: &mut impl FnMut(&Operand, Option<(&[usize], &[usize])>) -> f64,
) -> i64 {
match &self.sub {
OobSub::Fixed(r) => *r,
OobSub::Axis { axis, raw } => raw[cell[*axis as usize]],
OobSub::Data { op, array } => subscript_of(read(op, array.then_some((shape, cell)))),
}
}
fn faults(&self, raw: i64) -> bool {
self.error && (raw < 1 || raw > self.n)
}
}
impl FaultCand {
fn message_at(
&self,
cell: &[usize],
faults: &[String],
read: &mut impl FnMut(&Operand, Option<(&[usize], &[usize])>) -> f64,
) -> Option<String> {
match &self.what {
FaultWhat::Every(m) => Some(faults[*m as usize].clone()),
FaultWhat::ConstOob { name, dims } => dims.iter().enumerate().find_map(|(d, od)| {
let raw = od.raw_at(&self.shape, cell, read);
od.faults(raw)
.then(|| crate::simulate_array::eval::const_oob_message(name, raw, od.n, d))
}),
}
}
fn faulting_positions(&self) -> Option<Vec<Vec<bool>>> {
let FaultWhat::ConstOob { dims, .. } = &self.what else {
return None;
};
let mut at: Vec<Vec<bool>> = self.shape.iter().map(|&n| vec![false; n]).collect();
for od in dims {
match &od.sub {
OobSub::Fixed(r) if od.faults(*r) => return None,
OobSub::Fixed(_) => {}
OobSub::Axis { axis, raw } => {
for (p, &r) in raw.iter().enumerate() {
if od.faults(r) {
at[*axis as usize][p] = true;
}
}
}
OobSub::Data { .. } if od.error => return None,
OobSub::Data { .. } => {}
}
}
Some(at)
}
fn key(&self, cell: Option<&[usize]>) -> FaultOrderKey {
let mut key = self.prefix.clone();
if let Some(cell) = cell {
for &a in &self.visit {
key.push(self.lo[a as usize] + cell[a as usize] as i64);
}
}
key.extend(self.cvals.iter().copied());
key.push(self.seq);
key
}
fn first(
&self,
faults: &[String],
cond: &mut impl FnMut(&Operand, Option<(&[usize], &[usize])>) -> f64,
) -> Option<(FaultOrderKey, String)> {
for pc in self.path.iter().filter(|pc| !pc.array) {
if (cond(&pc.op, None) != 0.0) != pc.want {
return None;
}
}
if !self.cells {
let msg = self.message_at(&[], faults, cond)?;
return Some((self.key(None), msg));
}
if self.shape.contains(&0) {
return None;
}
let pos = self.faulting_positions();
let rank = self.shape.len();
let mut cell: SmallVec<[usize; 4]> = SmallVec::from_elem(0, rank);
let mut found = None;
self.walk(0, false, &pos, &mut cell, &mut |c| {
if !self
.path
.iter()
.filter(|pc| pc.array)
.all(|pc| (cond(&pc.op, Some((&self.shape, c))) != 0.0) == pc.want)
{
return false;
}
match self.message_at(c, faults, cond) {
Some(m) => {
found = Some((self.key(Some(c)), m));
true
}
None => false,
}
});
found
}
fn walk(
&self,
level: usize,
hit: bool,
pos: &Option<Vec<Vec<bool>>>,
cell: &mut SmallVec<[usize; 4]>,
f: &mut impl FnMut(&[usize]) -> bool,
) -> bool {
if level == self.visit.len() {
return f(cell);
}
let a = self.visit[level] as usize;
let last = level + 1 == self.visit.len();
for p in 0..self.shape[a] {
let here = match pos {
None => true,
Some(at) => hit || at[a][p],
};
if last && !here {
continue;
}
cell[a] = p;
if self.walk(level + 1, here, pos, cell, f) {
return true;
}
}
false
}
}
pub(crate) fn slab_flat(shape: &[usize], cell: &[usize], col_major: bool) -> usize {
if col_major {
(0..shape.len())
.rev()
.fold(0, |o, d| o * shape[d] + cell[d])
} else {
(0..shape.len()).fold(0, |o, d| o * shape[d] + cell[d])
}
}
impl FaultScanSpec {
pub(crate) fn first(
&self,
faults: &[String],
mut cond: impl FnMut(&Operand, Option<(&[usize], &[usize])>) -> f64,
) -> Option<(FaultOrderKey, String)> {
self.cands
.iter()
.filter_map(|c| c.first(faults, &mut cond))
.min_by(|a, b| a.0.cmp(&b.0))
}
}
#[derive(Clone, Debug)]
pub(crate) enum ScalarReadKind {
SelfRead { sweep: u32 },
ZeroGhost,
ConstArray {
name: String,
policy: SmallVec<[crate::value_invention::BoundaryKind; 4]>,
},
}
#[derive(Clone, Debug)]
pub(crate) struct ScalarReadSpec {
pub subs: SmallVec<[Operand; 4]>,
pub kind: ScalarReadKind,
}
pub(crate) enum ScalarReadAt {
Elem(SmallVec<[usize; 4]>),
Ghost,
Fault(String),
}
impl ScalarReadSpec {
pub(crate) fn resolve(
&self,
raw: &[i64],
src_shape: &[usize],
sweeps: &[SweepSpec],
cur: &[usize],
) -> ScalarReadAt {
match &self.kind {
ScalarReadKind::SelfRead { sweep } => {
let sw = &sweeps[*sweep as usize];
let mut cell: SmallVec<[usize; 4]> = SmallVec::new();
for (d, &r) in raw.iter().enumerate() {
if r < 1 || r > sw.shape[d] as i64 {
return ScalarReadAt::Fault(
crate::simulate_array::eval::recur_unavailable_message(&sw.var, raw),
);
}
cell.push((r - 1) as usize);
}
if sw.ordinal(&cell) >= sw.ordinal(cur) {
return ScalarReadAt::Fault(
crate::simulate_array::eval::recur_unavailable_message(&sw.var, raw),
);
}
ScalarReadAt::Elem(cell)
}
ScalarReadKind::ZeroGhost => {
let mut ix: SmallVec<[usize; 4]> = SmallVec::new();
for (d, &r) in raw.iter().enumerate() {
if r < 1 || r > src_shape[d] as i64 {
return ScalarReadAt::Ghost;
}
ix.push((r - 1) as usize);
}
ScalarReadAt::Elem(ix)
}
ScalarReadKind::ConstArray { name, policy } => {
use crate::value_invention::BoundaryKind;
let mut ix: SmallVec<[usize; 4]> = SmallVec::new();
for (d, &r) in raw.iter().enumerate() {
let n = src_shape[d] as i64;
let at = if (1..=n).contains(&r) {
Some((r - 1) as usize)
} else {
match policy[d] {
BoundaryKind::Periodic if n >= 1 => {
Some((r - 1).rem_euclid(n) as usize)
}
BoundaryKind::Clamp if n >= 1 => Some((r.clamp(1, n) - 1) as usize),
_ => None,
}
};
match at {
Some(i) => ix.push(i),
None => {
return ScalarReadAt::Fault(
crate::simulate_array::eval::const_oob_message(name, r, n, d),
);
}
}
}
ScalarReadAt::Elem(ix)
}
}
}
}
#[inline]
pub(crate) fn subscript_of(v: f64) -> i64 {
let i = v as i64;
if i as f64 == v {
return i;
}
if v.abs() < 4.0e15 {
let f = v - i as f64;
if f >= 0.5 {
i + 1
} else if f <= -0.5 {
i - 1
} else {
i
}
} else {
v.round() as i64
}
}
pub(crate) struct SlotTables<'a> {
pub dy_writes: &'a [DyWrite],
pub fused: &'a [FusedSpec],
pub assemblies: &'a [AssembleSpec],
pub sweeps: &'a [SweepSpec],
pub scalar_reads: &'a [ScalarReadSpec],
pub data_gathers: &'a [DataGatherSpec],
pub lanes: &'a [LaneSpec],
pub fault_scans: &'a [FaultScanSpec],
}
#[derive(Clone, Debug)]
pub(crate) struct ForcingRef {
pub name: String,
pub shape: DimU,
}
pub(crate) const CALENDAR_FNS: [&str; 9] = [
"datetime.year",
"datetime.month",
"datetime.day",
"datetime.hour",
"datetime.minute",
"datetime.second",
"datetime.day_of_year",
"datetime.julian_day",
"datetime.is_leap_year",
];
pub(crate) fn calendar_at(func: u8, t_utc: f64) -> f64 {
use crate::registered_functions::{ClosedArg, evaluate_closed_function};
evaluate_closed_function(CALENDAR_FNS[func as usize], &[ClosedArg::Scalar(t_utc)])
.map(|v| v.as_f64())
.unwrap_or(f64::NAN)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum InterpKind {
Linear,
Bilinear,
SearchSorted,
}
impl InterpKind {
pub(crate) fn from_name(name: &str) -> Option<InterpKind> {
match name {
"interp.linear" => Some(InterpKind::Linear),
"interp.bilinear" => Some(InterpKind::Bilinear),
"interp.searchsorted" => Some(InterpKind::SearchSorted),
_ => None,
}
}
pub(crate) fn name(self) -> &'static str {
match self {
InterpKind::Linear => "interp.linear",
InterpKind::Bilinear => "interp.bilinear",
InterpKind::SearchSorted => "interp.searchsorted",
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct InterpTable {
pub kind: InterpKind,
pub table: Vec<f64>,
pub axis_x: Vec<f64>,
pub axis_y: Vec<f64>,
pub strict: Option<String>,
}
impl InterpTable {
pub(crate) fn at(&self, x: f64, y: f64) -> f64 {
use crate::registered_functions::{interp_bilinear_at, interp_linear_at, searchsorted_at};
if let Some(fault) = self.out_of_bounds(x, y) {
crate::simulate_array::eval::latch_gather_fault(fault);
return f64::NAN;
}
match self.kind {
InterpKind::Linear => interp_linear_at(&self.table, &self.axis_x, x),
InterpKind::Bilinear => {
interp_bilinear_at(&self.table, &self.axis_x, &self.axis_y, x, y)
}
InterpKind::SearchSorted => searchsorted_at(x, &self.axis_x) as f64,
}
}
pub(crate) fn out_of_bounds(&self, x: f64, y: f64) -> Option<String> {
use crate::lower_table_lookup::out_of_bounds_fault;
let id = self.strict.as_deref()?;
out_of_bounds_fault(id, 1, &self.axis_x, x).or_else(|| match self.kind {
InterpKind::Bilinear => out_of_bounds_fault(id, 2, &self.axis_y, y),
InterpKind::Linear | InterpKind::SearchSorted => None,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum RingSel {
Fixed(usize),
Axis { axis: u8, off: i64 },
List { axis: u8, list: u32 },
}
#[derive(Clone, Debug)]
pub(crate) struct RingRef {
pub src_shape: DimU,
pub sel: SmallVec<[RingSel; 3]>,
pub lists: Vec<Vec<u32>>,
}
impl RingRef {
pub(crate) fn axes(&self) -> SmallVec<[u8; 3]> {
let mut v: SmallVec<[u8; 3]> = SmallVec::new();
for s in &self.sel {
if let RingSel::Axis { axis, .. } | RingSel::List { axis, .. } = s
&& !v.contains(axis)
{
v.push(*axis);
}
}
v
}
}
#[derive(Clone, Debug)]
pub(crate) struct GeomSpec {
pub manifold: crate::geometry::Manifold,
pub a: RingRef,
pub b: RingRef,
pub shape: DimU,
pub pairs: Option<(u8, u8)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum GatherAxis {
Data,
Affine { axis: u8, off: i64 },
Fixed(usize),
}
#[derive(Clone, Debug)]
pub(crate) struct IndexGatherSpec {
pub axes: SmallVec<[GatherAxis; 4]>,
pub src_shape: DimU,
pub shape: DimU,
}
impl IndexGatherSpec {
pub(crate) fn data_axis(&self) -> usize {
self.axes
.iter()
.position(|a| *a == GatherAxis::Data)
.expect("an IndexGather has one data axis")
}
}
#[inline]
pub(crate) fn data_subscript(v: f64, n: usize) -> Option<usize> {
if n as u64 >= 1 << 52 {
let one_based = subscript_of(v);
return (one_based >= 1 && one_based <= n as i64).then(|| (one_based - 1) as usize);
}
if v >= 0.5 && v < n as f64 + 0.5 {
Some(unsafe { (v + 0.5).to_int_unchecked::<i64>() } as usize - 1)
} else {
None
}
}
#[derive(Clone, Debug)]
pub(crate) struct FaultOrder {
pub rule: u32,
pub head: SmallVec<[i64; 12]>,
pub axes: SmallVec<[u8; 4]>,
pub lo: DimI,
pub tail: SmallVec<[i64; 8]>,
}
impl FaultOrder {
pub(crate) fn key(&self, cell: &[usize]) -> SmallVec<[i64; 12]> {
let mut k = self.head.clone();
for &a in &self.axes {
k.push(self.lo[a as usize] + cell[a as usize] as i64);
}
k.extend(self.tail.iter().copied());
k
}
}
#[derive(Clone, Debug)]
pub(crate) struct DataGatherSpec {
pub subs: SmallVec<[Operand; 4]>,
pub kind: ScalarReadKind,
pub src_shape: DimU,
pub shape: DimU,
pub order: Option<FaultOrder>,
pub reversed: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum DataAt {
Elem(usize),
Ghost,
Fault { d: usize, raw: i64 },
}
impl DataGatherSpec {
#[inline]
pub(crate) fn at(&self, v: impl Fn(usize) -> f64) -> DataAt {
use crate::value_invention::BoundaryKind;
let mut flat = 0usize;
let mut ghost = false;
let mut fault: Option<(usize, i64)> = None;
let m = self.src_shape.len();
for (d, &n) in self.src_shape.iter().enumerate() {
let r = subscript_of(v(d));
let n_i = n as i64;
let i = if r >= 1 && r <= n_i {
(r - 1) as usize
} else {
match &self.kind {
ScalarReadKind::ConstArray { policy, .. } => match policy[d] {
BoundaryKind::Periodic if n_i >= 1 => (r - 1).rem_euclid(n_i) as usize,
BoundaryKind::Clamp if n_i >= 1 => (r.clamp(1, n_i) - 1) as usize,
_ => {
let logical = if self.reversed { m - 1 - d } else { d };
if fault.is_none_or(|(l, _)| logical < l) {
fault = Some((logical, r));
}
0
}
},
_ => {
ghost = true;
0
}
}
};
flat = flat * n + i;
}
match fault {
Some((d, raw)) => DataAt::Fault { d, raw },
None if ghost => DataAt::Ghost,
None => DataAt::Elem(flat),
}
}
pub(crate) fn fault_message(&self, d: usize, raw: i64) -> String {
let name = match &self.kind {
ScalarReadKind::ConstArray { name, .. } => name.as_str(),
_ => unreachable!("only a const-array gather faults"),
};
let m = self.src_shape.len();
let stored = if self.reversed { m - 1 - d } else { d };
let n = self.src_shape[stored] as i64;
crate::simulate_array::eval::const_oob_message(name, raw, n, d)
}
pub(crate) fn first_fault(
&self,
v: impl Fn(usize, usize) -> f64,
) -> Option<(SmallVec<[i64; 12]>, String)> {
let order = self.order.as_ref()?;
let nd = self.shape.len();
let n: usize = self.shape.iter().product();
let mut cell: DimU = DimU::from_elem(0, nd);
let mut best: Option<(SmallVec<[i64; 12]>, usize, i64)> = None;
for k in 0..n {
if let DataAt::Fault { d, raw } = self.at(|d| v(d, k)) {
let key = order.key(&cell);
if best.as_ref().is_none_or(|(b, _, _)| key < *b) {
best = Some((key, d, raw));
}
}
let mut a = nd;
while a > 0 {
a -= 1;
cell[a] += 1;
if cell[a] < self.shape[a] {
break;
}
cell[a] = 0;
}
}
best.map(|(key, d, raw)| (key, self.fault_message(d, raw)))
}
}
#[derive(Clone, Debug)]
pub(crate) struct RegionSpec {
pub dest_lo: DimU,
pub shape: DimU,
}
#[derive(Clone, Debug)]
pub(crate) struct AssembleSpec {
pub parts: Vec<(Operand, u32)>,
}
#[derive(Clone, Debug)]
pub(crate) struct StateRef {
pub name: String,
pub shape: DimU,
pub origin: DimI,
pub flat_offset: usize,
}
#[derive(Clone, Debug)]
pub(crate) struct DyWrite {
pub slot: SlotId,
pub var: u32,
pub dest_lo: SmallVec<[usize; 4]>,
pub scalar_flat: Option<usize>,
pub scatter: Option<Vec<usize>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum LaneKind {
State,
Param,
Slot,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum LaneIx {
Affine { base: u32, step: u32 },
Table(Vec<u32>),
}
impl LaneIx {
pub(crate) fn of(ix: Vec<u32>) -> Self {
if let [b, n, ..] = ix[..]
&& n >= b
{
let step = n - b;
if ix
.iter()
.enumerate()
.all(|(l, &v)| u64::from(b) + l as u64 * u64::from(step) == u64::from(v))
{
return LaneIx::Affine { base: b, step };
}
}
LaneIx::Table(ix)
}
#[inline(always)]
pub(crate) fn at(&self, l: usize) -> u32 {
match self {
LaneIx::Affine { base, step } => base + l as u32 * step,
LaneIx::Table(t) => t[l],
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct LaneTable {
pub kind: LaneKind,
pub ix: LaneIx,
}
#[derive(Clone, Debug)]
pub(crate) enum LaneDst {
Dy(LaneIx),
Slot(SlotId),
}
#[derive(Clone, Debug)]
pub(crate) struct LaneWrite {
pub src: MRef,
pub dst: LaneDst,
}
#[derive(Clone, Debug)]
pub(crate) struct LaneSpec {
pub lanes: u32,
pub inputs: Vec<LaneTable>,
pub scalars: Vec<Operand>,
pub micro: Vec<MicroOp>,
pub n_regs: GroupIx,
pub writes: Vec<LaneWrite>,
pub direct: Vec<(u32, u32)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum RuleKind {
Observed(usize),
Rhs(usize),
}
#[derive(Clone, Debug)]
pub(crate) struct RuleInfo {
pub name: String,
pub kind: RuleKind,
pub cadence: Cadence,
}
#[derive(Clone, Debug)]
pub(crate) struct DeclinedTape {
pub rules: Vec<RuleInfo>,
pub reasons: Vec<Option<String>>,
}
#[derive(Clone, Debug)]
pub(crate) struct StorageDesc {
pub elems: usize,
pub offset: usize,
pub cadence: Cadence,
pub dedicated: bool,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct SlabLayout {
pub storages: Vec<StorageDesc>,
pub total_elems: usize,
pub const_elems: usize,
pub segment_elems: usize,
pub recycled_elems: usize,
}
pub(crate) struct TapeProgram {
pub instrs: Vec<Instr>,
pub precision: Vec<crate::precision::Precision>,
pub n_const: u32,
pub n_segment: u32,
pub n_rhs: u32,
pub slots: Vec<SlotDesc>,
pub plans: Vec<GatherPlan>,
pub regions: Vec<RegionSpec>,
pub assemblies: Vec<AssembleSpec>,
pub const_data: Vec<ConstArrayData>,
pub interp_tables: Vec<InterpTable>,
pub geoms: Vec<GeomSpec>,
pub index_gathers: Vec<IndexGatherSpec>,
pub gather_tables: Vec<GatherTable>,
pub seg_tables: Vec<SegTable>,
pub faults: Vec<String>,
pub fault_order: Vec<(u32, SmallVec<[i64; 12]>)>,
pub data_gathers: Vec<DataGatherSpec>,
pub fault_scans: Vec<FaultScanSpec>,
pub sweeps: Vec<SweepSpec>,
pub scalar_reads: Vec<ScalarReadSpec>,
pub state_vars: Vec<StateRef>,
pub forcings: Vec<ForcingRef>,
pub dy_writes: Vec<DyWrite>,
pub exports: Vec<(String, SlotId)>,
pub rules: Vec<RuleInfo>,
pub slab: SlabLayout,
pub provenance: Vec<u32>,
pub params_len: usize,
pub fused: Vec<FusedSpec>,
pub lanes: Vec<LaneSpec>,
pub fuse_stats: FuseStats,
pub col_major: bool,
}
impl TapeProgram {
pub(crate) fn tables(&self) -> SlotTables<'_> {
SlotTables {
dy_writes: &self.dy_writes,
fused: &self.fused,
assemblies: &self.assemblies,
sweeps: &self.sweeps,
scalar_reads: &self.scalar_reads,
data_gathers: &self.data_gathers,
lanes: &self.lanes,
fault_scans: &self.fault_scans,
}
}
pub(crate) fn section_range(&self, c: Cadence) -> std::ops::Range<usize> {
let nc = self.n_const as usize;
let ns = self.n_segment as usize;
match c {
Cadence::Const => 0..nc,
Cadence::Segment => nc..nc + ns,
Cadence::Continuous => nc + ns..self.instrs.len(),
}
}
pub(crate) fn guarded_positions(&self) -> Vec<bool> {
let mut guarded = vec![false; self.instrs.len()];
for (pc, ins) in self.instrs.iter().enumerate() {
let len = match ins {
Instr::JmpIfZero {
n_true, n_false, ..
} => (*n_true + *n_false) as usize,
Instr::Sweep { spec } => self.sweeps[*spec as usize].body_len as usize,
_ => 0,
};
let end = (pc + 1 + len).min(guarded.len());
guarded[pc + 1..end].fill(true);
}
guarded
}
pub(crate) fn section_of(&self, i: usize) -> Cadence {
if i < self.n_const as usize {
Cadence::Const
} else if i < (self.n_const + self.n_segment) as usize {
Cadence::Segment
} else {
Cadence::Continuous
}
}
}