1use std::fmt::Write;
9
10use super::sched::{Item, Sched};
11use super::{BinOp, CmpOp, Elem, IId, INode, IType, IntGraph, UnOp};
12
13pub const WGSL_PRELUDE: &str = "\
16fn tx_mulhi_u32(a: u32, b: u32) -> u32 {
17 let al = a & 0xffffu; let ah = a >> 16u;
18 let bl = b & 0xffffu; let bh = b >> 16u;
19 let lh = al * bh; let hl = ah * bl;
20 let mid = ((al * bl) >> 16u) + (lh & 0xffffu) + (hl & 0xffffu);
21 return ah * bh + (lh >> 16u) + (hl >> 16u) + (mid >> 16u);
22}
23fn tx_mulhi_i32(a: i32, b: i32) -> i32 {
24 let ua = bitcast<u32>(a); let ub = bitcast<u32>(b);
25 return bitcast<i32>(tx_mulhi_u32(ua, ub) - select(0u, ub, a < 0i) - select(0u, ua, b < 0i));
26}
27";
28
29#[derive(Clone, Copy, PartialEq)]
30enum Lang {
31 Wgsl,
32 Rust,
33}
34
35struct Em<'a> {
36 g: &'a IntGraph,
37 lang: Lang,
38 sched: Sched,
39 params: Vec<String>,
40 out: String,
41 guard: bool,
43}
44
45fn ty_name(lang: Lang, t: IType) -> &'static str {
46 match (lang, t) {
47 (_, IType::I32) => "i32",
48 (_, IType::U32) => "u32",
49 (_, IType::Bool) => "bool",
50 }
51}
52
53impl Em<'_> {
54 fn lit(&self, t: IType, v: u32) -> String {
55 match (self.lang, t) {
56 (_, IType::Bool) => (v != 0).to_string(),
57 (Lang::Wgsl, IType::I32) if v as i32 == i32::MIN => "i32(-2147483648)".into(),
58 (Lang::Rust, IType::I32) if v as i32 == i32::MIN => "i32::MIN".into(),
59 (Lang::Wgsl, IType::I32) if (v as i32) < 0 => format!("({}i)", v as i32),
60 (Lang::Wgsl, IType::I32) => format!("{}i", v as i32),
61 (Lang::Rust, IType::I32) if (v as i32) < 0 => format!("({}i32)", v as i32),
62 (Lang::Rust, IType::I32) => format!("{}i32", v as i32),
63 (Lang::Wgsl, IType::U32) => format!("{v}u"),
64 (Lang::Rust, IType::U32) => format!("{v}u32"),
65 }
66 }
67
68 fn r(&self, id: IId) -> String {
70 match self.g.node(id) {
71 INode::Const(t, v) => self.lit(t, v),
72 INode::Param(p, _) => format!("({})", self.params[p as usize]),
73 INode::State(l, k) => format!("st{l}_{k}"),
74 INode::LoopOut(l, k) => format!("s{l}_{k}"),
75 INode::Tok => self.lit(IType::U32, 0),
76 _ if self.sched.inline[id.index()] => self.def(id),
77 _ => format!("t{}", id.0),
78 }
79 }
80
81 fn def(&self, id: IId) -> String {
82 let g = self.g;
83 let w = self.lang == Lang::Wgsl;
84 match g.node(id) {
85 INode::Bin(op, a, b) => {
86 let t = g.ty(a);
87 let (x, y) = (self.r(a), self.r(b));
88 let tn = ty_name(self.lang, t);
89 if w {
90 let amt = |y: String| {
91 if g.ty(b) == IType::U32 {
92 y
93 } else {
94 format!("bitcast<u32>({y})")
95 }
96 };
97 match op {
98 BinOp::Add => format!("({x} + {y})"),
99 BinOp::Sub => format!("({x} - {y})"),
100 BinOp::Mul => format!("({x} * {y})"),
101 BinOp::MulHi => format!("tx_mulhi_{tn}({x}, {y})"),
102 BinOp::And => format!("({x} & {y})"),
103 BinOp::Or => format!("({x} | {y})"),
104 BinOp::Xor if t == IType::Bool => format!("({x} != {y})"),
105 BinOp::Xor => format!("({x} ^ {y})"),
106 BinOp::Shl => format!("({x} << {})", amt(y)),
107 BinOp::Shr => format!("({x} >> {})", amt(y)),
108 BinOp::Min => format!("min({x}, {y})"),
109 BinOp::Max => format!("max({x}, {y})"),
110 BinOp::Div => format!("({x} / {y})"),
111 BinOp::Rem => format!("({x} % {y})"),
112 }
113 } else {
114 match op {
115 BinOp::Add => format!("{x}.wrapping_add({y})"),
116 BinOp::Sub => format!("{x}.wrapping_sub({y})"),
117 BinOp::Mul => format!("{x}.wrapping_mul({y})"),
118 BinOp::MulHi if t == IType::I32 => {
119 format!("(((({x} as i64) * ({y} as i64)) >> 32) as i32)")
120 }
121 BinOp::MulHi => format!("(((({x} as u64) * ({y} as u64)) >> 32) as u32)"),
122 BinOp::And => format!("({x} & {y})"),
123 BinOp::Or => format!("({x} | {y})"),
124 BinOp::Xor => format!("({x} ^ {y})"),
125 BinOp::Shl => format!("{x}.wrapping_shl({y} as u32)"),
126 BinOp::Shr => format!("{x}.wrapping_shr({y} as u32)"),
127 BinOp::Min => format!("{x}.min({y})"),
128 BinOp::Max => format!("{x}.max({y})"),
129 BinOp::Div => {
130 format!("(if {y} == 0 {{ {x} }} else {{ {x}.wrapping_div({y}) }})")
131 }
132 BinOp::Rem => {
133 format!("(if {y} == 0 {{ 0 }} else {{ {x}.wrapping_rem({y}) }})")
134 }
135 }
136 }
137 }
138 INode::Un(op, a) => {
139 let x = self.r(a);
140 match (op, g.ty(a), self.lang) {
141 (UnOp::Neg, IType::U32, Lang::Wgsl) => format!("(0u - {x})"),
142 (UnOp::Neg, _, Lang::Wgsl) => format!("(-{x})"),
143 (UnOp::Neg, _, Lang::Rust) => format!("{x}.wrapping_neg()"),
144 (UnOp::Not, IType::Bool, _) | (UnOp::Not, _, Lang::Rust) => format!("(!{x})"),
145 (UnOp::Not, _, Lang::Wgsl) => format!("(~{x})"),
146 (UnOp::Clz, _, Lang::Wgsl) => format!("countLeadingZeros({x})"),
147 (UnOp::Clz, t, Lang::Rust) => {
148 format!("({x}.leading_zeros() as {})", ty_name(Lang::Rust, t))
149 }
150 }
151 }
152 INode::Cmp(op, a, b) => {
153 let s = match op {
154 CmpOp::Eq => "==",
155 CmpOp::Ne => "!=",
156 CmpOp::Lt => "<",
157 CmpOp::Le => "<=",
158 };
159 format!("({} {s} {})", self.r(a), self.r(b))
160 }
161 INode::Select(c, a, b) => {
162 if w {
163 format!("select({}, {}, {})", self.r(b), self.r(a), self.r(c))
164 } else {
165 format!(
166 "(if {} {{ {} }} else {{ {} }})",
167 self.r(c),
168 self.r(a),
169 self.r(b)
170 )
171 }
172 }
173 INode::Bitcast(t, a) => {
174 if w {
175 format!("bitcast<{}>({})", ty_name(self.lang, t), self.r(a))
176 } else {
177 format!("({} as {})", self.r(a), ty_name(self.lang, t))
178 }
179 }
180 INode::Load(b, i) => {
181 let buf = &g.bufs[b as usize];
182 let (n, i) = (&buf.name, self.r(i));
183 if w && self.guard {
184 let (word, z) = match buf.elem {
185 Elem::I32 => (i.to_string(), "0i"),
186 Elem::U32 => (i.to_string(), "0u"),
187 Elem::U8 => (format!("({i} >> 2u)"), "0u"),
188 Elem::I8 => (format!("({i} >> 2u)"), "0i"),
189 };
190 let v = match buf.elem {
191 Elem::I32 | Elem::U32 => format!("{n}[{i}]"),
192 Elem::U8 => format!("extractBits({n}[{i} >> 2u], ({i} & 3u) * 8u, 8u)"),
193 Elem::I8 => format!(
194 "extractBits(bitcast<i32>({n}[{i} >> 2u]), ({i} & 3u) * 8u, 8u)"
195 ),
196 };
197 return format!("select({z}, {v}, {word} < arrayLength(&{n}))");
198 }
199 if !w {
200 let get = |j: String| format!("{n}.get({j} as usize).copied().unwrap_or(0)");
201 return match buf.elem {
202 Elem::U32 => get(i.clone()),
203 Elem::I32 => format!("({} as i32)", get(i.clone())),
204 Elem::U8 => format!(
205 "(({} >> (({i} & 3) * 8)) & 255)",
206 get(format!("({i} >> 2)"))
207 ),
208 Elem::I8 => format!(
209 "(({} >> (({i} & 3) * 8)) as u8 as i8 as i32)",
210 get(format!("({i} >> 2)"))
211 ),
212 };
213 }
214 match (buf.elem, self.lang) {
215 (Elem::I32 | Elem::U32, Lang::Wgsl) => format!("{n}[{i}]"),
216 (Elem::U8, Lang::Wgsl) => {
217 format!("extractBits({n}[{i} >> 2u], ({i} & 3u) * 8u, 8u)")
218 }
219 (Elem::I8, Lang::Wgsl) => {
220 format!("extractBits(bitcast<i32>({n}[{i} >> 2u]), ({i} & 3u) * 8u, 8u)")
221 }
222 (Elem::U32, Lang::Rust) => format!("{n}[{i} as usize]"),
223 (Elem::I32, Lang::Rust) => format!("({n}[{i} as usize] as i32)"),
224 (Elem::U8, Lang::Rust) => {
225 format!("(({n}[({i} >> 2) as usize] >> (({i} & 3) * 8)) & 255)")
226 }
227 (Elem::I8, Lang::Rust) => format!(
228 "(({n}[({i} >> 2) as usize] >> (({i} & 3) * 8)) as u8 as i8 as i32)"
229 ),
230 }
231 }
232 INode::LoadRw(b, i, _) => {
233 let buf = &g.bufs[b as usize];
234 let (len, base) = buf.rw.as_ref().unwrap();
235 let (n, i) = (&buf.name, self.r(i));
236 if w {
237 let z = if buf.elem == Elem::I32 { "0i" } else { "0u" };
238 if self.guard {
239 return format!(
240 "select({z}, {n}[({base}) + min({i}, {}u)], {i} < {len}u)",
241 len.saturating_sub(1)
242 );
243 }
244 return format!("{n}[({base}) + {i}]");
245 }
246 let cast = if buf.elem == Elem::I32 { " as i32" } else { "" };
247 format!("((if {i} < {len}u32 {{ {n}.get({i} as usize).copied().unwrap_or(0) }} else {{ 0 }}){cast})")
248 }
249 INode::Store(..) => self.lit(IType::U32, 0),
250 INode::Const(..)
251 | INode::Param(..)
252 | INode::State(..)
253 | INode::LoopOut(..)
254 | INode::Tok => self.r(id),
255 }
256 }
257
258 fn store(&self, b: u16, i: IId, v: IId) -> String {
260 let buf = &self.g.bufs[b as usize];
261 let (len, base) = buf.rw.as_ref().unwrap();
262 let (n, i, v) = (&buf.name, self.r(i), self.r(v));
263 match self.lang {
264 Lang::Wgsl if self.guard => format!("if ({i} < {len}u) {{ {n}[({base}) + {i}] = {v}; }}"),
265 Lang::Wgsl => format!("{n}[({base}) + {i}] = {v};"),
266 Lang::Rust => format!("if {i} < {len}u32 {{ if let Some(w) = {n}.get_mut({i} as usize) {{ *w = {v} as u32; }} }}"),
267 }
268 }
269
270 fn line(&mut self, depth: usize, s: &str) {
271 for _ in 0..depth {
272 self.out.push_str(" ");
273 }
274 self.out.push_str(s);
275 self.out.push('\n');
276 }
277
278 fn items(&mut self, items: &[Item], depth: usize) {
279 for it in items {
280 match it {
281 Item::Node(id) => {
282 let id = *id;
283 let skip = matches!(
284 self.g.node(id),
285 INode::Const(..)
286 | INode::Param(..)
287 | INode::State(..)
288 | INode::LoopOut(..)
289 | INode::Tok
290 );
291 if skip || self.sched.inline[id.index()] {
292 continue;
293 }
294 if let INode::Store(b, i, v, _) = self.g.node(id) {
295 let s = self.store(b, i, v);
296 self.line(depth, &s);
297 }
298 let s = format!(
299 "let t{}: {} = {};",
300 id.0,
301 ty_name(self.lang, self.g.ty(id)),
302 self.def(id)
303 );
304 self.line(depth, &s);
305 }
306 Item::Loop { l, pre, post } => self.lp(*l, pre, post, depth),
307 }
308 }
309 }
310
311 fn lp(&mut self, l: u32, pre: &[Item], post: &[Item], depth: usize) {
312 let g = self.g;
313 let lp = &g.loops[l as usize];
314 for (k, &i) in lp.init.iter().enumerate() {
315 let t = ty_name(self.lang, g.ty(i));
316 let s = match self.lang {
317 Lang::Wgsl => format!("var s{l}_{k}: {t} = {};", self.r(i)),
318 Lang::Rust => format!("let mut s{l}_{k}: {t} = {};", self.r(i)),
319 };
320 self.line(depth, &s);
321 }
322 if lp.cap.is_some() {
323 let s = match self.lang {
324 Lang::Wgsl => format!("var c{l}: u32 = 0u;"),
325 Lang::Rust => format!("let mut c{l}: u32 = 0;"),
326 };
327 self.line(depth, &s);
328 }
329 self.line(depth, "loop {");
330 if let Some(cap) = lp.cap {
331 self.line(
332 depth + 1,
333 &format!("if c{l} >= {} {{ break; }}", self.lit(IType::U32, cap)),
334 );
335 self.line(
336 depth + 1,
337 &format!("c{l} = c{l} + {};", self.lit(IType::U32, 1)),
338 );
339 }
340 for k in 0..lp.init.len() {
341 self.line(depth + 1, &format!("let st{l}_{k} = s{l}_{k};"));
342 }
343 self.items(pre, depth + 1);
344 let c = self.r(lp.cond);
345 self.line(depth + 1, &format!("if !{} {{ break; }}", paren(&c)));
346 self.items(post, depth + 1);
347 for (k, &x) in lp.next.iter().enumerate() {
348 let s = format!("s{l}_{k} = {};", self.r(x));
349 self.line(depth + 1, &s);
350 }
351 self.line(depth, "}");
352 }
353}
354
355fn paren(s: &str) -> String {
356 if s.starts_with('(') && s.ends_with(')') {
357 s.to_string()
358 } else {
359 format!("({s})")
360 }
361}
362
363impl IntGraph {
364 fn emitter(&self, lang: Lang, outputs: &[IId], params: &[&str]) -> Em<'_> {
365 Em {
366 g: self,
367 lang,
368 sched: self.schedule(outputs),
369 params: params.iter().map(|s| s.to_string()).collect(),
370 out: String::new(),
371 guard: false,
372 }
373 }
374
375 pub(crate) fn nparams(&self) -> Vec<IType> {
376 let mut t: Vec<IType> = Vec::new();
377 for n in &self.nodes {
378 if let INode::Param(p, ty) = *n {
379 if t.len() <= p as usize {
380 t.resize(p as usize + 1, IType::U32);
381 }
382 t[p as usize] = ty;
383 }
384 }
385 t
386 }
387
388 pub fn to_wgsl_body(
393 &self,
394 outputs: &[IId],
395 params: &[&str],
396 dests: &[&str],
397 depth: usize,
398 ) -> String {
399 let mut e = self.emitter(Lang::Wgsl, outputs, params);
400 let items = std::mem::take(&mut e.sched.items);
401 e.items(&items, depth);
402 let rs: Vec<String> = outputs.iter().map(|o| e.r(*o)).collect();
405 for (k, r) in rs.iter().enumerate() {
406 e.line(depth, &format!("let r{k} = {r};"));
407 }
408 for (k, d) in dests.iter().enumerate().take(outputs.len()) {
409 e.line(depth, &format!("{d} = r{k};"));
410 }
411 e.out
412 }
413
414 pub fn to_wgsl_body_guarded(
418 &self,
419 outputs: &[IId],
420 params: &[&str],
421 dests: &[&str],
422 depth: usize,
423 ) -> String {
424 let mut e = self.emitter(Lang::Wgsl, outputs, params);
425 e.guard = true;
426 let items = std::mem::take(&mut e.sched.items);
427 e.items(&items, depth);
428 let rs: Vec<String> = outputs.iter().map(|o| e.r(*o)).collect();
429 for (k, r) in rs.iter().enumerate() {
430 e.line(depth, &format!("let r{k} = {r};"));
431 }
432 for (k, d) in dests.iter().enumerate().take(outputs.len()) {
433 e.line(depth, &format!("{d} = r{k};"));
434 }
435 e.out
436 }
437
438 pub fn to_wgsl_fn(&self, name: &str, outputs: &[IId]) -> String {
441 let pt = self.nparams();
442 let pn: Vec<String> = (0..pt.len()).map(|i| format!("p{i}")).collect();
443 let pr: Vec<&str> = pn.iter().map(|s| s.as_str()).collect();
444 let mut s = format!("struct {name}_out {{\n");
445 for (k, o) in outputs.iter().enumerate() {
446 let _ = writeln!(s, " o{k}: {},", ty_name(Lang::Wgsl, self.ty(*o)));
447 }
448 s.push_str("}\n");
449 let args: Vec<String> = pt
450 .iter()
451 .enumerate()
452 .map(|(i, t)| format!("p{i}: {}", ty_name(Lang::Wgsl, *t)))
453 .collect();
454 let _ = writeln!(s, "fn {name}({}) -> {name}_out {{", args.join(", "));
455 let dn: Vec<String> = (0..outputs.len()).map(|k| format!("r.o{k}")).collect();
456 let dr: Vec<&str> = dn.iter().map(|s| s.as_str()).collect();
457 s.push_str(&format!(" var r: {name}_out;\n"));
458 s.push_str(&self.to_wgsl_body(outputs, &pr, &dr, 1));
459 s.push_str(" return r;\n}\n");
460 s
461 }
462
463 pub fn to_rust(&self, name: &str, outputs: &[IId]) -> String {
467 let pt = self.nparams();
468 let pn: Vec<String> = (0..pt.len()).map(|i| format!("p{i}")).collect();
469 let pr: Vec<&str> = pn.iter().map(|s| s.as_str()).collect();
470 let mut args: Vec<String> = pt
471 .iter()
472 .enumerate()
473 .map(|(i, t)| format!("p{i}: {}", ty_name(Lang::Rust, *t)))
474 .collect();
475 args.extend(self.bufs.iter().map(|b| {
476 format!(
477 "{}: &{}[u32]",
478 b.name,
479 if b.rw.is_some() { "mut " } else { "" }
480 )
481 }));
482 let rt: Vec<&str> = outputs
483 .iter()
484 .map(|o| ty_name(Lang::Rust, self.ty(*o)))
485 .collect();
486 let mut e = self.emitter(Lang::Rust, outputs, &pr);
487 let same = rt.windows(2).all(|w| w[0] == w[1]) && !rt.is_empty();
489 let ret = if same {
490 format!("[{}; {}]", rt[0], rt.len())
491 } else {
492 format!("({},)", rt.join(", "))
493 };
494 e.out = format!(
495 "#[allow(unused, clippy::all)]\npub fn {name}({}) -> {ret} {{\n",
496 args.join(", ")
497 );
498 let items = std::mem::take(&mut e.sched.items);
499 e.items(&items, 1);
500 let rs: Vec<String> = outputs.iter().map(|o| e.r(*o)).collect();
501 let s = if same {
502 format!("[{}]", rs.join(", "))
503 } else {
504 format!("({},)", rs.join(", "))
505 };
506 e.line(1, &s);
507 e.out.push_str("}\n");
508 e.out
509 }
510}