1#![deny(
21 clippy::arithmetic_side_effects,
22 clippy::indexing_slicing,
23 clippy::cast_possible_truncation
24)]
25#![deny(missing_docs)]
26#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
27pub mod bitstream;
28pub mod module;
29pub mod psv;
30pub mod rdat;
31pub mod reflection;
32pub mod sampling;
33
34pub use module::Module;
35
36#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
39#[error("DXIL at bit {bit}: {detail}")]
40pub struct Error {
41 pub bit: usize,
43 pub detail: String,
45}
46
47pub type Result<T> = std::result::Result<T, Error>;
49
50pub(crate) fn error(bit: usize, detail: impl Into<String>) -> Error {
51 Error {
52 bit,
53 detail: detail.into(),
54 }
55}
56
57#[derive(Clone, Copy, Debug)]
59pub struct Part<'a> {
60 pub program_version: u32,
62 pub dxil_version: u32,
64 pub bitcode: &'a [u8],
66}
67
68pub fn parts(container: &[u8]) -> Result<Vec<dxcontainer::Chunk<'_>>> {
70 dxcontainer::chunks(container).map_err(|e| error(e.offset.saturating_mul(8), e.detail))
71}
72
73pub fn program(data: &[u8]) -> Result<Part<'_>> {
75 let view = primase_foundations::bytes::View::new(data);
76 let word = |at: usize| -> Result<u32> {
77 view.array::<4>(at)
78 .map(u32::from_le_bytes)
79 .map_err(|e| error(at.saturating_mul(8), e.to_string()))
80 };
81 let program_version = word(0)?;
82 let dwords = word(4)? as usize;
83 if dwords.checked_mul(4) != Some(data.len()) {
84 return Err(error(32, "program size disagrees with the part"));
85 }
86 if view.slice(8, 4).map_err(|e| error(64, e.to_string()))? != b"DXIL" {
87 return Err(error(64, "program header magic"));
88 }
89 let dxil_version = word(12)?;
90 let offset = word(16)? as usize;
92 let size = word(20)? as usize;
93 let bitcode = offset
94 .checked_add(8)
95 .and_then(|start| view.slice(start, size).ok())
96 .ok_or_else(|| error(128, "bitcode outside the part"))?;
97 Ok(Part {
98 program_version,
99 dxil_version,
100 bitcode,
101 })
102}
103
104pub fn part(container: &[u8]) -> Result<Part<'_>> {
106 let chunks = parts(container)?;
107 let data = chunks
108 .iter()
109 .find(|c| &c.kind == b"DXIL")
110 .ok_or_else(|| error(0, "no DXIL part"))?
111 .data;
112 program(data)
113}
114
115pub fn module(chunks: &[dxcontainer::Chunk<'_>], kind: &[u8; 4]) -> Result<Option<Module>> {
117 let Some(c) = chunks.iter().find(|c| &c.kind == kind) else {
118 return Ok(None);
119 };
120 read_module(&program(c.data)?).map(Some)
121}
122
123fn read_module(part: &Part<'_>) -> Result<Module> {
124 let blocks = bitstream::read(part.bitcode, *b"BC\xc0\xde")?;
125 Module::read(&blocks)
126}
127
128pub fn operations(container: &[u8]) -> Result<(u32, std::collections::BTreeMap<String, usize>)> {
132 use module::Op;
133 let chunks = parts(container)?;
134 let part = program(
135 chunks
136 .iter()
137 .find(|c| &c.kind == b"DXIL")
138 .ok_or_else(|| error(0, "no DXIL part"))?
139 .data,
140 )?;
141 let module = read_module(&part)?;
142 let mut out = std::collections::BTreeMap::new();
143 for f in &module.functions {
144 for inst in &f.instructions {
145 let Op::Call { function, args, .. } = &inst.op else {
146 continue;
147 };
148 let Some(name) = module.name(*function).filter(|n| n.starts_with("dx.op.")) else {
149 continue;
150 };
151 let code = args.first().and_then(|&a| {
152 let value = match a.checked_sub(f.first) {
153 None => module.values.get(a),
154 Some(local) => f.locals.get(local),
155 };
156 match value {
157 Some(module::Value::Constant(module::Constant::Integer(i))) => Some(*i),
158 _ => None,
159 }
160 });
161 let key = match code {
162 Some(code) => format!("{name} (op {code})"),
163 None => name.to_owned(),
164 };
165 out.entry(key)
166 .and_modify(|n: &mut usize| *n = n.saturating_add(1))
167 .or_insert(1);
168 }
169 }
170 Ok((part.program_version >> 16, out))
171}