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