iridia-exec-ir 0.5.0

Run a program of iridia's shader IR on the CPU, one invocation or a pixel quad, by the IR's own definition of each operation
Documentation
# iridia-exec-ir

Runs a program of [iridia's shader IR](../iridia-ir) on the CPU, one invocation or a pixel program's 2 x 2 quad, by the IR's own definition of each operation: the reference the passes and lowerings are checked against.

Part of [iridia](https://github.com/Kalmarv/iridia), which turns compiled shaders into programs other tools can read and write. The design is [`docs/design/ir.md`](../docs/design/ir.md). This crate does no I/O.

## What it runs

- `run(program, bound)` takes the program and a `Bound`: each input slot's lanes, each constant buffer's rows, each pipeline state resource's value, and a sampler the caller answers each `Sample` with (the texture and sampler ids, the coordinate's lanes, the level of detail). It gives a `Run`: each output slot's lanes as last stored (a lane never stored is zero, a slot never stored `None`) and whether the invocation was killed.
- `run_quad(program, bounds)` runs a pixel program's quad, a `Bound` per lane (lanes 0 and 1 the top row). A derivative is a difference between lanes: the right lane's less the left's along x, the lower's less the upper's along y, of the top row or left column when coarse (and for `Fineness::Any`, as `iridia-exec` takes it), of the lane's own when fine. An implicit-LOD sample is asked with the quad's coarse derivatives of its coordinate (`Lod::Grad`, `Lod::GradBias`). A demoted lane (a discard) goes on as a helper the others still read. The lanes run one after another with no threads, each until it meets a derivative or implicit-LOD sample whose operands are not all known, then again knowing them; lanes that do not meet it at the same operation and loop iteration are refused as divergent, and so is a run of more than `MEETING_LIMIT` meetings. `needs_quad(program)` says whether a program's results depend on its quad.
- Both body forms: every construct of a `Regions` body, and the blocks of a `Cfg` one (block parameters copied in parallel). A run of more than `STEP_LIMIT` loop iterations or block executions is refused.
- The arithmetic is the reference `iridia-exec` uses: IEEE 754 `f32` for the basic operations, the transcendental functions (`exp2`, `log2`, `sin`, `cos`, `sqrt`, `inverseSqrt`, `pow`) in `f64` rounded once, a dot product summed left to right with each step rounded. Denormals are kept. Each operation's enum decides the rest: a zero-wins multiply, a NaN rule of `min` and `max`, ties away from zero.

## Example

```rust
use iridia_ir::{
    Conventions, FloatEnv, Program, Provenance,
    interface::{Interface, Label, ScalarType, ShaderFormat, Stage, StageFacts, SystemValue, Value, Varying},
};

let entry = iridia_ir::text::parse(
    "iridia-ir 1
fn main {
  %0: vec2<f32> = input 0.xy
  %1: vec2<f32> = vec2<f32>(0x7f800000, 3.0)
  %2: vec2<f32> = fmul.zerowins %0, %1
  output 0.xy = %2
  return
}
",
)
.unwrap();
let varying = |name: &str, system| Varying {
    label: Label { name: name.into(), index: 0 },
    system,
    value: Value { scalar: ScalarType::Float, width: 2 },
    interp: None,
    used: true,
    provenance: Default::default(),
};
let program = Program {
    stage: Stage::Pixel,
    entry,
    interface: Interface {
        stage: Stage::Pixel,
        inputs: vec![varying("TEXCOORD", None)],
        outputs: vec![varying("SV_Target", Some(SystemValue::Target(0)))],
        resources: Vec::new(),
        facts: StageFacts::default(),
    },
    conventions: Conventions::D3D,
    float: FloatEnv::default(),
    provenance: Provenance {
        format: ShaderFormat::D3d9,
        target: "example".into(),
        lifter: "text".into(),
        lifter_version: 1,
        source_hash: None,
        instruction_count: None,
        policies: Vec::new(),
    },
    notes: Vec::new(),
};
iridia_ir::validate(&program).unwrap();

let mut sampler = |_| Err::<[f32; 4], String>("no textures".into());
let inputs = [[0f32, 2.0, 0.0, 0.0].map(f32::to_bits)];
let mut bound = iridia_exec_ir::Bound {
    inputs: &inputs,
    buffers: &[],
    state: &[],
    sampler: &mut sampler,
    denormals: iridia_ir::Denormals::Any,
};
let run = iridia_exec_ir::run(&program, &mut bound).unwrap();
// zero wins: 0 * inf is +0, not NaN; the lanes past the output's width are zero
assert_eq!(run.outputs[0], Some([0, 6f32.to_bits(), 0, 0]));
assert!(!run.killed);
```

## Guarantees and limits

- This subset runs 32-bit scalars and vectors, constant buffer reads, pipeline state, samples, derivatives in a quad, demotion and kill; anything else (memory other than variables and constant buffers, other widths) is refused by name, not approximated.
- A program whose float environment leaves denormals open runs by `Bound::denormals`; one that states a rule runs by it.
- `run` runs one invocation, which has no derivatives: it refuses a derivative, and an implicit level of detail is `Lod::Auto`, for the caller's sampler to decide.
- A quad's sampler may be asked a request again, once per pass that reaches it, and must answer it the same way each time.

## Tests

`cargo test -p iridia-exec-ir` needs no game files: `tests/fixtures.rs` runs programs in IR text on inputs whose results are worked by hand from the operations' definitions (the NaN and rounding rules, zero-wins multiplication, saturating conversion, lane masks, a loop, a kill; a quad's coarse and fine derivatives, a derivative in a loop, a divergent quad refused, an implicit-LOD sample's footprint) and checks the text parser's refusals.

## Licence

GPL-3.0-or-later.