use super::fused::{
FusedScratch, IndexTable, Window, dispatch_bin_kernel, dispatch_un_kernel, fill_whole,
resolve_fused, run_fused_window,
};
use super::kernels::{ew1, ew2, seg_reduce, table_gather};
use super::par::FusedWorkers;
use super::resolve::{Rv, SrcView};
use super::*;
const MIN_CHAIN_WORK_PER_WORKER: usize = 1 << 10;
const CHAIN_LEAD_WORK: usize = 1 << 13;
pub(in crate::simulate_array::tape) struct SegChain {
pub(super) start: usize,
pub(super) end: usize,
pub(super) seg: usize,
pub(super) fused: Option<u32>,
n_out: usize,
max_row: usize,
work: usize,
pub(super) private: bool,
id: usize,
pub(super) compact: Option<CompactPlan>,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct CompactPlan {
mask: SlotId,
table: u32,
pub(super) w: Option<SlotId>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Class {
Tuple,
Cell,
Any,
}
type Touches = Vec<(SlotId, Class, bool)>;
pub(super) fn seg_chains(prog: &TapeProgram, slot_off: &[usize]) -> Vec<SegChain> {
let cont = (prog.n_const + prog.n_segment) as usize;
let guarded = prog.guarded_positions();
let tables = prog.tables();
let mut per_call = vec![false; prog.slots.len()];
for ins in &prog.instrs[cont..] {
ins.for_each_def(&tables, |s| per_call[s as usize] = true);
}
let mut out = Vec::new();
for seg in cont..prog.instrs.len() {
if let Some(mut ch) = plan_one(prog, slot_off, cont, seg, &guarded) {
ch.id = out.len();
ch.compact = compact_plan(prog, &ch, &per_call);
out.push(ch);
}
}
out
}
fn compact_plan(prog: &TapeProgram, ch: &SegChain, per_call: &[bool]) -> Option<CompactPlan> {
let Instr::SegReduce {
op: BinCode::Add,
src,
mask: Some(mask),
..
} = &prog.instrs[ch.seg]
else {
return None;
};
let [
Instr::TableGather { table, out: t1, .. },
Instr::Bin {
op: BinCode::Mul,
a,
b,
out: t2,
},
] = &prog.instrs[ch.start..ch.seg]
else {
return None;
};
if !ch.private || t2 != src || per_call[*mask as usize] {
return None;
}
let is_t1 = |o: &Operand| matches!(o, Operand::Slot(s) if s == t1);
let w = match (is_t1(a), is_t1(b)) {
(true, false) => b,
(false, true) => a,
_ => return None,
};
let w = match w {
Operand::Lit(_) | Operand::Param(_) | Operand::Time => None,
Operand::Slot(s) if prog.slots[*s as usize].scalar => None,
Operand::Slot(s) if !per_call[*s as usize] => Some(*s),
_ => return None,
};
Some(CompactPlan {
mask: *mask,
table: *table,
w,
})
}
fn plan_one(
prog: &TapeProgram,
slot_off: &[usize],
cont: usize,
seg: usize,
guarded: &[bool],
) -> Option<SegChain> {
let Instr::SegReduce {
src,
mask,
table,
out,
..
} = &prog.instrs[seg]
else {
return None;
};
let rows = &prog.seg_tables[*table as usize].rows;
let n_out = rows.len().checked_sub(1)?;
let n_pairs = *rows.last()? as usize;
let odesc = &prog.slots[*out as usize];
if n_out < 2 || odesc.scalar || odesc.elems() != n_out {
return None;
}
let tuple_slot = |s: SlotId| tuple_box(prog, s, n_pairs);
let mut touches: Touches = vec![(*src, Class::Tuple, false), (*out, Class::Cell, true)];
if !tuple_slot(*src) {
return None;
}
if let Some(m) = mask {
if !tuple_slot(*m) {
return None;
}
touches.push((*m, Class::Tuple, false));
}
let mut start = seg;
while start > cont && !guarded[start - 1] {
let mut t = Vec::new();
if !tuple_instr(prog, &prog.instrs[start - 1], n_pairs, &mut t) {
break;
}
touches.extend(t);
start -= 1;
}
let mut work = n_pairs * (seg - start + 1);
let mut fused = None;
if let Some(Instr::Fused { spec }) = prog.instrs.get(seg + 1)
&& !guarded[seg + 1]
{
let mut t = Vec::new();
let fs = &prog.fused[*spec as usize];
if cell_group(fs, n_out, &mut t) {
touches.extend(t);
fused = Some(*spec);
work += n_out * (fs.micro.len() + fs.inputs.len() + fs.outputs.len() + 1);
}
}
let end = seg + 1 + usize::from(fused.is_some());
if guarded[start..end].iter().any(|&g| g)
|| (start..end).any(|pc| prog.precision.get(pc).is_some())
|| !disjoint(prog, slot_off, &touches)
{
return None;
}
let tables = prog.tables();
let mut defs: Vec<SlotId> = Vec::new();
for ins in &prog.instrs[start..seg] {
ins.for_each_def(&tables, |s| defs.push(s));
}
let mut private = prog.exports.iter().all(|(_, s)| !defs.contains(s));
for (pc, ins) in prog.instrs.iter().enumerate() {
if !(start..=seg).contains(&pc) {
ins.for_each_read(&tables, |s| private &= !defs.contains(&s));
}
}
let max_row = rows.windows(2).map(|r| (r[1] - r[0]) as usize).max();
Some(SegChain {
start,
end,
seg,
fused,
n_out,
max_row: max_row.unwrap_or(0),
work,
private,
id: 0,
compact: None,
})
}
fn tuple_instr(prog: &TapeProgram, ins: &Instr, n_pairs: usize, t: &mut Touches) -> bool {
let tuple_slot = |s: SlotId| tuple_box(prog, s, n_pairs);
let operand = |op: &Operand, t: &mut Touches| match op {
Operand::Lit(_) | Operand::Param(_) | Operand::Time => true,
Operand::State(ix) => {
let sh = &prog.state_vars[*ix as usize].shape;
sh.is_empty() || sh[..] == [n_pairs]
}
Operand::Slot(s) if prog.slots[*s as usize].scalar => {
t.push((*s, Class::Any, false));
true
}
Operand::Slot(s) if tuple_slot(*s) => {
t.push((*s, Class::Tuple, false));
true
}
_ => false,
};
let out = match ins {
Instr::TableGather { src, out, .. } => {
match src {
SrcRef::Slot(s) => t.push((*s, Class::Any, false)),
SrcRef::State(_) => {}
}
*out
}
Instr::Bin { a, b, out, .. } => {
if !(operand(a, t) && operand(b, t)) {
return false;
}
*out
}
Instr::Un { a, out, .. } | Instr::Neg { a, out } => {
if !operand(a, t) {
return false;
}
*out
}
_ => return false,
};
if !tuple_slot(out) {
return false;
}
t.push((out, Class::Tuple, true));
true
}
fn tuple_box(prog: &TapeProgram, s: SlotId, n_pairs: usize) -> bool {
let d = &prog.slots[s as usize];
!d.scalar && d.shape[..] == [n_pairs]
}
fn cell_group(fs: &FusedSpec, n_out: usize, t: &mut Touches) -> bool {
if fs.n_elems() != n_out
|| fs.reduce.is_some()
|| fs.pre.is_some()
|| fs.interleave.is_some()
|| !fs.scan_fuse.is_empty()
|| fs.micro.iter().any(|m| matches!(m, MicroOp::Scan { .. }))
{
return false;
}
let mut aligned = vec![true; fs.inputs.len()];
for (i, inp) in fs.inputs.iter().enumerate() {
aligned[i] = inp.gather.is_none() && inp.index.is_none() && inp.src_shape == fs.shape;
if let Some(s) = inp.shifted_ix {
aligned[i] &= inp.elem_stride == 1;
let s = s as usize;
fs.schedule.for_each_run(|r| {
aligned[i] &= r.in_off.get(s).is_some_and(|&o| o == r.out_off as i64);
});
}
}
for (inp, al) in fs.inputs.iter().zip(aligned) {
if let SrcRef::Slot(s) = inp.src {
t.push((s, if al { Class::Cell } else { Class::Any }, false));
}
}
for &(_, s) in &fs.outputs {
t.push((s, Class::Cell, true));
}
true
}
fn disjoint(prog: &TapeProgram, slot_off: &[usize], touches: &Touches) -> bool {
let range = |s: SlotId| {
let off = slot_off[s as usize];
(off != usize::MAX).then(|| (off, off + prog.slots[s as usize].elems().max(1)))
};
for (i, &(s, cs, ws)) in touches.iter().enumerate() {
let Some((a0, a1)) = range(s) else { continue };
for &(r, cr, wr) in &touches[i + 1..] {
if !(ws || wr) {
continue;
}
let Some((b0, b1)) = range(r) else { continue };
if a0 >= b1 || b0 >= a1 {
continue;
}
if (a0, a1) != (b0, b1) || cs != cr || cs == Class::Any {
return false;
}
}
}
true
}
#[derive(Clone, Copy)]
enum Arg {
S(f64),
P(*const f64),
}
#[derive(Clone, Copy)]
enum Op {
Table {
dst: *mut f64,
src: *const f64,
pos: *const u32,
},
Bin {
op: BinCode,
dst: *mut f64,
a: Arg,
b: Arg,
},
Un {
op: UnCode,
dst: *mut f64,
a: Arg,
},
Neg {
dst: *mut f64,
a: Arg,
},
}
#[derive(Clone, Copy)]
struct SumOfProducts {
src: *const f64,
pos: *const u32,
t1: *mut f64,
t2: *mut f64,
w: Arg,
gather_first: bool,
zero_seed: bool,
store: bool,
}
impl SumOfProducts {
fn of(
ops: &[Op],
seg_op: BinCode,
init: f64,
seg_src: *const f64,
mask: Option<*const f64>,
private: bool,
) -> Option<Self> {
if seg_op != BinCode::Add || crate::precision::is_f32() {
return None;
}
let [
Op::Table { dst: t1, src, pos },
Op::Bin {
op: BinCode::Mul,
dst: t2,
a,
b,
},
] = *ops
else {
return None;
};
let is_t1 = |x: Arg| matches!(x, Arg::P(p) if std::ptr::eq(p, t1));
if !std::ptr::eq(t2, seg_src) || !(is_t1(a) || is_t1(b)) {
return None;
}
let gather_first = is_t1(a);
let w = if gather_first { b } else { a };
let reads_back = |p: *const f64| std::ptr::eq(p, t1) || std::ptr::eq(p, t2);
let read_back = matches!(w, Arg::P(p) if reads_back(p)) || mask.is_some_and(reads_back);
Some(SumOfProducts {
src,
pos,
t1,
t2,
w,
gather_first,
zero_seed: init.to_bits() == 0,
store: !private || read_back,
})
}
unsafe fn run(&self, rows: &[u32], mask: Option<*const f64>, init: f64, dst: *mut f64) {
let w = |k: usize| match self.w {
Arg::S(v) => v,
Arg::P(p) => unsafe { *p.add(k) },
};
for c in 0..rows.len() - 1 {
let mut acc = init;
for k in rows[c] as usize..rows[c + 1] as usize {
unsafe {
let p = *self.pos.add(k);
let g = if p == GATHER_GHOST {
0.0
} else {
*self.src.add(p as usize)
};
if self.store {
*self.t1.add(k) = g;
}
#[allow(clippy::if_same_then_else)]
let v = if self.gather_first {
g * w(k)
} else {
w(k) * g
};
if self.store {
*self.t2.add(k) = v;
}
match mask {
Some(m) if self.zero_seed => {
acc += if *m.add(k) != 0.0 { v } else { 0.0 };
}
Some(m) => {
if *m.add(k) != 0.0 {
acc += v;
}
}
None => acc += v,
}
}
}
unsafe { *dst.add(c) = acc };
}
}
}
#[derive(Default)]
pub(in crate::simulate_array::tape) struct ChainScratch {
ops: Vec<Op>,
bufs: Vec<(*const f64, usize)>,
tmp: Vec<f64>,
compact: Vec<Option<Compacted>>,
}
unsafe impl Send for ChainScratch {}
#[derive(Default)]
struct Compacted {
rows: Vec<u32>,
pos: Vec<u32>,
w: Vec<f64>,
}
pub(super) unsafe fn refill_compact(
chains: &[SegChain],
scratch: &mut ChainScratch,
prog: &TapeProgram,
slab_ptr: *const f64,
slot_off: &[usize],
) {
if scratch.compact.len() != chains.len() {
scratch.compact.clear();
scratch.compact.resize_with(chains.len(), || None);
}
for ch in chains {
let Some(plan) = ch.compact else { continue };
let Instr::SegReduce { table, .. } = &prog.instrs[ch.seg] else {
unreachable!("a chain's reduction is a SegReduce")
};
let rows = &prog.seg_tables[*table as usize].rows;
let n_pairs = *rows.last().expect("a planned chain has cells") as usize;
let read = |s: SlotId| unsafe {
std::slice::from_raw_parts(slab_ptr.add(slot_off[s as usize]), n_pairs)
};
let mask = read(plan.mask);
let w = plan.w.map(read);
let pos = &prog.gather_tables[plan.table as usize].pos;
let c = scratch.compact[ch.id].get_or_insert_with(|| Compacted {
rows: Vec::with_capacity(rows.len()),
pos: Vec::with_capacity(n_pairs),
w: Vec::with_capacity(if w.is_some() { n_pairs } else { 0 }),
});
c.rows.clear();
c.pos.clear();
c.w.clear();
c.rows.push(0);
for r in rows.windows(2) {
for k in r[0] as usize..r[1] as usize {
if mask[k] != 0.0 {
c.pos.push(pos[k]);
if let Some(w) = w {
c.w.push(w[k]);
}
}
}
c.rows.push(c.pos.len() as u32);
}
}
}
struct Job<'a> {
ops: &'a [Op],
rows: &'a [u32],
seg_op: BinCode,
init: f64,
seg_src: *const f64,
seg_mask: Option<*const f64>,
seg_dst: *mut f64,
n_out: usize,
ways: usize,
fused: Option<(&'a FusedSpec, super::fused::Resolved<'a>)>,
sop: Option<SumOfProducts>,
cap: usize,
lead: usize,
bufs: &'a [(*const f64, usize)],
tmp: *mut f64,
tmp_stride: usize,
idx: &'a [Option<IndexTable>],
simd: SimdLevel,
precision: crate::precision::Precision,
}
unsafe impl Sync for Job<'_> {}
fn tuple_arg(o: &Operand, env: &Env, slab_ptr: *mut f64, slot_off: &[usize]) -> Option<Arg> {
Some(match o {
Operand::Lit(v) => Arg::S(*v),
Operand::Param(p) => Arg::S(env.params[*p as usize]),
Operand::Time => Arg::S(env.t),
Operand::Slot(s) => {
let p = unsafe { slab_ptr.add(slot_off[*s as usize]) };
if env.prog.slots[*s as usize].scalar {
Arg::S(unsafe { *p })
} else {
Arg::P(p)
}
}
Operand::State(ix) => {
let sv = &env.prog.state_vars[*ix as usize];
if sv.shape.is_empty() {
Arg::S(env.state[sv.flat_offset])
} else {
Arg::P(super::resolve::state_ptr(env, *ix))
}
}
})
}
fn rv(a: Arg, lo: usize) -> Rv {
match a {
Arg::S(v) => Rv::S(v),
Arg::P(p) => Rv::V {
ptr: unsafe { p.add(lo) },
strides: smallvec::smallvec![1],
},
}
}
fn chain_ways(ch: &SegChain, threads: usize) -> usize {
if super::par::forced() {
return threads.min(ch.n_out).max(1);
}
threads
.min(ch.work / MIN_CHAIN_WORK_PER_WORKER)
.min(ch.n_out / 8)
.max(1)
}
#[allow(clippy::too_many_arguments)]
pub(super) unsafe fn run_chain(
ch: &SegChain,
env: &Env,
slab_ptr: *mut f64,
slot_off: &[usize],
scratch: &mut ChainScratch,
fscratch: &mut FusedScratch,
idx_tables: &[Vec<Option<IndexTable>>],
simd: SimdLevel,
dy_home: &[usize],
dy: *mut f64,
) -> bool {
let ways = chain_ways(ch, fscratch.workers.threads);
let prog = env.prog;
let Instr::SegReduce {
op,
init,
src,
mask,
table,
out,
} = &prog.instrs[ch.seg]
else {
unreachable!("a chain's reduction is a SegReduce")
};
let rows = &prog.seg_tables[*table as usize].rows[..];
scratch.ops.clear();
for ins in &prog.instrs[ch.start..ch.seg] {
let at = |o: SlotId| unsafe { slab_ptr.add(slot_off[o as usize]) };
let rvs = |o: &Operand| tuple_arg(o, env, slab_ptr, slot_off);
let op = match ins {
Instr::TableGather { src, table, out } => Op::Table {
dst: at(*out),
src: match src {
SrcRef::Slot(s) => at(*s),
SrcRef::State(ix) => super::resolve::state_ptr(env, *ix),
},
pos: prog.gather_tables[*table as usize].pos.as_ptr(),
},
Instr::Bin { op, a, b, out } => match (rvs(a), rvs(b)) {
(Some(a), Some(b)) => Op::Bin {
op: *op,
dst: at(*out),
a,
b,
},
_ => return false,
},
Instr::Un { op, a, out } => match rvs(a) {
Some(a) => Op::Un {
op: *op,
dst: at(*out),
a,
},
None => return false,
},
Instr::Neg { a, out } => match rvs(a) {
Some(a) => Op::Neg { dst: at(*out), a },
None => return false,
},
_ => unreachable!("chain tuple instructions are planned"),
};
scratch.ops.push(op);
}
let seg_src = unsafe { slab_ptr.add(slot_off[*src as usize]) as *const f64 };
let seg_mask = mask.map(|m| unsafe { slab_ptr.add(slot_off[m as usize]) as *const f64 });
let seg_dst = unsafe { slab_ptr.add(slot_off[*out as usize]) };
let (resolved, workers) = match ch.fused {
Some(spec) => {
let (r, w) = unsafe {
resolve_fused(
spec as usize,
env,
slab_ptr,
slot_off,
fscratch,
dy_home,
dy,
)
};
(Some((&prog.fused[spec as usize], r)), w)
}
None => (None, &mut fscratch.workers),
};
let mut sop = SumOfProducts::of(&scratch.ops, *op, *init, seg_src, seg_mask, ch.private);
let cap = ch.max_row.max(CHAIN_BLOCK);
scratch.bufs.clear();
if ch.private && sop.is_none() {
for (j, op) in scratch.ops.iter().enumerate() {
let dst = op.dst() as *const f64;
if !scratch.bufs.iter().any(|&(p, _)| std::ptr::eq(p, dst)) {
scratch.bufs.push((dst, j));
}
}
}
let tmp_stride = scratch.bufs.len() * cap;
if scratch.tmp.len() < ways * tmp_stride {
scratch.tmp.resize(ways * tmp_stride, 0.0);
}
let tmp = scratch.tmp.as_mut_ptr();
let (mut rows, mut seg_mask) = (rows, seg_mask);
if let (Some(sp), Some(Some(c))) = (&mut sop, scratch.compact.get(ch.id))
&& !sp.store
{
sp.pos = c.pos.as_ptr();
if ch.compact.is_some_and(|p| p.w.is_some()) {
sp.w = Arg::P(c.w.as_ptr());
}
rows = &c.rows;
seg_mask = None;
}
let job = Job {
ops: &scratch.ops,
rows,
seg_op: *op,
init: *init,
seg_src,
seg_mask,
seg_dst,
n_out: ch.n_out,
ways,
idx: ch.fused.map_or(&[][..], |s| &idx_tables[s as usize][..]),
sop,
cap,
lead: (ch.n_out / (4 * ways + 1)).min(CHAIN_LEAD_WORK * ch.n_out / ch.work.max(1)),
bufs: &scratch.bufs,
tmp,
tmp_stride,
fused: resolved,
simd,
precision: crate::precision::active(),
};
unsafe { run_job(workers, &job) };
true
}
fn cell_share(n: usize, lead: usize, w: usize, ways: usize, at: *const f64) -> (usize, usize) {
const ALIGN: usize = 8;
let phase = (at as usize / std::mem::size_of::<f64>()) % ALIGN;
let cut = |k: usize| {
if k == 0 {
0
} else if k == ways {
n
} else {
let c = lead + (n - lead) * k / ways;
((c + phase) / ALIGN * ALIGN).saturating_sub(phase).min(n)
}
};
(cut(w), cut(w + 1))
}
unsafe fn run_job(workers: &mut FusedWorkers, job: &Job) {
workers.run_with_workers(job.ways, &|w, fregs, cursor| {
let (c0, c1) = cell_share(job.n_out, job.lead, w, job.ways, job.seg_dst);
if c0 >= c1 {
return;
}
let _p = crate::precision::enter(job.precision);
let dst = unsafe { job.seg_dst.add(c0) };
let rows = &job.rows[c0..=c1];
let (mask, init) = (job.seg_mask, job.init);
if let Some(sp) = &job.sop {
unsafe { sp.run(rows, mask, init, dst) };
if let Some((fs, r)) = &job.fused {
unsafe { cell_window(job, fs, r, fregs, cursor, c0, c1) };
}
return;
}
let tmp = unsafe { job.tmp.add(w * job.tmp_stride) };
let mut b0 = c0;
while b0 < c1 {
let q0 = job.rows[b0] as usize;
let mut b1 = b0 + 1;
while b1 < c1 && job.rows[b1 + 1] as usize - q0 <= job.cap {
b1 += 1;
}
let len = job.rows[b1] as usize - q0;
for (j, op) in job.ops.iter().enumerate() {
unsafe { run_op(job.block_op(*op, j, q0, tmp), 0, len) };
}
let src = job.place(job.seg_src, job.ops.len(), q0, tmp);
let mask = mask.map(|m| job.place(m, job.ops.len(), q0, tmp) as *const f64);
let rows = &job.rows[b0..=b1];
let dst = unsafe { job.seg_dst.add(b0) };
macro_rules! fold {
($f:expr) => {
unsafe { seg_reduce(dst, src, mask, rows, q0, init, $f) }
};
}
dispatch_bin_kernel!(&job.seg_op, fold);
if let Some((fs, r)) = &job.fused {
unsafe { cell_window(job, fs, r, fregs, cursor, b0, b1) };
}
b0 = b1;
}
});
}
impl Job<'_> {
fn place(&self, p: *const f64, j: usize, q0: usize, tmp: *mut f64) -> *mut f64 {
for (i, &(s, from)) in self.bufs.iter().enumerate() {
if std::ptr::eq(s, p) && from < j {
return unsafe { tmp.add(i * self.cap) };
}
}
unsafe { p.add(q0) as *mut f64 }
}
fn block_op(&self, op: Op, j: usize, q0: usize, tmp: *mut f64) -> Op {
let arg = |a: Arg| match a {
Arg::S(v) => Arg::S(v),
Arg::P(p) => Arg::P(self.place(p, j, q0, tmp)),
};
let dst = |d: *mut f64| self.place(d, j + 1, q0, tmp);
match op {
Op::Table { dst: d, src, pos } => Op::Table {
dst: dst(d),
src,
pos: unsafe { pos.add(q0) },
},
Op::Bin { op, dst: d, a, b } => Op::Bin {
op,
dst: dst(d),
a: arg(a),
b: arg(b),
},
Op::Un { op, dst: d, a } => Op::Un {
op,
dst: dst(d),
a: arg(a),
},
Op::Neg { dst: d, a } => Op::Neg {
dst: dst(d),
a: arg(a),
},
}
}
}
impl Op {
fn dst(&self) -> *mut f64 {
match *self {
Op::Table { dst, .. }
| Op::Bin { dst, .. }
| Op::Un { dst, .. }
| Op::Neg { dst, .. } => dst,
}
}
}
const CHAIN_BLOCK: usize = 512;
unsafe fn cell_window(
job: &Job,
fs: &FusedSpec,
r: &super::fused::Resolved,
fregs: &mut [f64],
cursor: &mut super::fused::RunCursor,
c0: usize,
c1: usize,
) {
unsafe {
fill_whole(fs, r.bases, r.whole_src, c0, c1, 0);
run_fused_window(
job.simd,
fs,
r.svals,
r.bases,
r.outs,
std::ptr::null_mut(),
job.idx,
fregs,
cursor,
Window {
lo: c0,
hi: c1,
period: 0,
node_elems: r.node_elems,
},
);
}
}
unsafe fn run_op(op: Op, lo: usize, len: usize) {
if len == 0 {
return;
}
let sh = [len];
match op {
Op::Table { dst, src, pos } => unsafe {
let v = SrcView {
ptr: src,
shape: smallvec::smallvec![usize::MAX],
strides: smallvec::smallvec![1],
};
table_gather(
dst.add(lo),
&v,
std::slice::from_raw_parts(pos.add(lo), len),
);
},
Op::Bin { op, dst, a, b } => {
let (av, bv) = (rv(a, lo), rv(b, lo));
let dst = unsafe { dst.add(lo) };
macro_rules! strided {
($f:expr) => {
unsafe { ew2(dst, &sh, &av, &bv, $f) }
};
}
dispatch_bin_kernel!(&op, strided);
}
Op::Un { op, dst, a } => {
let av = rv(a, lo);
let dst = unsafe { dst.add(lo) };
macro_rules! strided {
($f:expr) => {
unsafe { ew1(dst, &sh, &av, $f) }
};
}
dispatch_un_kernel!(&op, strided);
}
Op::Neg { dst, a } => unsafe { ew1(dst.add(lo), &sh, &rv(a, lo), |x| -x) },
}
}