use super::interp::{Cx, exec_share, exec_straight, is_straight, shares};
use super::resolve::rm_strides;
use super::*;
const PHASE_MIN_WORK: usize = 1 << 12;
fn phase_min_work() -> usize {
if super::par::forced() {
0
} else {
PHASE_MIN_WORK
}
}
const MOVE_MAX_ELEMS: usize = 1 << 15;
const MAX_PLANNED: usize = 1 << 13;
#[derive(Clone, Copy, Debug)]
struct Task {
pc: u32,
ways: u16,
thread: u16,
}
#[derive(Debug)]
enum Step {
Loop(std::ops::Range<usize>),
Fork {
tasks: std::ops::Range<usize>,
threads: usize,
},
}
pub(in crate::simulate_array::tape) struct CallPlan {
ways: usize,
range: std::ops::Range<usize>,
steps: Vec<Step>,
tasks: Vec<Task>,
pub(super) slot_off: Vec<usize>,
pub(super) slab_len: usize,
}
fn dy_written(prog: &TapeProgram, ins: &Instr, dy_home: &[usize]) -> Option<(usize, usize)> {
match ins {
Instr::DyWrite { write } => {
let w = &prog.dy_writes[*write as usize];
if dy_home[w.slot as usize] != usize::MAX {
return None;
}
if let Some(pos) = &w.scatter {
let lo = pos.iter().copied().min()?;
let hi = pos.iter().copied().max()? + 1;
return Some((lo, hi));
}
if let Some(flat) = w.scalar_flat {
return Some((flat, flat + 1));
}
let sv = &prog.state_vars[w.var as usize];
Some((
sv.flat_offset,
sv.flat_offset + sv.shape.iter().product::<usize>().max(1),
))
}
_ => None,
}
}
fn fused_out(prog: &TapeProgram, s: SlotId, dy_home: &[usize]) -> (bool, Option<(usize, usize)>) {
match dy_home.get(s as usize) {
Some(&UNSTORED) => (false, None),
Some(&off) if off != usize::MAX => (
false,
Some((off, off + prog.slots[s as usize].elems().max(1))),
),
_ => (true, None),
}
}
enum Place {
Task(u16),
Loop(usize),
}
fn place(
prog: &TapeProgram,
pc: usize,
end: usize,
ways: usize,
chains: &[super::chain::SegChain],
) -> Place {
if let Some(ch) = chains.iter().find(|c| c.start == pc && c.end <= end) {
return Place::Loop(ch.end - pc);
}
let ins = &prog.instrs[pc];
match ins {
Instr::JmpIfZero {
n_true, n_false, ..
} => return Place::Loop(1 + (*n_true + *n_false) as usize),
Instr::Sweep { spec } => {
return Place::Loop(1 + prog.sweeps[*spec as usize].body_len as usize);
}
_ => {}
}
if !is_straight(ins) || prog.precision.get(pc).is_some() {
return Place::Loop(1);
}
let split = match ins {
Instr::Fused { spec } => super::par::split_ways(&prog.fused[*spec as usize], ways),
Instr::Assemble { out, .. } => {
let d = &prog.slots[*out as usize];
if d.shape.is_empty() {
1
} else {
super::par::ways_for(d.elems(), ways)
}
}
Instr::Scan {
axis: 0, src_shape, ..
} => {
let lanes: usize = src_shape.iter().skip(1).product();
if src_shape.len() < 2 {
1
} else {
super::par::lane_split_ways(lanes, src_shape.iter().product(), ways)
}
}
Instr::Gather { out, .. } | Instr::Reduce { out, .. } => {
let d = &prog.slots[*out as usize];
if d.scalar {
1
} else {
super::par::ways_for(d.elems(), ways)
}
}
Instr::Copy { out, .. } => {
let d = &prog.slots[*out as usize];
if d.scalar {
1
} else {
super::par::copy_ways(ways, &rm_strides(&d.shape), &d.shape)
}
}
Instr::DyWrite { write } => {
let w = &prog.dy_writes[*write as usize];
if w.scatter.is_some() || w.scalar_flat.is_some() {
1
} else {
let sv = &prog.state_vars[w.var as usize];
let shape = &prog.slots[w.slot as usize].shape;
let st = dy_strides(prog, &sv.shape);
super::par::copy_ways(ways, &st, shape)
}
}
_ => 1,
};
if split <= 1 {
Place::Task(0)
} else if shares(prog, ins) {
Place::Task(split as u16)
} else {
Place::Loop(1)
}
}
fn task_cost(prog: &TapeProgram, ins: &Instr) -> usize {
let elems = ins.out().map_or(1, |o| prog.slots[o as usize].elems());
super::par::instr_work(prog, ins).max(elems).max(1)
}
impl CallPlan {
pub(super) fn build(
prog: &TapeProgram,
range: std::ops::Range<usize>,
ways: usize,
chains: &[super::chain::SegChain],
dy_home: &[usize],
base_off: &[usize],
) -> Option<CallPlan> {
if ways < 2 || range.len() > MAX_PLANNED {
return None;
}
let tables = prog.tables();
let n_store = prog.slab.storages.len();
let mut defs = vec![0u32; prog.slots.len()];
let mut def_in = vec![false; prog.slots.len()];
for (pc, ins) in prog.instrs.iter().enumerate() {
ins.for_each_def(&tables, |s| {
defs[s as usize] += 1;
def_in[s as usize] = range.contains(&pc);
});
}
let movable = |s: SlotId| defs[s as usize] == 1 && def_in[s as usize];
let mut moved = vec![u32::MAX; prog.slots.len()];
let mut slot_off = base_off.to_vec();
let mut slab_len = prog.slab.total_elems;
let budget = prog.slab.total_elems;
let mut n_moved = 0usize;
let store_of = |moved: &[u32], s: SlotId| -> Option<u32> {
match moved[s as usize] {
u32::MAX => {
let st = prog.slots[s as usize].storage;
(st != u32::MAX).then_some(st)
}
m => Some(m),
}
};
let mut steps: Vec<Step> = Vec::new();
let mut tasks: Vec<Task> = Vec::new();
let mut seg: Vec<(Task, usize, usize)> = Vec::new();
let mut last_write = vec![0usize; n_store];
let mut last_read = vec![0usize; n_store];
let mut wslots: Vec<SlotId> = Vec::new();
let mut dy_writes: Vec<(usize, usize, usize)> = Vec::new();
let mut reads: Vec<u32> = Vec::new();
let mut writes: Vec<u32> = Vec::new();
let mut dys: Vec<(usize, usize)> = Vec::new();
let flush =
|seg: &mut Vec<(Task, usize, usize)>, steps: &mut Vec<Step>, tasks: &mut Vec<Task>| {
let phases = seg.iter().map(|t| t.1).max().map_or(0, |m| m + 1);
for ph in 0..phases {
let start = tasks.len();
let mut work = 0usize;
let mut split = false;
let mut wholes: Vec<(usize, usize)> = Vec::new();
for &(t, p, cost) in seg.iter() {
if p != ph {
continue;
}
work = work.saturating_add(cost);
split |= t.ways > 0;
if t.ways == 0 {
wholes.push((tasks.len(), cost));
}
tasks.push(t);
}
wholes.sort_by_key(|&(i, c)| (std::cmp::Reverse(c), i));
let mut load = vec![0usize; ways];
let mut used = 1usize;
for (i, c) in wholes {
let t = (0..ways).min_by_key(|&t| (load[t], t)).unwrap_or(0);
load[t] += c;
tasks[i].thread = t as u16;
used = used.max(t + 1);
}
let threads = if work < phase_min_work() {
1
} else if split {
ways
} else {
used
};
if threads == 1 {
for t in &mut tasks[start..] {
t.thread = 0;
}
}
steps.push(Step::Fork {
tasks: start..tasks.len(),
threads,
});
}
seg.clear();
};
let mut pc = range.start;
while pc < range.end {
let split = match place(prog, pc, range.end, ways, chains) {
Place::Loop(n) => {
let hi = (pc + n).min(range.end);
flush(&mut seg, &mut steps, &mut tasks);
last_write.fill(0);
last_read.fill(0);
dy_writes.clear();
match steps.last_mut() {
Some(Step::Loop(r)) if r.end == pc => r.end = hi,
_ => steps.push(Step::Loop(pc..hi)),
}
pc = hi;
continue;
}
Place::Task(w) => w,
};
let ins = &prog.instrs[pc];
if matches!(ins, Instr::DyWrite { write }
if dy_home[prog.dy_writes[*write as usize].slot as usize] != usize::MAX)
{
pc += 1;
continue;
}
reads.clear();
writes.clear();
wslots.clear();
dys.clear();
ins.for_each_read(&tables, |s| reads.extend(store_of(&moved, s)));
match ins {
Instr::Fused { spec } => {
let fs = &prog.fused[*spec as usize];
let outs = fs.outputs.iter().map(|&(_, s)| s);
for s in outs.chain(fs.reduce.as_ref().map(|r| r.out)) {
let (slab, dy) = fused_out(prog, s, dy_home);
if slab {
wslots.push(s);
}
dys.extend(dy);
}
}
_ => ins.for_each_def(&tables, |s| wslots.push(s)),
}
dys.extend(dy_written(prog, ins, dy_home));
let mut phase = 0usize;
for &b in &reads {
phase = phase.max(last_write[b as usize]);
}
for &(lo, hi) in &dys {
for &(a, b, p) in &dy_writes {
if lo < b && a < hi {
phase = phase.max(p);
}
}
}
for &s in &wslots {
let Some(b) = store_of(&moved, s) else {
continue;
};
let wait = last_write[b as usize].max(last_read[b as usize]);
let elems = prog.slots[s as usize].elems().max(1);
if wait > phase
&& movable(s)
&& elems <= MOVE_MAX_ELEMS
&& n_moved + elems <= budget
{
slab_len = slab_len.div_ceil(8) * 8;
slot_off[s as usize] = slab_len;
slab_len += elems;
n_moved += elems;
moved[s as usize] = (last_write.len()) as u32;
last_write.push(0);
last_read.push(0);
}
writes.extend(store_of(&moved, s));
}
for &b in &writes {
phase = phase.max(last_write[b as usize]).max(last_read[b as usize]);
}
for &b in &reads {
let r = &mut last_read[b as usize];
*r = (*r).max(phase + 1);
}
for &b in &writes {
last_write[b as usize] = phase + 1;
last_read[b as usize] = 0;
}
for &(lo, hi) in &dys {
dy_writes.push((lo, hi, phase + 1));
}
seg.push((
Task {
pc: pc as u32,
ways: split,
thread: 0,
},
phase,
task_cost(prog, ins),
));
pc += 1;
}
flush(&mut seg, &mut steps, &mut tasks);
Some(CallPlan {
ways,
range,
steps,
tasks,
slot_off,
slab_len,
})
}
pub(super) fn fits(&self, range: &std::ops::Range<usize>, ways: usize) -> bool {
self.ways == ways && self.range == *range
}
}
struct PhaseJob<'a> {
cx: Cx<'a>,
prog: &'a TapeProgram,
tasks: &'a [Task],
main: *mut Scratch,
peers: *mut Scratch,
precision: crate::precision::Precision,
}
unsafe impl Sync for PhaseJob<'_> {}
impl PhaseJob<'_> {
unsafe fn run(&self, w: usize) {
let _p = crate::precision::enter(self.precision);
let scratch = unsafe {
if w == 0 {
&mut *self.main
} else {
&mut *self.peers.add(w - 1)
}
};
for t in self.tasks {
let ins = &self.prog.instrs[t.pc as usize];
if t.ways > 0 {
if w < t.ways as usize {
unsafe { exec_share(&self.cx, ins, scratch, w, t.ways as usize) };
}
} else if t.thread as usize == w {
unsafe { exec_straight(&self.cx, ins, scratch, 1) };
}
}
}
}
pub(super) fn run_plan(plan: &CallPlan, env: &Env, exec: &mut TapeExec, dy: &mut [f64]) {
let prog = env.prog;
while exec.peers.len() + 1 < plan.ways {
exec.peers.push(Scratch {
fregs: vec![0.0; exec.scratch.fregs.len()],
fscratch: super::fused::FusedScratch::for_peer(prog),
asm_parts: Vec::new(),
});
}
for step in &plan.steps {
let (tasks, threads) = match step {
Step::Loop(r) => {
super::interp::run_range(env, r.clone(), exec, dy);
continue;
}
Step::Fork { tasks, threads } => (&plan.tasks[tasks.clone()], *threads),
};
let TapeExec {
slab,
slot_off,
plan_full,
asm_covered,
idx_tables,
simd,
dy_home_quiet,
scratch,
peers,
..
} = exec;
let cx = Cx {
env,
slab_ptr: slab.as_mut_ptr(),
slot_off,
plan_full,
asm_covered,
idx_tables,
simd: *simd,
dy_home: dy_home_quiet,
dy: dy.as_mut_ptr(),
dy_len: dy.len(),
};
if threads <= 1 {
let call = scratch.fscratch.workers.call_ways;
for t in tasks {
unsafe { exec_straight(&cx, &prog.instrs[t.pc as usize], scratch, call) };
}
continue;
}
let job = PhaseJob {
cx,
prog,
tasks,
main: scratch,
peers: peers.as_mut_ptr(),
precision: crate::precision::active(),
};
super::pool::run(threads, &|w| unsafe { job.run(w) });
}
}