use std::collections::HashMap;
use std::fmt::Write;
use crate::codegen::{expr_str, ref_str};
use crate::graph::ExprGraph;
use crate::node::{ExprId, Node};
pub struct SplitKernels {
pub parts: Vec<String>,
pub n_scratch: usize,
pub workgroup_size: u32,
}
fn children(node: Node) -> [Option<ExprId>; 3] {
match node {
Node::Var(_) | Node::Lit(_) => [None; 3],
Node::Add(a, b) | Node::Mul(a, b) | Node::Atan2(a, b) => [Some(a), Some(b), None],
Node::Neg(a)
| Node::Recip(a)
| Node::Sqrt(a)
| Node::Sin(a)
| Node::Exp2(a)
| Node::Log2(a) => [Some(a), None, None],
Node::Select(c, a, b) => [Some(c), Some(a), Some(b)],
}
}
impl ExprGraph {
pub fn to_wgsl_split(
&self,
outputs: &[ExprId],
n_inputs: usize,
max_nodes: usize,
) -> SplitKernels {
self.split_wgsl(outputs, n_inputs, max_nodes, false)
}
pub fn to_wgsl_split_f64(
&self,
outputs: &[ExprId],
n_inputs: usize,
max_nodes: usize,
) -> SplitKernels {
self.split_wgsl(outputs, n_inputs, max_nodes, true)
}
fn split_wgsl(
&self,
outputs: &[ExprId],
n_inputs: usize,
max_nodes: usize,
f64: bool,
) -> SplitKernels {
let wg = 256u32;
let (ty, suffix) = if f64 { ("f64", "lf") } else { ("f32", "") };
let live = self.live_set(outputs);
let mut order: Vec<usize> = live
.iter()
.copied()
.filter(|&i| !matches!(self.node(ExprId(i as u32)), Node::Var(_) | Node::Lit(_)))
.collect();
order.sort_unstable();
let chunks: Vec<&[usize]> = order.chunks(max_nodes.max(1)).collect();
let nparts = chunks.len().max(1);
let mut part_of: HashMap<usize, usize> = HashMap::new();
for (p, c) in chunks.iter().enumerate() {
for &i in *c {
part_of.insert(i, p);
}
}
let mut slot: HashMap<usize, usize> = HashMap::new();
let need = |i: usize, reader: usize, slot: &mut HashMap<usize, usize>| {
if let Some(&p) = part_of.get(&i) {
if p != reader && !slot.contains_key(&i) {
let n = slot.len();
slot.insert(i, n);
}
}
};
for (p, c) in chunks.iter().enumerate() {
for &i in *c {
for ch in children(self.node(ExprId(i as u32))).into_iter().flatten() {
need(ch.0 as usize, p, &mut slot);
}
}
}
for o in outputs {
need(o.0 as usize, nparts - 1, &mut slot);
}
let ns = slot.len().max(1);
let helpers = if f64 { F64_HELPERS } else { "" };
let header = format!(
"{helpers}struct Params {{ count: u32, _p1: u32, _p2: u32, _p3: u32 }}\n\
@group(0) @binding(0) var<storage, read> inputs: array<{ty}>;\n\
@group(0) @binding(1) var<storage, read_write> scratch: array<{ty}>;\n\
@group(0) @binding(2) var<storage, read_write> outputs: array<{ty}>;\n\
@group(0) @binding(3) var<uniform> params: Params;\n\
@compute @workgroup_size({wg})\n\
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {{\n let idx = gid.x;\n if (idx >= params.count) {{ return; }}\n"
);
let mut parts = Vec::with_capacity(nparts);
for p in 0..nparts {
let chunk: &[usize] = chunks.get(p).copied().unwrap_or(&[]);
let mut src = header.clone();
let mut loaded: std::collections::HashSet<usize> = std::collections::HashSet::new();
let mut body = String::new();
let load = |id: ExprId,
pre: &mut String,
loaded: &mut std::collections::HashSet<usize>| {
let i = id.0 as usize;
if !loaded.insert(i) {
return;
}
match self.node(id) {
Node::Var(n) => {
writeln!(pre, " let x{n} = inputs[idx * {n_inputs}u + {n}u];").unwrap()
}
Node::Lit(_) => {}
_ => {
if part_of.get(&i) != Some(&p) {
writeln!(pre, " let t{i} = scratch[idx * {ns}u + {}u];", slot[&i])
.unwrap();
}
}
}
};
let mut pre = String::new();
for &i in chunk {
let node = self.node(ExprId(i as u32));
for ch in children(node).into_iter().flatten() {
load(ch, &mut pre, &mut loaded);
}
loaded.insert(i);
let mut rhs = expr_str(self, node, suffix, crate::codegen::Dialect::Wgsl);
if f64 {
for f in ["sin(", "exp2(", "log2(", "atan2("] {
if let Some(args) = rhs.strip_prefix(f) {
rhs = format!("{}_64({args}", &f[..f.len() - 1]);
break;
}
}
}
writeln!(body, " let t{i} = {rhs};").unwrap();
if let Some(s) = slot.get(&i) {
writeln!(body, " scratch[idx * {ns}u + {s}u] = t{i};").unwrap();
}
}
if p == nparts - 1 {
for o in outputs {
load(*o, &mut pre, &mut loaded);
}
let no = outputs.len();
for (k, o) in outputs.iter().enumerate() {
writeln!(
body,
" outputs[idx * {no}u + {k}u] = {};",
ref_str(self, *o, suffix)
)
.unwrap();
}
}
src += ⪯
src += &body;
src += "}\n";
parts.push(src);
}
SplitKernels {
parts,
n_scratch: ns,
workgroup_size: wg,
}
}
}
const F64_HELPERS: &str = "fn sin_64(x: f64) -> f64 {
let k = round(x * 0.6366197723675814lf);
let r = (x - k * 1.5707963267948966lf) - k * 6.123233995736766e-17lf;
let r2 = r * r;
let s = r * (1.0lf + r2 * (-0.16666666666666666lf + r2 * (0.008333333333333333lf + r2 * (-0.0001984126984126984lf
+ r2 * (2.755731922398589e-6lf + r2 * (-2.505210838544172e-8lf + r2 * (1.6059043836821613e-10lf
+ r2 * (-7.647163731819816e-13lf))))))));
let c = 1.0lf + r2 * (-0.5lf + r2 * (0.041666666666666664lf + r2 * (-0.001388888888888889lf
+ r2 * (2.48015873015873e-5lf + r2 * (-2.755731922398589e-7lf + r2 * (2.08767569878681e-9lf
+ r2 * (-1.1470745597729725e-11lf)))))));
let q = i32(k - 4.0lf * floor(k * 0.25lf));
if (q == 0) { return s; }
if (q == 1) { return c; }
if (q == 2) { return -s; }
return -c;
}
fn pow2i64(n: i32) -> f64 {
var m = n;
var b = 2.0lf;
if (m < 0) { b = 0.5lf; m = -m; }
var p = 1.0lf;
for (var i = 0; i < 11; i++) {
if ((m & 1) == 1) { p = p * b; }
b = b * b;
m = m >> 1u;
}
return p;
}
fn exp2_64(x: f64) -> f64 {
let n = floor(clamp(x, -1070.0lf, 1023.0lf) + 0.5lf);
let y = (x - n) * 0.6931471805599453lf;
var e = 1.0lf;
var term = 1.0lf;
for (var i = 1; i < 18; i++) { term = term * y / f64(i); e = e + term; }
return e * pow2i64(i32(n));
}
fn log2_64(x: f64) -> f64 {
var m = x;
var e = 0.0lf;
for (var i = 0; i < 20 && m >= 1.8446744073709552e19lf; i++) { m = m * 5.421010862427522e-20lf; e = e + 64.0lf; }
for (var i = 0; i < 20 && m < 5.421010862427522e-20lf; i++) { m = m * 1.8446744073709552e19lf; e = e - 64.0lf; }
for (var i = 0; i < 70 && m >= 1.4142135623730951lf; i++) { m = m * 0.5lf; e = e + 1.0lf; }
for (var i = 0; i < 70 && m < 0.7071067811865476lf; i++) { m = m * 2.0lf; e = e - 1.0lf; }
let t = (m - 1.0lf) / (m + 1.0lf);
let t2 = t * t;
var acc = 0.0lf;
for (var k = 12; k >= 0; k--) { acc = acc * t2 + 1.0lf / f64(2 * k + 1); }
return e + 2.0lf * t * acc * 1.4426950408889634lf;
}
fn atan2_64(y: f64, x: f64) -> f64 {
var th = f64(atan2(f32(y), f32(x)));
for (var i = 0; i < 2; i++) {
let s = sin_64(th);
let c = sin_64(th + 1.5707963267948966lf);
th = th + (y * c - x * s) / (x * c + y * s);
}
return th;
}
";