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 disasm;
29pub mod module;
30pub mod psv;
31pub mod rdat;
32pub mod reflection;
33pub mod sampling;
34
35pub use module::Module;
36
37#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
40#[error("DXIL at bit {bit}: {detail}")]
41pub struct Error {
42 pub bit: usize,
44 pub detail: String,
46}
47
48pub type Result<T> = std::result::Result<T, Error>;
50
51pub(crate) fn error(bit: usize, detail: impl Into<String>) -> Error {
52 Error {
53 bit,
54 detail: detail.into(),
55 }
56}
57
58#[derive(Clone, Copy, Debug)]
60pub struct Part<'a> {
61 pub program_version: u32,
63 pub dxil_version: u32,
65 pub bitcode: &'a [u8],
67}
68
69impl Part<'_> {
70 pub fn target(&self) -> Option<String> {
74 let prefix = match self.program_version >> 16 {
75 0 => "ps",
76 1 => "vs",
77 2 => "gs",
78 3 => "hs",
79 4 => "ds",
80 5 => "cs",
81 6 => "lib",
82 13 => "ms",
83 14 => "as",
84 _ => return None,
85 };
86 let (major, minor) = ((self.program_version >> 4) & 0xf, self.program_version & 0xf);
87 Some(format!("{prefix}_{major}_{minor}"))
88 }
89}
90
91pub fn parts(container: &[u8]) -> Result<Vec<dxcontainer::Chunk<'_>>> {
93 dxcontainer::chunks(container).map_err(|e| error(e.offset.saturating_mul(8), e.detail))
94}
95
96pub fn program(data: &[u8]) -> Result<Part<'_>> {
98 let view = primase_foundations::bytes::View::new(data);
99 let word = |at: usize| -> Result<u32> {
100 view.array::<4>(at)
101 .map(u32::from_le_bytes)
102 .map_err(|e| error(at.saturating_mul(8), e.to_string()))
103 };
104 let program_version = word(0)?;
105 let dwords = word(4)? as usize;
106 if dwords.checked_mul(4) != Some(data.len()) {
107 return Err(error(32, "program size disagrees with the part"));
108 }
109 if view.slice(8, 4).map_err(|e| error(64, e.to_string()))? != b"DXIL" {
110 return Err(error(64, "program header magic"));
111 }
112 let dxil_version = word(12)?;
113 let offset = word(16)? as usize;
115 let size = word(20)? as usize;
116 let bitcode = offset
117 .checked_add(8)
118 .and_then(|start| view.slice(start, size).ok())
119 .ok_or_else(|| error(128, "bitcode outside the part"))?;
120 Ok(Part {
121 program_version,
122 dxil_version,
123 bitcode,
124 })
125}
126
127pub fn part(container: &[u8]) -> Result<Part<'_>> {
129 let chunks = parts(container)?;
130 let data = chunks
131 .iter()
132 .find(|c| &c.kind == b"DXIL")
133 .ok_or_else(|| error(0, "no DXIL part"))?
134 .data;
135 program(data)
136}
137
138pub fn module(chunks: &[dxcontainer::Chunk<'_>], kind: &[u8; 4]) -> Result<Option<Module>> {
140 let Some(c) = chunks.iter().find(|c| &c.kind == kind) else {
141 return Ok(None);
142 };
143 read_module(&program(c.data)?).map(Some)
144}
145
146fn read_module(part: &Part<'_>) -> Result<Module> {
147 let blocks = bitstream::read(part.bitcode, *b"BC\xc0\xde")?;
148 Module::read(&blocks)
149}
150
151pub fn operations(container: &[u8]) -> Result<(u32, std::collections::BTreeMap<String, usize>)> {
155 use module::Op;
156 let chunks = parts(container)?;
157 let part = program(
158 chunks
159 .iter()
160 .find(|c| &c.kind == b"DXIL")
161 .ok_or_else(|| error(0, "no DXIL part"))?
162 .data,
163 )?;
164 let module = read_module(&part)?;
165 let mut out = std::collections::BTreeMap::new();
166 for f in &module.functions {
167 for inst in &f.instructions {
168 let Op::Call { function, args, .. } = &inst.op else {
169 continue;
170 };
171 let Some(name) = module.name(*function).filter(|n| n.starts_with("dx.op.")) else {
172 continue;
173 };
174 let code = args.first().and_then(|&a| {
175 let value = match a.checked_sub(f.first) {
176 None => module.values.get(a),
177 Some(local) => f.locals.get(local),
178 };
179 match value {
180 Some(module::Value::Constant(module::Constant::Integer(i))) => Some(*i),
181 _ => None,
182 }
183 });
184 let key = match code {
185 Some(code) => format!("{name} (op {code})"),
186 None => name.to_owned(),
187 };
188 out.entry(key)
189 .and_modify(|n: &mut usize| *n = n.saturating_add(1))
190 .or_insert(1);
191 }
192 }
193 Ok((part.program_version >> 16, out))
194}