iridia-passes 0.5.0

Passes over iridia's shader IR: variable promotion, simplification, specialisation, structurization and target legalization
Documentation

iridia-passes

Passes over iridia's shader IR: each takes a function and gives one that means the same, in a form closer to what an output needs.

Part of iridia, which turns compiled shaders into programs other tools can read and write. A reader lifts a program into the IR, these passes rewrite it, and a writer (iridia-naga) lowers it. The design is docs/design/ir.md. This crate is no_std with alloc and does no I/O.

Passes

  • promote: variables (a source's registers) to SSA values, carried out of If and Block constructs as results and through loops as parameters. A variable a load reads before any store is undefined there, as the IR says.
  • simplify: lane bookkeeping folded (a swizzle of a swizzle is one swizzle, the compose of every lane of a value in order is that value, the compose of some of its lanes a swizzle) and values nothing uses removed.
  • specialise: pipeline state the caller knows (ResourceKind::PipelineState, such as D3D9's projected-texture flag) made literals, the branches they decide folded and the resources taken out of the interface. A state value that does not fit its parameter is refused.
  • structurize: a Cfg body made Regions, by Ramsey's structuring of reducible control flow (structure, which works on any graph of nodes and edges); an irreducible graph is refused.
  • break_flags: an exit to a construct that is not the innermost one, which targets whose break and continue are innermost-only (naga, WGSL) cannot take, routed through a jump variable: the inner constructs break, and a guard after each one passes the exit on. It returns how many exits it routed.

Example

let text = "iridia-ir 1
fn main {
  var r: f32
  %0: f32 = input 0.x
  r = %0
  %1: f32 = f32(0.0)
  %2: bool = fcmp.lt %0, %1
  if %2 {
    %3: f32 = fneg %0
    r = %3
    yield
  } else {
    yield
  }
  %4: f32 = load r
  output 0.x = %4
  return
}
";
let mut f = iridia_ir::text::parse(text).unwrap();
iridia_passes::promote(&mut f).unwrap();
iridia_passes::simplify(&mut f);
assert!(f.vars.is_empty());
// r leaves the if as its result: the negation in one arm, the input in the other
assert_eq!(
    iridia_ir::text::print(&f),
    "iridia-ir 1
fn main {
  %0: f32 = input 0.x
  %1: f32 = f32(0.0)
  %2: bool = fcmp.lt %0, %1
  if %2 {
    %3: f32 = fneg %0
    yield %3
  } else {
    yield %0
  } -> %5: f32
  output 0.x = %5
  return
}
"
);

Guarantees and limits

  • Each pass keeps the meaning of every program the validator accepts, by the IR's definitions; the tests check that by running programs before and after in iridia-exec-ir, on hand-worked fixtures, on 1,500 generated control flow graphs and on the Direct3D 9 programs of the migration gate.
  • structurize does not split nodes: an irreducible graph is refused, not made reducible.
  • specialise folds state only where the caller states its value; a program that still reads state is for a target that binds it.

Tests

cargo test -p iridia-passes needs no game files. tests/fixtures.rs checks each pass against the text it should give and by running it; tests/structurize.rs has the design's counterexample (a loop entered at two points unless the state is known), a nested loop whose exit needs routing, and the generated graphs.

Licence

GPL-3.0-or-later.