iridia-ir 0.6.0

iridia's shader IR: typed SSA values and operations in structured regions or a control flow graph, with the interface vocabulary, a validator and a text form
Documentation

iridia-ir

iridia's shader IR: typed SSA values and operations, in a function body that is either a tree of structured regions or a control flow graph with block parameters, beside the interface vocabulary a program exposes (varyings, resources, stage facts, D3D provenance).

Part of iridia, which turns compiled shaders into programs other tools can read and write. Readers lift into this IR; passes (iridia-passes) rewrite it; writers (iridia-naga) lower it; iridia-exec-ir runs it by its own definitions. The design is docs/design/ir.md. This crate is no_std with alloc and does no I/O.

What it holds

  • A Program: its entry Function, Interface, Conventions (clip depth, window and texture origin), FloatEnv (denormal controls per float type), Provenance (format, target, lifter and version, source hash, the compatibility policies the lifter chose) and notes.
  • A Function owns its values (ValueDef: a Type, an Op, a source Origin, a Precision permission such as D3D9's _pp) and its variables (source registers until promotion). Its Body is Regions (If, Block and Loop constructs whose regions end in a yield, a labelled break or continue carrying values, a return, a kill or unreachable) or Cfg (blocks with parameters, ending in a jump, a two-way branch, a return, a kill or unreachable).
  • Operations whose meaning differs between sources carry the rule as an enum and have no default: MinMaxNan, SaturateNan, MulZero (IEEE or D3D9's zero-wins multiply), OnZero, FloatToInt, Rounding (with ties-away for D3D9 mova), OutOfBounds, Level.
  • Interface inputs and outputs are read and written by lane mask (LoadInput(slot, lanes), StoreOutput(slot, lanes, value)); a lane never stored is zero. Fixed-function state the code depends on is a declared resource (ResourceKind::PipelineState, a StateParam such as D3D9's projected-texture flag) read by LoadState.
  • Node::Demote discards a pixel and lets it go on as a helper of its quad (D3D's texkill and discard, WGSL's discard); a kill also ends it.
  • validate checks types, the dominance of every use (regions by scope, graphs by dominator tree; a block nothing reaches is dominated by every block, and its code is still checked), labels and exits, the interface each load and store names, and the stage's rules (a kill, a demotion, a derivative and an implicit-level sample are pixel-only). It returns every problem it finds.
  • text::print writes a function one line per node, and text::parse reads it back: printing what it reads gives the same text. The first line names the schema version; the text is for fixtures, diffs and notes, not a storage format.

Example

use iridia_ir::{Builder, End, Op, Type};

let mut b = Builder::new("main");
let x = b.value(Type::F32, Op::LoadInput(0, 0b1));
let two = b.f32(2.0);
let y = b.op(Op::Binary(iridia_ir::Binary::FMul(iridia_ir::MulZero::Ieee), x, two)).unwrap();
b.store_output(0, 0b1, y);
let f = b.finish(End::Return).unwrap();

let text = iridia_ir::text::print(&f);
assert_eq!(
    text,
    "iridia-ir 1\nfn main {\n  %0: f32 = input 0.x\n  %1: f32 = f32(2.0)\n  %2: f32 = fmul.ieee %0, %1\n  output 0.x = %2\n  return\n}\n"
);
let back = iridia_ir::text::parse(&text).unwrap();
assert_eq!(iridia_ir::text::print(&back), text);

Guarantees and limits

  • A program the validator accepts has one meaning for every input, including NaN, infinities, signed zero and out-of-range values, where its operations' enums say what the source does; the reference for that meaning is iridia-exec-ir.
  • The text form states everything an operation means but not source origins, and a value the body never places is not written.
  • The operation set is what the readers on it need so far (Direct3D 9 shader model 3); it grows with them.

Licence

GPL-3.0-or-later.