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