dxil-reader 0.8.0

DXIL reader: decodes Direct3D Shader Model 6 programs (LLVM bitcode in a DXBC container) to modules, with resource, signature and RDAT reflection records
Documentation
#![allow(clippy::unwrap_used)]
//! The DXIL reader on the control-flow and memory fixtures (dxc 1.9.2609, default optimisation and -Od): a switch is read as
//! a switch, a local array as an alloca with getelementptr, loads and stores; and every committed fixture reads.
use dxil_reader::module::{Module, Op};

fn module(name: &str) -> Module {
    let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name);
    let bytes = std::fs::read(path).unwrap();
    let chunks = dxcontainer::chunks(&bytes).unwrap();
    let dxil = chunks.iter().find(|c| &c.kind == b"DXIL").unwrap();
    let part = dxil_reader::program(dxil.data).unwrap();
    let blocks = dxil_reader::bitstream::read(part.bitcode, *b"BC\xc0\xde").unwrap();
    Module::read(&blocks).unwrap()
}

fn count(m: &Module, pred: impl Fn(&Op, usize) -> bool) -> usize {
    m.functions
        .iter()
        .flat_map(|f| f.instructions.iter())
        .filter(|i| pred(&i.op, i.block))
        .count()
}

#[test]
fn a_switch_is_a_switch_with_its_cases() {
    for name in ["switch.dxil", "switch.od.dxil"] {
        let m = module(name);
        assert_eq!(count(&m, |op, _| matches!(op, Op::Switch { .. })), 2, "{name}");
        let widest = m
            .functions
            .iter()
            .flat_map(|f| &f.instructions)
            .filter_map(|i| match &i.op {
                Op::Switch { cases, .. } => Some(cases.len()),
                _ => None,
            })
            .max();
        assert_eq!(
            widest,
            Some(4),
            "{name}: the first switch has the labels 0, 1, 2 and 5"
        );
    }
}

#[test]
fn a_local_array_is_memory() {
    for name in ["arrays.dxil", "arrays.od.dxil"] {
        let m = module(name);
        assert_eq!(count(&m, |op, _| matches!(op, Op::Alloca { .. })), 1, "{name}");
        assert!(count(&m, |op, _| matches!(op, Op::Store { .. })) >= 4, "{name}");
        assert!(count(&m, |op, _| matches!(op, Op::Gep { .. })) >= 4, "{name}");
        // the constant table is a global with an initial value
        let tables = m
            .values
            .iter()
            .filter(|v| {
                matches!(
                    v,
                    dxil_reader::module::Value::Global {
                        init: Some(_),
                        constant: true,
                        ..
                    }
                )
            })
            .count();
        assert!(tables >= 1, "{name}: {tables} constant tables");
    }
}

#[test]
fn every_fixture_reads() {
    let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures");
    let mut n = 0;
    for e in std::fs::read_dir(dir).unwrap() {
        let p = e.unwrap().path();
        if p.extension().is_some_and(|x| x == "dxil") {
            module(&p.file_name().unwrap().to_string_lossy());
            n += 1;
        }
    }
    assert!(n >= 17, "{n} fixtures");
}

#[test]
fn a_pointer_type_keeps_its_address_space() {
    use dxil_reader::module::{Type, Value};
    for name in ["compute_basic.dxil", "compute_basic.od.dxil"] {
        let m = module(name);
        // a global's type is the pointer to its value in the global's own space, group shared memory in space 3
        let mut spaces = std::collections::BTreeSet::new();
        for v in &m.values {
            if let Value::Global {
                ty,
                value_ty,
                address_space,
                ..
            } = v
            {
                assert_eq!(m.types[*ty], Type::Pointer(*value_ty, *address_space), "{name}");
                spaces.insert(*address_space);
            }
        }
        assert!(
            spaces.contains(&3),
            "{name}: group shared globals are in space 3, found {spaces:?}"
        );
        let pointers: Vec<_> = m
            .types
            .iter()
            .filter_map(|t| match t {
                Type::Pointer(to, space) => Some((*to, *space)),
                _ => None,
            })
            .collect();
        let mut seen = std::collections::BTreeSet::new();
        assert!(
            pointers.iter().all(|p| seen.insert(*p)),
            "{name}: a pointer type is in the table twice"
        );
        // the type table of the -Od build has pointers to one pointee in both spaces (i32 addrspace(3)* and the i32* of
        // @dx.nothing.a): two types, not one
        if name == "compute_basic.od.dxil" {
            assert!(
                pointers
                    .iter()
                    .any(|&(to, space)| space == 3 && pointers.contains(&(to, 0))),
                "{name}: {pointers:?}"
            );
        }
    }
}