#![deny(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
clippy::cast_possible_truncation
)]
#![deny(missing_docs)]
#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
pub mod bitstream;
pub mod module;
pub mod psv;
pub mod rdat;
pub mod reflection;
pub use module::Module;
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("DXIL at bit {bit}: {detail}")]
pub struct Error {
pub bit: usize,
pub detail: String,
}
pub type Result<T> = std::result::Result<T, Error>;
pub(crate) fn error(bit: usize, detail: impl Into<String>) -> Error {
Error {
bit,
detail: detail.into(),
}
}
#[derive(Clone, Copy, Debug)]
pub struct Part<'a> {
pub program_version: u32,
pub dxil_version: u32,
pub bitcode: &'a [u8],
}
pub fn parts(container: &[u8]) -> Result<Vec<dxcontainer::Chunk<'_>>> {
dxcontainer::chunks(container).map_err(|e| error(e.offset.saturating_mul(8), e.detail))
}
pub fn program(data: &[u8]) -> Result<Part<'_>> {
let view = primase_foundations::bytes::View::new(data);
let word = |at: usize| -> Result<u32> {
view.array::<4>(at)
.map(u32::from_le_bytes)
.map_err(|e| error(at.saturating_mul(8), e.to_string()))
};
let program_version = word(0)?;
let dwords = word(4)? as usize;
if dwords.checked_mul(4) != Some(data.len()) {
return Err(error(32, "program size disagrees with the part"));
}
if view.slice(8, 4).map_err(|e| error(64, e.to_string()))? != b"DXIL" {
return Err(error(64, "program header magic"));
}
let dxil_version = word(12)?;
let offset = word(16)? as usize;
let size = word(20)? as usize;
let bitcode = offset
.checked_add(8)
.and_then(|start| view.slice(start, size).ok())
.ok_or_else(|| error(128, "bitcode outside the part"))?;
Ok(Part {
program_version,
dxil_version,
bitcode,
})
}
pub fn part(container: &[u8]) -> Result<Part<'_>> {
let chunks = parts(container)?;
let data = chunks
.iter()
.find(|c| &c.kind == b"DXIL")
.ok_or_else(|| error(0, "no DXIL part"))?
.data;
program(data)
}
pub fn module(chunks: &[dxcontainer::Chunk<'_>], kind: &[u8; 4]) -> Result<Option<Module>> {
let Some(c) = chunks.iter().find(|c| &c.kind == kind) else {
return Ok(None);
};
read_module(&program(c.data)?).map(Some)
}
fn read_module(part: &Part<'_>) -> Result<Module> {
let blocks = bitstream::read(part.bitcode, *b"BC\xc0\xde")?;
Module::read(&blocks)
}
pub fn operations(container: &[u8]) -> Result<(u32, std::collections::BTreeMap<String, usize>)> {
use module::Op;
let chunks = parts(container)?;
let part = program(
chunks
.iter()
.find(|c| &c.kind == b"DXIL")
.ok_or_else(|| error(0, "no DXIL part"))?
.data,
)?;
let module = read_module(&part)?;
let mut out = std::collections::BTreeMap::new();
for f in &module.functions {
for inst in &f.instructions {
let Op::Call { function, args, .. } = &inst.op else {
continue;
};
let Some(name) = module.name(*function).filter(|n| n.starts_with("dx.op.")) else {
continue;
};
let code = args.first().and_then(|&a| {
let value = match a.checked_sub(f.first) {
None => module.values.get(a),
Some(local) => f.locals.get(local),
};
match value {
Some(module::Value::Constant(module::Constant::Integer(i))) => Some(*i),
_ => None,
}
});
let key = match code {
Some(code) => format!("{name} (op {code})"),
None => name.to_owned(),
};
out.entry(key)
.and_modify(|n: &mut usize| *n = n.saturating_add(1))
.or_insert(1);
}
}
Ok((part.program_version >> 16, out))
}