1use std::collections::HashMap;
6use std::fmt::Write;
7
8use crate::codegen::{expr_str, ref_str};
9use crate::graph::ExprGraph;
10use crate::node::{ExprId, Node};
11
12pub struct SplitKernels {
18 pub parts: Vec<String>,
20 pub n_scratch: usize,
22 pub workgroup_size: u32,
24}
25
26fn children(node: Node) -> [Option<ExprId>; 3] {
27 match node {
28 Node::Var(_) | Node::Lit(_) => [None; 3],
29 Node::Add(a, b) | Node::Mul(a, b) | Node::Atan2(a, b) => [Some(a), Some(b), None],
30 Node::Neg(a)
31 | Node::Recip(a)
32 | Node::Sqrt(a)
33 | Node::Sin(a)
34 | Node::Exp2(a)
35 | Node::Log2(a) => [Some(a), None, None],
36 Node::Select(c, a, b) => [Some(c), Some(a), Some(b)],
37 }
38}
39
40impl ExprGraph {
41 pub fn to_wgsl_split(
45 &self,
46 outputs: &[ExprId],
47 n_inputs: usize,
48 max_nodes: usize,
49 ) -> SplitKernels {
50 self.split_wgsl(outputs, n_inputs, max_nodes, false)
51 }
52
53 pub fn to_wgsl_split_f64(
58 &self,
59 outputs: &[ExprId],
60 n_inputs: usize,
61 max_nodes: usize,
62 ) -> SplitKernels {
63 self.split_wgsl(outputs, n_inputs, max_nodes, true)
64 }
65
66 fn split_wgsl(
67 &self,
68 outputs: &[ExprId],
69 n_inputs: usize,
70 max_nodes: usize,
71 f64: bool,
72 ) -> SplitKernels {
73 let wg = 256u32;
74 let (ty, suffix) = if f64 { ("f64", "lf") } else { ("f32", "") };
75 let live = self.live_set(outputs);
76 let mut order: Vec<usize> = live
77 .iter()
78 .copied()
79 .filter(|&i| !matches!(self.node(ExprId(i as u32)), Node::Var(_) | Node::Lit(_)))
80 .collect();
81 order.sort_unstable();
82 let chunks: Vec<&[usize]> = order.chunks(max_nodes.max(1)).collect();
83 let nparts = chunks.len().max(1);
84 let mut part_of: HashMap<usize, usize> = HashMap::new();
85 for (p, c) in chunks.iter().enumerate() {
86 for &i in *c {
87 part_of.insert(i, p);
88 }
89 }
90 let mut slot: HashMap<usize, usize> = HashMap::new();
93 let need = |i: usize, reader: usize, slot: &mut HashMap<usize, usize>| {
94 if let Some(&p) = part_of.get(&i) {
95 if p != reader && !slot.contains_key(&i) {
96 let n = slot.len();
97 slot.insert(i, n);
98 }
99 }
100 };
101 for (p, c) in chunks.iter().enumerate() {
102 for &i in *c {
103 for ch in children(self.node(ExprId(i as u32))).into_iter().flatten() {
104 need(ch.0 as usize, p, &mut slot);
105 }
106 }
107 }
108 for o in outputs {
109 need(o.0 as usize, nparts - 1, &mut slot);
110 }
111 let ns = slot.len().max(1);
112 let helpers = if f64 { F64_HELPERS } else { "" };
113 let header = format!(
114 "{helpers}struct Params {{ count: u32, _p1: u32, _p2: u32, _p3: u32 }}\n\
115 @group(0) @binding(0) var<storage, read> inputs: array<{ty}>;\n\
116 @group(0) @binding(1) var<storage, read_write> scratch: array<{ty}>;\n\
117 @group(0) @binding(2) var<storage, read_write> outputs: array<{ty}>;\n\
118 @group(0) @binding(3) var<uniform> params: Params;\n\
119 @compute @workgroup_size({wg})\n\
120 fn main(@builtin(global_invocation_id) gid: vec3<u32>) {{\n let idx = gid.x;\n if (idx >= params.count) {{ return; }}\n"
121 );
122 let mut parts = Vec::with_capacity(nparts);
123 for p in 0..nparts {
124 let chunk: &[usize] = chunks.get(p).copied().unwrap_or(&[]);
125 let mut src = header.clone();
126 let mut loaded: std::collections::HashSet<usize> = std::collections::HashSet::new();
127 let mut body = String::new();
128 let load = |id: ExprId,
129 pre: &mut String,
130 loaded: &mut std::collections::HashSet<usize>| {
131 let i = id.0 as usize;
132 if !loaded.insert(i) {
133 return;
134 }
135 match self.node(id) {
136 Node::Var(n) => {
137 writeln!(pre, " let x{n} = inputs[idx * {n_inputs}u + {n}u];").unwrap()
138 }
139 Node::Lit(_) => {}
140 _ => {
141 if part_of.get(&i) != Some(&p) {
142 writeln!(pre, " let t{i} = scratch[idx * {ns}u + {}u];", slot[&i])
143 .unwrap();
144 }
145 }
146 }
147 };
148 let mut pre = String::new();
149 for &i in chunk {
150 let node = self.node(ExprId(i as u32));
151 for ch in children(node).into_iter().flatten() {
152 load(ch, &mut pre, &mut loaded);
153 }
154 loaded.insert(i);
155 let mut rhs = expr_str(self, node, suffix, crate::codegen::Dialect::Wgsl);
156 if f64 {
157 for f in ["sin(", "exp2(", "log2(", "atan2("] {
158 if let Some(args) = rhs.strip_prefix(f) {
159 rhs = format!("{}_64({args}", &f[..f.len() - 1]);
160 break;
161 }
162 }
163 }
164 writeln!(body, " let t{i} = {rhs};").unwrap();
165 if let Some(s) = slot.get(&i) {
166 writeln!(body, " scratch[idx * {ns}u + {s}u] = t{i};").unwrap();
167 }
168 }
169 if p == nparts - 1 {
170 for o in outputs {
171 load(*o, &mut pre, &mut loaded);
172 }
173 let no = outputs.len();
174 for (k, o) in outputs.iter().enumerate() {
175 writeln!(
176 body,
177 " outputs[idx * {no}u + {k}u] = {};",
178 ref_str(self, *o, suffix)
179 )
180 .unwrap();
181 }
182 }
183 src += ⪯
184 src += &body;
185 src += "}\n";
186 parts.push(src);
187 }
188 SplitKernels {
189 parts,
190 n_scratch: ns,
191 workgroup_size: wg,
192 }
193 }
194}
195
196const F64_HELPERS: &str = "fn sin_64(x: f64) -> f64 {
200 let k = round(x * 0.6366197723675814lf);
201 let r = (x - k * 1.5707963267948966lf) - k * 6.123233995736766e-17lf;
202 let r2 = r * r;
203 let s = r * (1.0lf + r2 * (-0.16666666666666666lf + r2 * (0.008333333333333333lf + r2 * (-0.0001984126984126984lf
204 + r2 * (2.755731922398589e-6lf + r2 * (-2.505210838544172e-8lf + r2 * (1.6059043836821613e-10lf
205 + r2 * (-7.647163731819816e-13lf))))))));
206 let c = 1.0lf + r2 * (-0.5lf + r2 * (0.041666666666666664lf + r2 * (-0.001388888888888889lf
207 + r2 * (2.48015873015873e-5lf + r2 * (-2.755731922398589e-7lf + r2 * (2.08767569878681e-9lf
208 + r2 * (-1.1470745597729725e-11lf)))))));
209 let q = i32(k - 4.0lf * floor(k * 0.25lf));
210 if (q == 0) { return s; }
211 if (q == 1) { return c; }
212 if (q == 2) { return -s; }
213 return -c;
214}
215fn pow2i64(n: i32) -> f64 {
216 var m = n;
217 var b = 2.0lf;
218 if (m < 0) { b = 0.5lf; m = -m; }
219 var p = 1.0lf;
220 for (var i = 0; i < 11; i++) {
221 if ((m & 1) == 1) { p = p * b; }
222 b = b * b;
223 m = m >> 1u;
224 }
225 return p;
226}
227fn exp2_64(x: f64) -> f64 {
228 let n = floor(clamp(x, -1070.0lf, 1023.0lf) + 0.5lf);
229 let y = (x - n) * 0.6931471805599453lf;
230 var e = 1.0lf;
231 var term = 1.0lf;
232 for (var i = 1; i < 18; i++) { term = term * y / f64(i); e = e + term; }
233 return e * pow2i64(i32(n));
234}
235fn log2_64(x: f64) -> f64 {
236 var m = x;
237 var e = 0.0lf;
238 for (var i = 0; i < 20 && m >= 1.8446744073709552e19lf; i++) { m = m * 5.421010862427522e-20lf; e = e + 64.0lf; }
239 for (var i = 0; i < 20 && m < 5.421010862427522e-20lf; i++) { m = m * 1.8446744073709552e19lf; e = e - 64.0lf; }
240 for (var i = 0; i < 70 && m >= 1.4142135623730951lf; i++) { m = m * 0.5lf; e = e + 1.0lf; }
241 for (var i = 0; i < 70 && m < 0.7071067811865476lf; i++) { m = m * 2.0lf; e = e - 1.0lf; }
242 let t = (m - 1.0lf) / (m + 1.0lf);
243 let t2 = t * t;
244 var acc = 0.0lf;
245 for (var k = 12; k >= 0; k--) { acc = acc * t2 + 1.0lf / f64(2 * k + 1); }
246 return e + 2.0lf * t * acc * 1.4426950408889634lf;
247}
248fn atan2_64(y: f64, x: f64) -> f64 {
249 var th = f64(atan2(f32(y), f32(x)));
250 for (var i = 0; i < 2; i++) {
251 let s = sin_64(th);
252 let c = sin_64(th + 1.5707963267948966lf);
253 th = th + (y * c - x * s) / (x * c + y * s);
254 }
255 return th;
256}
257";