1mod emit;
35mod eval;
36mod sched;
37mod wire;
38
39pub use emit::WGSL_PRELUDE;
40pub use eval::Program;
41pub use wire::{Limits, WireError};
42
43use std::collections::HashMap;
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47pub enum IType {
48 I32,
50 U32,
52 Bool,
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub enum Elem {
59 I8,
61 U8,
63 I32,
65 U32,
67}
68
69impl Elem {
70 pub fn ty(self) -> IType {
72 match self {
73 Elem::I8 | Elem::I32 => IType::I32,
74 Elem::U8 | Elem::U32 => IType::U32,
75 }
76 }
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
81pub struct IId(pub(crate) u32);
82
83impl IId {
84 pub fn index(self) -> usize {
86 self.0 as usize
87 }
88}
89
90#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
92pub enum BinOp {
93 Add,
95 Sub,
97 Mul,
99 MulHi,
101 And,
103 Or,
105 Xor,
107 Shl,
109 Shr,
111 Min,
113 Max,
115 Div,
118 Rem,
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
125pub enum CmpOp {
126 Eq,
128 Ne,
130 Lt,
132 Le,
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
138pub enum UnOp {
139 Neg,
141 Not,
143 Clz,
145}
146
147#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
149pub enum INode {
150 Const(IType, u32),
152 Param(u16, IType),
154 Bin(BinOp, IId, IId),
156 Un(UnOp, IId),
158 Cmp(CmpOp, IId, IId),
160 Select(IId, IId, IId),
162 Bitcast(IType, IId),
164 Load(u16, IId),
166 State(u32, u16),
168 LoopOut(u32, u16),
170 Tok,
172 LoadRw(u16, IId, IId),
174 Store(u16, IId, IId, IId),
177}
178
179#[derive(Clone, Debug)]
181pub struct Loop {
182 pub init: Vec<IId>,
184 pub cond: IId,
186 pub next: Vec<IId>,
188 pub parent: Option<u32>,
190 pub cap: Option<u32>,
193}
194
195#[derive(Clone, Debug)]
197pub struct Buffer {
198 pub name: String,
200 pub elem: Elem,
202 pub rw: Option<(u32, String)>,
206}
207
208#[derive(Clone, Default)]
210pub struct IntGraph {
211 nodes: Vec<INode>,
212 types: Vec<IType>,
213 scope: Vec<Option<u32>>,
215 intern: HashMap<INode, IId>,
216 loops: Vec<Loop>,
217 bufs: Vec<Buffer>,
218 building: Vec<u32>,
219}
220
221impl IntGraph {
222 pub fn new() -> Self {
224 Self::default()
225 }
226
227 pub fn len(&self) -> usize {
229 self.nodes.len()
230 }
231
232 pub fn is_empty(&self) -> bool {
234 self.nodes.is_empty()
235 }
236
237 pub fn node(&self, id: IId) -> INode {
239 self.nodes[id.index()]
240 }
241
242 pub fn ty(&self, id: IId) -> IType {
244 self.types[id.index()]
245 }
246
247 pub fn loop_(&self, l: u32) -> &Loop {
249 &self.loops[l as usize]
250 }
251
252 pub fn buffers(&self) -> &[Buffer] {
254 &self.bufs
255 }
256
257 fn depth(&self, mut l: Option<u32>) -> usize {
258 let mut d = 0;
259 while let Some(x) = l {
260 d += 1;
261 l = self.loops[x as usize].parent;
262 }
263 d
264 }
265
266 fn inner(&self, a: Option<u32>, b: Option<u32>) -> Option<u32> {
268 self.try_inner(a, b)
269 .expect("int graph: node mixes states of unrelated loops")
270 }
271
272 fn try_inner(&self, a: Option<u32>, b: Option<u32>) -> Result<Option<u32>, &'static str> {
273 let (da, db) = (self.depth(a), self.depth(b));
274 let (deep, shallow, mut d, ds) = if da >= db {
275 (a, b, da, db)
276 } else {
277 (b, a, db, da)
278 };
279 let mut x = deep;
280 while d > ds {
281 x = self.loops[x.unwrap() as usize].parent;
282 d -= 1;
283 }
284 if x != shallow {
285 return Err("node mixes states of unrelated loops");
286 }
287 Ok(deep)
288 }
289
290 fn insert(&mut self, n: INode) -> IId {
291 if let Some(&id) = self.intern.get(&n) {
292 return id;
293 }
294 let (ty, scope) = self.infer(n);
295 if let Some(folded) = self.fold(n, ty) {
296 return folded;
297 }
298 let id = IId(self.nodes.len() as u32);
299 self.nodes.push(n);
300 self.types.push(ty);
301 self.scope.push(scope);
302 self.intern.insert(n, id);
303 id
304 }
305
306 fn infer(&self, n: INode) -> (IType, Option<u32>) {
307 self.try_infer(n)
308 .unwrap_or_else(|e| panic!("int graph: {e}"))
309 }
310
311 fn try_infer(&self, n: INode) -> Result<(IType, Option<u32>), &'static str> {
312 let sc = |a: IId| self.scope[a.index()];
313 let ok = |c: bool, e: &'static str| if c { Ok(()) } else { Err(e) };
314 Ok(match n {
315 INode::Const(t, _) => (t, None),
316 INode::Param(_, t) => (t, None),
317 INode::Bin(op, a, b) => {
318 let (ta, tb) = (self.ty(a), self.ty(b));
319 match op {
320 BinOp::Shl | BinOp::Shr => {
321 ok(ta != IType::Bool && tb != IType::Bool, "shift of bool")?
322 }
323 BinOp::And | BinOp::Or | BinOp::Xor => {
324 ok(ta == tb, "bitwise op type mismatch")?
325 }
326 _ => {
327 ok(ta == tb, "arithmetic type mismatch")?;
328 ok(ta != IType::Bool, "arithmetic on bool")?;
329 }
330 }
331 (ta, self.try_inner(sc(a), sc(b))?)
332 }
333 INode::Un(op, a) => {
334 ok(
335 op == UnOp::Not || self.ty(a) != IType::Bool,
336 "integer op on bool",
337 )?;
338 (self.ty(a), sc(a))
339 }
340 INode::Cmp(_, a, b) => {
341 ok(self.ty(a) == self.ty(b), "compare type mismatch")?;
342 (IType::Bool, self.try_inner(sc(a), sc(b))?)
343 }
344 INode::Select(c, a, b) => {
345 ok(self.ty(c) == IType::Bool, "select condition must be bool")?;
346 ok(self.ty(a) == self.ty(b), "select type mismatch")?;
347 let s = self.try_inner(sc(a), sc(b))?;
348 (self.ty(a), self.try_inner(s, sc(c))?)
349 }
350 INode::Bitcast(t, a) => {
351 ok(
352 t != IType::Bool && self.ty(a) != IType::Bool,
353 "bitcast of bool",
354 )?;
355 (t, sc(a))
356 }
357 INode::Load(b, i) => {
358 ok(self.ty(i) == IType::U32, "load index must be u32")?;
359 let buf = self.bufs.get(b as usize).ok_or("buffer")?;
360 ok(buf.rw.is_none(), "plain load from a scratch buffer")?;
361 (buf.elem.ty(), sc(i))
362 }
363 INode::Tok => (IType::U32, None),
364 INode::LoadRw(b, i, t) => {
365 let buf = self.bufs.get(b as usize).ok_or("buffer")?;
366 ok(buf.rw.is_some(), "scratch load from a read-only buffer")?;
367 ok(
368 self.ty(i) == IType::U32 && self.ty(t) == IType::U32,
369 "scratch load index and token must be u32",
370 )?;
371 let s = self.try_inner(sc(i), sc(t))?;
372 ok(s == sc(t), "scratch load outside its token's scope")?;
373 (buf.elem.ty(), s)
374 }
375 INode::Store(b, i, v, t) => {
376 let buf = self.bufs.get(b as usize).ok_or("buffer")?;
377 ok(buf.rw.is_some(), "store to a read-only buffer")?;
378 ok(
379 self.ty(i) == IType::U32 && self.ty(t) == IType::U32,
380 "store index and token must be u32",
381 )?;
382 ok(self.ty(v) == buf.elem.ty(), "store value type")?;
383 let s = self.try_inner(self.try_inner(sc(i), sc(v))?, sc(t))?;
384 ok(s == sc(t), "store outside its token's scope")?;
385 (IType::U32, s)
386 }
387 INode::State(l, k) => (self.ty(self.loops[l as usize].init[k as usize]), Some(l)),
388 INode::LoopOut(l, k) => (
389 self.ty(self.loops[l as usize].init[k as usize]),
390 self.loops[l as usize].parent,
391 ),
392 })
393 }
394
395 fn fold(&mut self, n: INode, ty: IType) -> Option<IId> {
397 let c = |a: IId| match self.nodes[a.index()] {
398 INode::Const(_, v) => Some(v),
399 _ => None,
400 };
401 let v = match n {
402 INode::Bin(op, a, b) => eval::bin(op, self.ty(a), c(a)?, c(b)?),
403 INode::Un(op, a) => eval::un(op, self.ty(a), c(a)?),
404 INode::Cmp(op, a, b) => eval::cmp(op, self.ty(a), c(a)?, c(b)?),
405 INode::Select(s, a, b) => {
406 if c(s)? != 0 {
407 return Some(a);
408 } else {
409 return Some(b);
410 }
411 }
412 INode::Bitcast(_, a) => c(a)?,
413 _ => return None,
414 };
415 Some(self.insert(INode::Const(ty, v)))
416 }
417
418 pub fn i32(&mut self, v: i32) -> IId {
422 self.insert(INode::Const(IType::I32, v as u32))
423 }
424
425 pub fn u32(&mut self, v: u32) -> IId {
427 self.insert(INode::Const(IType::U32, v))
428 }
429
430 pub fn bool(&mut self, v: bool) -> IId {
432 self.insert(INode::Const(IType::Bool, v as u32))
433 }
434
435 pub fn param(&mut self, i: u16, t: IType) -> IId {
437 self.insert(INode::Param(i, t))
438 }
439
440 pub fn buffer(&mut self, name: &str, elem: Elem) -> u16 {
442 self.bufs.push(Buffer {
443 name: name.into(),
444 elem,
445 rw: None,
446 });
447 (self.bufs.len() - 1) as u16
448 }
449
450 pub fn buffer_rw(&mut self, name: &str, elem: Elem, len: u32, base: &str) -> u16 {
453 assert!(
454 matches!(elem, Elem::I32 | Elem::U32),
455 "scratch buffers hold 32-bit elements"
456 );
457 self.bufs.push(Buffer {
458 name: name.into(),
459 elem,
460 rw: Some((len, base.into())),
461 });
462 (self.bufs.len() - 1) as u16
463 }
464
465 pub fn token(&mut self) -> IId {
467 self.insert(INode::Tok)
468 }
469
470 pub fn load_rw(&mut self, buf: u16, index: IId, tok: IId) -> IId {
472 self.insert(INode::LoadRw(buf, index, tok))
473 }
474
475 pub fn store(&mut self, buf: u16, index: IId, value: IId, tok: IId) -> IId {
477 self.insert(INode::Store(buf, index, value, tok))
478 }
479
480 pub fn load(&mut self, buf: u16, index: IId) -> IId {
482 self.insert(INode::Load(buf, index))
483 }
484
485 pub fn bin(&mut self, op: BinOp, a: IId, b: IId) -> IId {
489 self.insert(INode::Bin(op, a, b))
490 }
491
492 pub fn add(&mut self, a: IId, b: IId) -> IId {
494 self.bin(BinOp::Add, a, b)
495 }
496
497 pub fn sub(&mut self, a: IId, b: IId) -> IId {
499 self.bin(BinOp::Sub, a, b)
500 }
501
502 pub fn mul(&mut self, a: IId, b: IId) -> IId {
504 self.bin(BinOp::Mul, a, b)
505 }
506
507 pub fn mul_wide(&mut self, a: IId, b: IId) -> (IId, IId) {
509 (self.bin(BinOp::Mul, a, b), self.bin(BinOp::MulHi, a, b))
510 }
511
512 pub fn and(&mut self, a: IId, b: IId) -> IId {
514 self.bin(BinOp::And, a, b)
515 }
516
517 pub fn or(&mut self, a: IId, b: IId) -> IId {
519 self.bin(BinOp::Or, a, b)
520 }
521
522 pub fn xor(&mut self, a: IId, b: IId) -> IId {
524 self.bin(BinOp::Xor, a, b)
525 }
526
527 pub fn shl(&mut self, a: IId, b: IId) -> IId {
529 self.bin(BinOp::Shl, a, b)
530 }
531
532 pub fn shr(&mut self, a: IId, b: IId) -> IId {
534 self.bin(BinOp::Shr, a, b)
535 }
536
537 pub fn min(&mut self, a: IId, b: IId) -> IId {
539 self.bin(BinOp::Min, a, b)
540 }
541
542 pub fn max(&mut self, a: IId, b: IId) -> IId {
544 self.bin(BinOp::Max, a, b)
545 }
546
547 pub fn clamp(&mut self, x: IId, lo: IId, hi: IId) -> IId {
549 let m = self.max(x, lo);
550 self.min(m, hi)
551 }
552
553 pub fn neg(&mut self, a: IId) -> IId {
555 self.insert(INode::Un(UnOp::Neg, a))
556 }
557
558 pub fn not(&mut self, a: IId) -> IId {
560 self.insert(INode::Un(UnOp::Not, a))
561 }
562
563 pub fn clz(&mut self, a: IId) -> IId {
565 self.insert(INode::Un(UnOp::Clz, a))
566 }
567
568 pub fn cmp(&mut self, op: CmpOp, a: IId, b: IId) -> IId {
570 self.insert(INode::Cmp(op, a, b))
571 }
572
573 pub fn eq(&mut self, a: IId, b: IId) -> IId {
575 self.cmp(CmpOp::Eq, a, b)
576 }
577
578 pub fn lt(&mut self, a: IId, b: IId) -> IId {
580 self.cmp(CmpOp::Lt, a, b)
581 }
582
583 pub fn le(&mut self, a: IId, b: IId) -> IId {
585 self.cmp(CmpOp::Le, a, b)
586 }
587
588 pub fn select(&mut self, c: IId, a: IId, b: IId) -> IId {
590 self.insert(INode::Select(c, a, b))
591 }
592
593 pub fn bitcast(&mut self, t: IType, a: IId) -> IId {
595 if self.ty(a) == t {
596 return a;
597 }
598 self.insert(INode::Bitcast(t, a))
599 }
600
601 pub fn while_loop(
607 &mut self,
608 init: &[IId],
609 body: impl FnOnce(&mut Self, &[IId]) -> (IId, Vec<IId>),
610 ) -> Vec<IId> {
611 let l = self.loops.len() as u32;
612 let parent = self.building.last().copied();
613 for &x in init {
614 self.inner(self.scope[x.index()], parent);
616 }
617 self.loops.push(Loop {
618 init: init.to_vec(),
619 cond: IId(0),
620 next: Vec::new(),
621 parent,
622 cap: None,
623 });
624 self.building.push(l);
625 let st: Vec<IId> = (0..init.len())
626 .map(|k| self.insert(INode::State(l, k as u16)))
627 .collect();
628 let (cond, next) = body(self, &st);
629 self.building.pop();
630 assert_eq!(self.ty(cond), IType::Bool, "loop condition must be bool");
631 assert_eq!(next.len(), init.len(), "loop next-state arity");
632 for (k, &n) in next.iter().enumerate() {
633 assert_eq!(self.ty(n), self.ty(init[k]), "loop state {k} changes type");
634 }
635 self.loops[l as usize].cond = cond;
636 self.loops[l as usize].next = next;
637 (0..init.len())
638 .map(|k| self.insert(INode::LoopOut(l, k as u16)))
639 .collect()
640 }
641
642 pub fn cap_loop(&mut self, out: IId, cap: u32) {
644 match self.node(out) {
645 INode::LoopOut(l, _) => self.loops[l as usize].cap = Some(cap),
646 _ => panic!("cap_loop: not a loop result"),
647 }
648 }
649
650 pub fn div(&mut self, a: IId, b: IId) -> IId {
652 self.bin(BinOp::Div, a, b)
653 }
654
655 pub fn rem(&mut self, a: IId, b: IId) -> IId {
657 self.bin(BinOp::Rem, a, b)
658 }
659
660 pub fn for_range(
662 &mut self,
663 n: IId,
664 init: &[IId],
665 body: impl FnOnce(&mut Self, IId, &[IId]) -> Vec<IId>,
666 ) -> Vec<IId> {
667 let zero = self.u32(0);
668 let mut s0 = vec![zero];
669 s0.extend_from_slice(init);
670 let out = self.while_loop(&s0, |g, s| {
671 let c = g.lt(s[0], n);
672 let one = g.u32(1);
673 let mut next = vec![g.add(s[0], one)];
674 next.extend(body(g, s[0], &s[1..]));
675 (c, next)
676 });
677 out[1..].to_vec()
678 }
679}
680
681#[cfg(test)]
682mod tests;