Skip to main content

dxil_reader/
reflection.rs

1//! The entry point's metadata, resource records, type annotations and signature parts of an SM6 container, as the
2//! records the file stores: the entry point and what its properties state (thread group, geometry, mesh, hull and
3//! domain state), its signature elements and their packing, the bound resource ranges of `dx.resources` with their
4//! tags, the field annotations of `dx.typeAnnotations`, and the rows of an `ISG1`/`OSG1`/`PSG1` part, each with DXIL's
5//! and D3D's own numbers. What the numbers mean for a graphics API is the caller's to decide.
6use crate::module::{Constant, Metadata, MetadataId, Module, Type, TypeId, Value, ValueId};
7use crate::{Result, error};
8
9/// One signature element of the entry point: its id (the `sigId` loadInput and storeOutput name), its first
10/// register and column, and its size.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Element {
13    /// The id the `loadInput` and `storeOutput` operations name the element by.
14    pub id: u32,
15    /// The semantic name.
16    pub name: String,
17    /// The first semantic index.
18    pub index: u32,
19    /// DXIL component type (5 u32, 4 i32, 9 f32, ...).
20    pub component_type: u8,
21    /// DXIL semantic kind (0 arbitrary, 1 SV_VertexID, 3 SV_Position, 13 SV_IsFrontFace, ...).
22    pub system_value: u8,
23    /// DXIL interpolation mode (1 constant, 2 linear, 4 linear noperspective, ...).
24    pub interpolation: u8,
25    /// The first register the element occupies.
26    pub row: u32,
27    /// The first component (0 x to 3 w) of that register.
28    pub column: u8,
29    /// How many registers it spans.
30    pub rows: u32,
31    /// How many components of each register it uses.
32    pub columns: u8,
33    /// An element the packer did not place (system values read another way): no register.
34    pub placed: bool,
35}
36/// A bound resource range: class (0 SRV, 1 UAV, 2 constant buffer, 3 sampler), range id, first register, count.
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct Range {
39    /// 0 SRV, 1 UAV, 2 constant buffer, 3 sampler.
40    pub class: u8,
41    /// The range id the resource operations name the range by.
42    pub id: u32,
43    /// The first register of the range.
44    pub lower: u32,
45    /// The number of registers; `u32::MAX` where the range is unbounded.
46    pub count: u32,
47    /// Constant buffers: their size in bytes; SRVs and UAVs: their shape (`DxilResourceKind`, 2 a 2D texture);
48    /// samplers: their kind (0 default, 1 comparison, 2 mono).
49    pub extra: u32,
50    /// The register space.
51    pub space: u32,
52    /// An SRV's or UAV's element type tag (`DxilComponentType`), where its tags state one.
53    pub element: Option<u32>,
54    /// A structured SRV's or UAV's element stride in bytes, where its tags state one.
55    pub stride: Option<u32>,
56    /// An unordered-access view with a hidden counter (an append or consume buffer, `IncrementCounter`).
57    pub counter: bool,
58    /// The module global a library names the resource by (a program of a library makes its handles from the
59    /// global's value).
60    pub variable: Option<ValueId>,
61}
62
63/// A metadata node being read. A problem with it is reported at the bit of the bitcode its record starts at.
64#[derive(Clone, Copy, Debug)]
65struct Node<'a> {
66    m: &'a Module,
67    id: MetadataId,
68    ops: &'a [Option<MetadataId>],
69    /// What the node is, for the messages.
70    what: &'static str,
71}
72
73impl<'a> Node<'a> {
74    /// The node `id`; where it is not a node the error is at the bit `at` (the record that refers to it).
75    fn new(m: &'a Module, at: usize, id: MetadataId, what: &'static str) -> Result<Self> {
76        match m.metadata.get(id) {
77            Some(Metadata::Node(ops)) => Ok(Node { m, id, ops, what }),
78            _ => Err(error(at, format!("{what}: expected a metadata node"))),
79        }
80    }
81    fn bit(&self) -> usize {
82        self.m.metadata_at.get(self.id).copied().unwrap_or(self.m.at)
83    }
84    /// The error for something wrong in this node.
85    fn fail(&self, detail: impl std::fmt::Display) -> crate::Error {
86        error(self.bit(), format!("{}: {detail}", self.what))
87    }
88    /// The node an operand names, which must not be null.
89    fn node(&self, id: Option<MetadataId>, what: &'static str) -> Result<Node<'a>> {
90        let id = id.ok_or_else(|| self.fail(format!("{what} is null")))?;
91        Node::new(self.m, self.bit(), id, what)
92    }
93    /// Operand `i`, which must exist (it may be null).
94    fn op(&self, i: usize) -> Result<Option<MetadataId>> {
95        self.ops
96            .get(i)
97            .copied()
98            .ok_or_else(|| self.fail(format!("node has no operand {i}")))
99    }
100    /// Operand `i`, null where the node has none.
101    fn maybe(&self, i: usize) -> Option<MetadataId> {
102        self.ops.get(i).copied().flatten()
103    }
104    /// An integer constant metadata operand.
105    fn int_of(&self, id: Option<MetadataId>, what: &str) -> Result<i64> {
106        match id.and_then(|i| self.m.metadata.get(i)) {
107            Some(Metadata::Value { value, .. }) => match self.m.values.get(*value) {
108                Some(Value::Constant(Constant::Integer(v))) => Ok(*v),
109                // Zero is stored as the null constant.
110                Some(Value::Constant(Constant::Null)) => Ok(0),
111                _ => Err(self.fail(format!("{what}: expected an integer constant"))),
112            },
113            _ => Err(self.fail(format!("{what}: expected an integer"))),
114        }
115    }
116    fn int(&self, i: usize, what: &str) -> Result<i64> {
117        self.int_of(self.op(i)?, what)
118    }
119    /// An integer operand in the range of `T`.
120    fn number_of<T: TryFrom<i64>>(&self, id: Option<MetadataId>, what: &str) -> Result<T> {
121        let v = self.int_of(id, what)?;
122        T::try_from(v).map_err(|_| self.fail(format!("{what}: {v} is out of range")))
123    }
124    fn number<T: TryFrom<i64>>(&self, i: usize, what: &str) -> Result<T> {
125        self.number_of(self.op(i)?, what)
126    }
127    /// The string an operand names: empty where it names none; an error where it is not UTF-8.
128    fn string_of(&self, id: Option<MetadataId>, what: &str) -> Result<String> {
129        match id.and_then(|i| self.m.metadata.get(i)) {
130            Some(Metadata::String(s)) => std::str::from_utf8(s).map(str::to_owned).map_err(|e| {
131                self.fail(format!(
132                    "{what}: not UTF-8 (valid up to byte {})",
133                    e.valid_up_to()
134                ))
135            }),
136            _ => Ok(String::new()),
137        }
138    }
139}
140
141/// The named metadata `name`: the bit its record starts at and the nodes it lists.
142fn named<'a>(m: &'a Module, name: &str) -> Option<(usize, &'a [MetadataId])> {
143    let i = m.named_metadata.iter().position(|(n, _)| n == name)?;
144    let (_, ids) = m.named_metadata.get(i)?;
145    Some((
146        m.named_metadata_at.get(i).copied().unwrap_or(m.at),
147        ids.as_slice(),
148    ))
149}
150
151/// The shader kind a library's entry point states (properties tag 8): the numbers of a program's kind (7 ray
152/// generation to 12 callable, 13 mesh).
153pub fn library_kind(m: &Module) -> Result<u32> {
154    let entry = the_entry(m)?;
155    let properties = entry
156        .node(entry.maybe(4), "entry point properties")
157        .map_err(|_| entry.fail("a library entry point without properties"))?;
158    for pair in properties.ops.chunks(2) {
159        let [tag, value] = pair else {
160            return Err(properties.fail("a tag without a value"));
161        };
162        if properties.int_of(*tag, "property tag")? == 8 {
163            return properties.number_of(*value, "shader kind");
164        }
165    }
166    Err(properties.fail("a library entry point that states no shader kind"))
167}
168
169/// The one entry point's record. A library also has a record with no function, which holds its resources.
170fn the_entry(m: &Module) -> Result<Node<'_>> {
171    let (at, entries) =
172        named(m, "dx.entryPoints").ok_or_else(|| error(m.at, "the module has no dx.entryPoints metadata"))?;
173    let mut with_function = Vec::new();
174    for &id in entries {
175        let entry = Node::new(m, at, id, "entry point")?;
176        if entry.op(0)?.is_some() {
177            with_function.push(entry);
178        }
179    }
180    match with_function.as_slice() {
181        [entry] => Ok(*entry),
182        _ => Err(error(
183            at,
184            format!(
185                "dx.entryPoints: expected exactly one entry point with a function, found {}",
186                with_function.len()
187            ),
188        )),
189    }
190}
191
192/// The entry point's function value.
193pub fn entry(m: &Module) -> Result<ValueId> {
194    let entry = the_entry(m)?;
195    match entry.op(0)?.and_then(|i| m.metadata.get(i)) {
196        Some(Metadata::Value { value, .. }) => Ok(*value),
197        _ => Err(entry.fail("operand 0 names no function")),
198    }
199}
200
201/// The entry point's thread group size (`numthreads`): the value of the properties' tag 4 (a mesh program's, the first
202/// field of its state, tag 9). `None` when the entry point states none (every stage but compute).
203pub fn num_threads(m: &Module) -> Result<Option<[u32; 3]>> {
204    let entry = the_entry(m)?;
205    let Some(properties) = entry.maybe(4) else {
206        return Ok(None);
207    };
208    let properties = entry.node(Some(properties), "entry point properties")?;
209    for pair in properties.ops.chunks(2) {
210        let [tag, value] = pair else {
211            return Err(properties.fail("a tag without a value"));
212        };
213        let tag = properties.int_of(*tag, "property tag")?;
214        if tag != 4 && tag != 9 {
215            continue;
216        }
217        let mut size = properties.node(*value, "numthreads")?;
218        // a mesh program states them first in its state
219        if tag == 9 {
220            size = size.node(size.op(0)?, "numthreads")?;
221        }
222        return Ok(Some([
223            size.number(0, "numthreads x")?,
224            size.number(1, "numthreads y")?,
225            size.number(2, "numthreads z")?,
226        ]));
227    }
228    Ok(None)
229}
230
231/// The entry point's shader flags: the value of the properties' tag 0, a 64-bit mask kept as stored. `None` when the
232/// entry point states none (the properties are null, or have no tag 0). The compiler writes the tag in nearly every
233/// entry point of the games read; this reader attaches no meaning to its bits.
234pub fn shader_flags(m: &Module) -> Result<Option<u64>> {
235    let entry = the_entry(m)?;
236    let Some(properties) = entry.maybe(4) else {
237        return Ok(None);
238    };
239    let properties = entry.node(Some(properties), "entry point properties")?;
240    for pair in properties.ops.chunks(2) {
241        let [tag, value] = pair else {
242            return Err(properties.fail("a tag without a value"));
243        };
244        if properties.int_of(*tag, "property tag")? == 0 {
245            let flags = properties.int_of(*value, "shader flags")?;
246            return Ok(Some(u64::from_le_bytes(flags.to_le_bytes())));
247        }
248    }
249    Ok(None)
250}
251
252/// The usage mask of each signature element (tag 3 of the element's trailing tag list: the components of the element
253/// the program reads or writes, bit 0 the element's first component, so a scalar packed into component w states 1,
254/// not 8), in the order of [`elements`]' inputs and outputs and [`patch_elements`]; `None` for an element whose trailing tag list is absent or has no tag 3. In the stock programs
255/// the only tag the lists hold is 3 (138,044 of 145,462 elements of skate.'s, 459,288 of 545,303 of Elden Ring's);
256/// any other tag is ignored.
257pub fn usage_masks(m: &Module) -> Result<[Vec<Option<u8>>; 3]> {
258    let entry = the_entry(m)?;
259    let Some(signatures) = entry.op(2)? else {
260        return Ok([Vec::new(), Vec::new(), Vec::new()]);
261    };
262    let signatures = entry.node(Some(signatures), "signatures")?;
263    let list = |i: usize| -> Result<Vec<Option<u8>>> {
264        let Some(list) = signatures.op(i)? else {
265            return Ok(Vec::new());
266        };
267        let list = signatures.node(Some(list), "signature")?;
268        list.ops
269            .iter()
270            .map(|e| {
271                let e = list.node(*e, "signature element")?;
272                let Some(tail) = e.maybe(10) else {
273                    return Ok(None);
274                };
275                let tail = e.node(Some(tail), "element tags")?;
276                let (pairs, rest) = tail.ops.as_chunks::<2>();
277                if !rest.is_empty() {
278                    return Err(tail.fail("a tag without its value"));
279                }
280                let mut mask = None;
281                for [tag, value] in pairs {
282                    if tail.int_of(*tag, "element tag")? == 3 {
283                        mask = Some(tail.number_of(*value, "usage mask")?);
284                    }
285                }
286                Ok(mask)
287            })
288            .collect()
289    };
290    Ok([list(0)?, list(1)?, list(2)?])
291}
292
293/// The entry point's input and output elements, by id.
294pub fn elements(m: &Module) -> Result<(Vec<Element>, Vec<Element>)> {
295    let [inputs, outputs, _] = signature_lists(m)?;
296    Ok((inputs, outputs))
297}
298
299/// A hull or domain program's patch-constant elements, by id.
300pub fn patch_elements(m: &Module) -> Result<Vec<Element>> {
301    let [_, _, patch] = signature_lists(m)?;
302    Ok(patch)
303}
304
305/// What a hull program's entry point states about its patch (the properties' tag 3): the patch-constant function, the
306/// control-point counts, the tessellator's domain, partitioning and output primitive and the largest factor.
307#[derive(Clone, Debug, PartialEq)]
308pub struct HullState {
309    /// The patch-constant function.
310    pub patch_constant: ValueId,
311    /// The number of input control points.
312    pub input_control_points: u32,
313    /// The number of output control points.
314    pub output_control_points: u32,
315    /// The tessellator domain (`D3D11_TESSELLATOR_DOMAIN`).
316    pub domain: u32,
317    /// The partitioning (`D3D11_TESSELLATOR_PARTITIONING`).
318    pub partitioning: u32,
319    /// The output primitive (`D3D11_TESSELLATOR_OUTPUT_PRIMITIVE`).
320    pub output_primitive: u32,
321    /// The largest tessellation factor.
322    pub max_tess_factor: f32,
323}
324
325/// The value of a properties tag of the entry point, if the entry point states it.
326fn entry_property(m: &Module, wanted: i64) -> Result<Option<Node<'_>>> {
327    let entry = the_entry(m)?;
328    let Some(properties) = entry.maybe(4) else {
329        return Ok(None);
330    };
331    let properties = entry.node(Some(properties), "entry point properties")?;
332    for pair in properties.ops.chunks(2) {
333        let [tag, value] = pair else {
334            return Err(properties.fail("a tag without a value"));
335        };
336        if properties.int_of(*tag, "property tag")? == wanted {
337            return properties.node(*value, "property").map(Some);
338        }
339    }
340    Ok(None)
341}
342
343/// What a geometry program's entry point states (the properties' tag 1), as stored: the primitive it reads (`input`, a
344/// `D3D_PRIMITIVE`: 1 point, 2 line, 3 triangle, 6 line and 7 triangle with adjacency), how many vertices it emits at
345/// most, the streams it writes (a mask), the topology its stream makes (a `D3D_PRIMITIVE_TOPOLOGY`: 1 point list, 3
346/// line strip, 5 triangle strip) and how many invocations run for a primitive.
347#[derive(Clone, Copy, Debug, PartialEq, Eq)]
348pub struct GeometryState {
349    /// The input primitive (`D3D_PRIMITIVE`).
350    pub input: u32,
351    /// The most vertices it emits.
352    pub max_vertices: u32,
353    /// The streams it writes, a mask.
354    pub streams: u32,
355    /// The topology of its output stream (`D3D_PRIMITIVE_TOPOLOGY`).
356    pub topology: u32,
357    /// How many invocations run for a primitive.
358    pub instances: u32,
359}
360
361/// A geometry program's state.
362pub fn geometry_state(m: &Module) -> Result<GeometryState> {
363    let state = entry_property(m, 1)?.ok_or_else(|| {
364        the_entry(m).map_or_else(
365            |e| e,
366            |e| e.fail("a geometry program without its state (properties tag 1)"),
367        )
368    })?;
369    Ok(GeometryState {
370        input: state.number(0, "geometry input primitive")?,
371        max_vertices: state.number(1, "geometry maximum vertices")?,
372        streams: state.number(2, "geometry streams")?,
373        topology: state.number(3, "geometry topology")?,
374        instances: state.number(4, "geometry instances")?,
375    })
376}
377
378/// What a mesh program's entry point states (the properties' tag 9) after its thread group, as stored: the most
379/// vertices and primitives it writes, the topology of its primitives (1 line, 2 triangle) and the size in bytes of the
380/// payload it reads from a task program. A null field is zero.
381#[derive(Clone, Copy, Debug, PartialEq, Eq)]
382pub struct MeshState {
383    /// The most vertices it writes.
384    pub max_vertices: u32,
385    /// The most primitives it writes.
386    pub max_primitives: u32,
387    /// The topology of its primitives (1 line, 2 triangle).
388    pub topology: u32,
389    /// The size in bytes of the payload it reads from a task program.
390    pub payload: u32,
391}
392
393/// A mesh program's state.
394pub fn mesh_state(m: &Module) -> Result<MeshState> {
395    let state = entry_property(m, 9)?.ok_or_else(|| {
396        the_entry(m).map_or_else(
397            |e| e,
398            |e| e.fail("a mesh program without its state (properties tag 9)"),
399        )
400    })?;
401    // a null field is zero
402    let field = |k: usize, what: &str| -> Result<u32> {
403        match state.op(k)? {
404            None => Ok(0),
405            some => state.number_of(some, what),
406        }
407    };
408    Ok(MeshState {
409        max_vertices: field(1, "mesh maximum vertices")?,
410        max_primitives: field(2, "mesh maximum primitives")?,
411        topology: field(3, "mesh topology")?,
412        payload: field(4, "mesh payload size")?,
413    })
414}
415
416/// A hull program's patch state.
417pub fn hull_state(m: &Module) -> Result<HullState> {
418    let state = entry_property(m, 3)?.ok_or_else(|| {
419        the_entry(m).map_or_else(
420            |e| e,
421            |e| e.fail("a hull program without its patch state (properties tag 3)"),
422        )
423    })?;
424    let small = |k: usize, what: &str| -> Result<u32> { state.number_of(state.maybe(k), what) };
425    let patch_constant = match state.maybe(0).and_then(|i| m.metadata.get(i)) {
426        Some(Metadata::Value { value, .. }) => *value,
427        _ => return Err(state.fail("a hull program without its patch-constant function")),
428    };
429    let max = match state.maybe(6).and_then(|i| m.metadata.get(i)) {
430        Some(Metadata::Value { value, .. }) => match m.values.get(*value) {
431            Some(Value::Constant(Constant::Float(bits))) => {
432                let bits = u32::try_from(*bits)
433                    .map_err(|_| state.fail("the maximum tessellation factor is wider than a float"))?;
434                f32::from_bits(bits)
435            }
436            _ => return Err(state.fail("the maximum tessellation factor is not a float")),
437        },
438        _ => return Err(state.fail("hull state without a maximum tessellation factor")),
439    };
440    Ok(HullState {
441        patch_constant,
442        input_control_points: small(1, "hull input control points")?,
443        output_control_points: small(2, "hull output control points")?,
444        domain: small(3, "hull domain")?,
445        partitioning: small(4, "hull partitioning")?,
446        output_primitive: small(5, "hull output primitive")?,
447        max_tess_factor: max,
448    })
449}
450
451/// A domain program's patch state (tag 2): the tessellator domain and the input control point count.
452pub fn domain_state(m: &Module) -> Result<(u32, u32)> {
453    let state = entry_property(m, 2)?.ok_or_else(|| {
454        the_entry(m).map_or_else(
455            |e| e,
456            |e| e.fail("a domain program without its patch state (properties tag 2)"),
457        )
458    })?;
459    Ok((
460        state.number_of(state.maybe(0), "domain")?,
461        state.number_of(state.maybe(1), "domain input control points")?,
462    ))
463}
464
465fn signature_lists(m: &Module) -> Result<[Vec<Element>; 3]> {
466    let entry = the_entry(m)?;
467    let Some(signatures) = entry.op(2)? else {
468        return Ok([Vec::new(), Vec::new(), Vec::new()]);
469    };
470    let signatures = entry.node(Some(signatures), "signatures")?;
471    let list = |i: usize| -> Result<Vec<Element>> {
472        let Some(list) = signatures.op(i)? else {
473            return Ok(Vec::new());
474        };
475        let list = signatures.node(Some(list), "signature")?;
476        list.ops
477            .iter()
478            .map(|e| {
479                let e = list.node(*e, "signature element")?;
480                let row = e.int(8, "row")?;
481                let element = Element {
482                    id: e.number(0, "element id")?,
483                    name: e.string_of(e.op(1)?, "element name")?,
484                    index: match e.op(4)? {
485                        Some(list) => match e.node(Some(list), "semantic indices")?.ops.first() {
486                            Some(first) => e.number_of(*first, "semantic index")?,
487                            None => 0,
488                        },
489                        None => 0,
490                    },
491                    component_type: e.number(2, "component type")?,
492                    system_value: e.number(3, "system value")?,
493                    interpolation: e.number(5, "interpolation")?,
494                    rows: e.number(6, "rows")?,
495                    columns: e.number(7, "columns")?,
496                    placed: row >= 0,
497                    row: u32::try_from(row.max(0))
498                        .map_err(|_| e.fail(format!("row {row} is out of range")))?,
499                    column: u8::try_from(e.int(9, "column")?.max(0))
500                        .map_err(|_| e.fail("column is out of range"))?,
501                };
502                // Four lanes per register; at most 32 registers.
503                if element.rows == 0
504                    || element.columns == 0
505                    || u32::from(element.column)
506                        .checked_add(u32::from(element.columns))
507                        .is_none_or(|end| end > 4)
508                    || element.row.checked_add(element.rows).is_none_or(|end| end > 32)
509                {
510                    return Err(e.fail("the element lies outside the 32 four-lane registers"));
511                }
512                Ok(element)
513            })
514            .collect()
515    };
516    Ok([list(0)?, list(1)?, list(2)?])
517}
518
519/// A resource's tag list (alternating tag and value): the element type (tag 0) and the structured stride (tag 1).
520fn tags(resource: Node<'_>, list: Option<MetadataId>) -> Result<(Option<u32>, Option<u32>)> {
521    let Some(list) = list else {
522        return Ok((None, None));
523    };
524    let ops = resource.node(Some(list), "resource tags")?;
525    let (pairs, rest) = ops.ops.as_chunks::<2>();
526    if !rest.is_empty() {
527        return Err(ops.fail("a tag without its value"));
528    }
529    let (mut element, mut stride) = (None, None);
530    for [tag, value] in pairs {
531        let value = ops.number_of(*value, "tag value")?;
532        match ops.int_of(*tag, "tag")? {
533            0 => element = Some(value),
534            1 => stride = Some(value),
535            _ => {}
536        }
537    }
538    Ok((element, stride))
539}
540
541/// The module global a resource record's variable names: the global itself, or the one it is a cast of.
542fn variable_of(m: &Module, id: Option<MetadataId>) -> Option<ValueId> {
543    let Some(Metadata::Value { value, .. }) = id.and_then(|i| m.metadata.get(i)) else {
544        return None;
545    };
546    match m.values.get(*value)? {
547        Value::Constant(Constant::Cast { value, .. }) => Some(*value),
548        Value::Global { .. } => Some(*value),
549        _ => None,
550    }
551}
552
553/// The bound resource ranges of `dx.resources` (SRVs, UAVs, constant buffers, samplers), each with its variable's name
554/// and the type its variable points to (the resource's handle type).
555pub fn ranges(m: &Module) -> Result<Vec<(Range, String, Option<TypeId>)>> {
556    let Some((at, [resources])) = named(m, "dx.resources") else {
557        return Ok(Vec::new());
558    };
559    let lists = Node::new(m, at, *resources, "dx.resources")?;
560    let mut out = Vec::new();
561    for (class, list) in lists.ops.iter().enumerate() {
562        let Some(list) = list else { continue };
563        let class =
564            u8::try_from(class).map_err(|_| lists.fail(format!("resource class {class} is out of range")))?;
565        let list = lists.node(Some(*list), "resource list")?;
566        for r in list.ops {
567            let r = list.node(*r, "resource")?;
568            // The record's variable is an undef pointer to the resource's type.
569            let ty = match r.op(1)?.and_then(|i| m.metadata.get(i)) {
570                Some(Metadata::Value { ty, .. }) => match m.types.get(*ty) {
571                    Some(Type::Pointer(t, _)) => Some(*t),
572                    _ => None,
573                },
574                _ => None,
575            };
576            let extra = r.number(6, "resource field")?;
577            let (element, stride) = match class {
578                0 => tags(r, r.maybe(8))?,
579                1 => tags(r, r.maybe(10))?,
580                _ => (None, None),
581            };
582            out.push((
583                Range {
584                    class,
585                    id: r.number(0, "resource id")?,
586                    lower: r.number(4, "lower bound")?,
587                    // an unbounded range is written -1 (all ones in 32 bits)
588                    count: {
589                        let v = r.int(5, "range size")?;
590                        u32::try_from(v)
591                            .or_else(|_| i32::try_from(v).map(|v| u32::from_ne_bytes(v.to_ne_bytes())))
592                            .map_err(|_| r.fail(format!("range size {v} is out of range")))?
593                    },
594                    extra,
595                    space: r.number(3, "register space")?,
596                    element,
597                    stride,
598                    // a UAV's record states whether it has a counter after the shape and the coherence
599                    counter: class == 1 && r.maybe(8).is_some() && r.int(8, "counter")? != 0,
600                    variable: variable_of(m, r.op(1)?),
601                },
602                r.string_of(r.op(2)?, "resource name")?,
603                ty,
604            ));
605        }
606    }
607    Ok(out)
608}
609
610/// One field of a struct type, as its annotation in `dx.typeAnnotations` states it.
611#[derive(Clone, Debug, PartialEq, Eq)]
612pub struct FieldAnnotation {
613    /// The field's name.
614    pub name: String,
615    /// Byte offset in the constant buffer.
616    pub offset: u32,
617    /// Its `DxilComponentType`: LLVM integers carry no signedness, the annotation does.
618    pub component: Option<u32>,
619    /// A matrix field's rows, columns and orientation (1 row-major, 2 column-major): LLVM lowers a matrix to an array
620    /// of its lines, so only the annotation says it is one.
621    pub matrix: Option<(u32, u32, u32)>,
622}
623
624/// Fields of a struct type from `dx.typeAnnotations` (field tag 6 the name, 3 the constant-buffer offset, 7 the
625/// component type). Empty when the module has no annotation for it.
626pub fn fields(m: &Module, t: TypeId) -> Result<Vec<FieldAnnotation>> {
627    let Some((at, annotations)) = named(m, "dx.typeAnnotations") else {
628        return Ok(Vec::new());
629    };
630    for &a in annotations {
631        let a = Node::new(m, at, a, "type annotations")?;
632        // Struct annotations: kind 0, then (struct type, annotation) pairs.
633        if a.int(0, "annotation kind")? != 0 {
634            continue;
635        }
636        let (pairs, rest) = a.ops.get(1..).unwrap_or_default().as_chunks::<2>();
637        if !rest.is_empty() {
638            return Err(a.fail("a struct type without its annotation"));
639        }
640        for [struct_type, annotation] in pairs {
641            let ty = match struct_type.and_then(|i| m.metadata.get(i)) {
642                Some(Metadata::Value { ty, .. }) => *ty,
643                _ => continue,
644            };
645            if ty != t {
646                continue;
647            }
648            let annotation = a.node(*annotation, "struct annotation")?;
649            // The first operand is the struct's own record; the fields follow.
650            let fields = annotation
651                .ops
652                .get(1..)
653                .ok_or_else(|| annotation.fail("a struct annotation without its first operand"))?;
654            return fields
655                .iter()
656                .map(|f| {
657                    let f = annotation.node(*f, "field annotation")?;
658                    let (mut name, mut offset, mut component, mut matrix) = (String::new(), 0, None, None);
659                    let (tags, rest) = f.ops.as_chunks::<2>();
660                    if !rest.is_empty() {
661                        return Err(f.fail("a tag without its value"));
662                    }
663                    for [tag, value] in tags {
664                        match f.int_of(*tag, "field tag")? {
665                            6 => name = f.string_of(*value, "field name")?,
666                            3 => offset = f.number_of(*value, "field offset")?,
667                            2 => {
668                                let ops = f.node(*value, "matrix annotation")?;
669                                matrix = Some((
670                                    ops.number(0, "matrix rows")?,
671                                    ops.number(1, "matrix columns")?,
672                                    ops.number(2, "matrix orientation")?,
673                                ));
674                            }
675                            7 => component = Some(f.number_of(*value, "field component type")?),
676                            _ => {}
677                        }
678                    }
679                    Ok(FieldAnnotation {
680                        name,
681                        offset,
682                        component,
683                        matrix,
684                    })
685                })
686                .collect();
687        }
688    }
689    Ok(Vec::new())
690}
691
692/// One element of an `ISG1`/`OSG1`/`PSG1` part as stored.
693#[derive(Clone, Debug, PartialEq, Eq)]
694pub struct SignatureElement {
695    /// The semantic name.
696    pub name: String,
697    /// The semantic index.
698    pub index: u32,
699    /// `D3D_NAME`.
700    pub system_value: u32,
701    /// `D3D_REGISTER_COMPONENT_TYPE`.
702    pub component_type: u32,
703    /// The register the element is packed into.
704    pub register: u32,
705    /// The components the element occupies, one bit each (x is bit 0).
706    pub mask: u8,
707    /// The components a program reads (inputs) or never writes (outputs), as stored.
708    pub read_write_mask: u8,
709    /// Where the record starts, in bits of the part.
710    pub bit: usize,
711}
712
713/// The elements of a signature part (`ISG1`, `OSG1` or `PSG1`): a count and the offset of the first record, then
714/// records of 32 bytes (stream, name offset, semantic index, system value, component type, register, mask and
715/// read-write mask, minimum precision). A name ends within 256 bytes.
716pub fn signature(part: &[u8]) -> Result<Vec<SignatureElement>> {
717    let bit = |byte: usize| byte.saturating_mul(8);
718    let v = primase_foundations::bytes::View::new(part);
719    let word = |at: usize| -> Result<u32> {
720        v.array::<4>(at)
721            .map(u32::from_le_bytes)
722            .map_err(|e| error(bit(at), e.to_string()))
723    };
724    // the word `delta` bytes after a record's start
725    let field = |record: usize, delta: usize| -> Result<u32> {
726        word(
727            record
728                .checked_add(delta)
729                .ok_or_else(|| error(bit(record), "signature element offset"))?,
730        )
731    };
732    let n = word(0)? as usize;
733    let first = word(4)? as usize;
734    // the records lie in the part: bound the count before anything is allocated
735    let table = n
736        .checked_mul(32)
737        .and_then(|size| first.checked_add(size))
738        .filter(|&end| end <= part.len())
739        .ok_or_else(|| {
740            error(
741                0,
742                format!(
743                    "{n} records of 32 bytes from byte {first} do not fit the part's {} bytes",
744                    part.len()
745                ),
746            )
747        })?;
748    let mut out = Vec::with_capacity(n);
749    for i in 0..n {
750        let e = i
751            .checked_mul(32)
752            .and_then(|o| o.checked_add(first))
753            .filter(|&e| e < table)
754            .ok_or_else(|| error(32, "signature element offset"))?;
755        let name_at = field(e, 4)? as usize;
756        let tail = part
757            .get(name_at..)
758            .ok_or_else(|| error(bit(name_at), "signature name offset"))?;
759        let end = tail
760            .iter()
761            .position(|&b| b == 0)
762            .filter(|&n| n <= 256)
763            .ok_or_else(|| error(bit(name_at), "signature name"))?;
764        let name = tail
765            .get(..end)
766            .and_then(|name| std::str::from_utf8(name).ok())
767            .ok_or_else(|| error(bit(name_at), "signature name is not UTF-8"))?;
768        let [mask, read_write_mask] = v
769            .array::<2>(
770                e.checked_add(24)
771                    .ok_or_else(|| error(bit(e), "signature element offset"))?,
772            )
773            .map_err(|x| error(bit(e), x.to_string()))?;
774        out.push(SignatureElement {
775            name: name.to_owned(),
776            index: field(e, 8)?,
777            system_value: field(e, 12)?,
778            component_type: field(e, 16)?,
779            register: field(e, 20)?,
780            mask,
781            read_write_mask,
782            bit: bit(e),
783        });
784    }
785    Ok(out)
786}