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, and the entry point's other property tags, the tail tags of its signature elements, the
6//! optional fields of the resource records and the string attributes of the entry function. What the numbers mean for a
7//! graphics API is the caller's to decide.
8use crate::module::{Constant, Metadata, MetadataId, Module, Type, TypeId, Value, ValueId};
9use crate::{Result, error};
10
11/// One signature element of the entry point: its id (the `sigId` loadInput and storeOutput name), its first
12/// register and column, and its size.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Element {
15    /// The id the `loadInput` and `storeOutput` operations name the element by.
16    pub id: u32,
17    /// The semantic name.
18    pub name: String,
19    /// The first semantic index.
20    pub index: u32,
21    /// DXIL component type (5 u32, 4 i32, 9 f32, ...).
22    pub component_type: u8,
23    /// DXIL semantic kind (0 arbitrary, 1 SV_VertexID, 3 SV_Position, 13 SV_IsFrontFace, ...).
24    pub system_value: u8,
25    /// DXIL interpolation mode (1 constant, 2 linear, 4 linear noperspective, ...).
26    pub interpolation: u8,
27    /// The first register the element occupies.
28    pub row: u32,
29    /// The first component (0 x to 3 w) of that register.
30    pub column: u8,
31    /// How many registers it spans.
32    pub rows: u32,
33    /// How many components of each register it uses.
34    pub columns: u8,
35    /// An element the packer did not place (system values read another way): no register.
36    pub placed: bool,
37}
38/// A bound resource range: class (0 SRV, 1 UAV, 2 constant buffer, 3 sampler), range id, first register, count.
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct Range {
41    /// 0 SRV, 1 UAV, 2 constant buffer, 3 sampler.
42    pub class: u8,
43    /// The range id the resource operations name the range by.
44    pub id: u32,
45    /// The first register of the range.
46    pub lower: u32,
47    /// The number of registers; `u32::MAX` where the range is unbounded.
48    pub count: u32,
49    /// Constant buffers: their size in bytes; SRVs and UAVs: their shape (`DxilResourceKind`, 2 a 2D texture);
50    /// samplers: their kind (0 default, 1 comparison, 2 mono).
51    pub extra: u32,
52    /// The register space.
53    pub space: u32,
54    /// An SRV's or UAV's element type tag (`DxilComponentType`), where its tags state one.
55    pub element: Option<u32>,
56    /// A structured SRV's or UAV's element stride in bytes, where its tags state one.
57    pub stride: Option<u32>,
58    /// An unordered-access view with a hidden counter (an append or consume buffer, `IncrementCounter`).
59    pub counter: bool,
60    /// The module global a library names the resource by (a program of a library makes its handles from the
61    /// global's value).
62    pub variable: Option<ValueId>,
63}
64
65/// A metadata node being read. A problem with it is reported at the bit of the bitcode its record starts at.
66#[derive(Clone, Copy, Debug)]
67struct Node<'a> {
68    m: &'a Module,
69    id: MetadataId,
70    ops: &'a [Option<MetadataId>],
71    /// What the node is, for the messages.
72    what: &'static str,
73}
74
75impl<'a> Node<'a> {
76    /// The node `id`; where it is not a node the error is at the bit `at` (the record that refers to it).
77    fn new(m: &'a Module, at: usize, id: MetadataId, what: &'static str) -> Result<Self> {
78        match m.metadata.get(id).and_then(Metadata::operands) {
79            Some(ops) => Ok(Node { m, id, ops, what }),
80            None => Err(error(at, format!("{what}: expected a metadata node"))),
81        }
82    }
83    fn bit(&self) -> usize {
84        self.m.metadata_at.get(self.id).copied().unwrap_or(self.m.at)
85    }
86    /// The error for something wrong in this node.
87    fn fail(&self, detail: impl std::fmt::Display) -> crate::Error {
88        error(self.bit(), format!("{}: {detail}", self.what))
89    }
90    /// The node an operand names, which must not be null.
91    fn node(&self, id: Option<MetadataId>, what: &'static str) -> Result<Node<'a>> {
92        let id = id.ok_or_else(|| self.fail(format!("{what} is null")))?;
93        Node::new(self.m, self.bit(), id, what)
94    }
95    /// Operand `i`, which must exist (it may be null).
96    fn op(&self, i: usize) -> Result<Option<MetadataId>> {
97        self.ops
98            .get(i)
99            .copied()
100            .ok_or_else(|| self.fail(format!("node has no operand {i}")))
101    }
102    /// Operand `i`, null where the node has none.
103    fn maybe(&self, i: usize) -> Option<MetadataId> {
104        self.ops.get(i).copied().flatten()
105    }
106    /// An integer constant metadata operand.
107    fn int_of(&self, id: Option<MetadataId>, what: &str) -> Result<i64> {
108        match id.and_then(|i| self.m.metadata.get(i)) {
109            Some(Metadata::Value { value, .. }) => match self.m.values.get(*value) {
110                Some(Value::Constant(Constant::Integer(v))) => Ok(*v),
111                // Zero is stored as the null constant.
112                Some(Value::Constant(Constant::Null)) => Ok(0),
113                _ => Err(self.fail(format!("{what}: expected an integer constant"))),
114            },
115            _ => Err(self.fail(format!("{what}: expected an integer"))),
116        }
117    }
118    fn int(&self, i: usize, what: &str) -> Result<i64> {
119        self.int_of(self.op(i)?, what)
120    }
121    /// An integer operand in the range of `T`.
122    fn number_of<T: TryFrom<i64>>(&self, id: Option<MetadataId>, what: &str) -> Result<T> {
123        let v = self.int_of(id, what)?;
124        T::try_from(v).map_err(|_| self.fail(format!("{what}: {v} is out of range")))
125    }
126    fn number<T: TryFrom<i64>>(&self, i: usize, what: &str) -> Result<T> {
127        self.number_of(self.op(i)?, what)
128    }
129    /// The string an operand names: empty where it names none; an error where it is not UTF-8.
130    fn string_of(&self, id: Option<MetadataId>, what: &str) -> Result<String> {
131        match id.and_then(|i| self.m.metadata.get(i)) {
132            Some(Metadata::String(s)) => std::str::from_utf8(s).map(str::to_owned).map_err(|e| {
133                self.fail(format!(
134                    "{what}: not UTF-8 (valid up to byte {})",
135                    e.valid_up_to()
136                ))
137            }),
138            _ => Ok(String::new()),
139        }
140    }
141}
142
143/// The named metadata `name`: the bit its record starts at and the nodes it lists.
144fn named<'a>(m: &'a Module, name: &str) -> Option<(usize, &'a [MetadataId])> {
145    let i = m.named_metadata.iter().position(|(n, _)| n == name)?;
146    let (_, ids) = m.named_metadata.get(i)?;
147    Some((
148        m.named_metadata_at.get(i).copied().unwrap_or(m.at),
149        ids.as_slice(),
150    ))
151}
152
153/// The shader kind a library's entry point states (properties tag 8): the numbers of a program's kind (7 ray
154/// generation to 12 callable, 13 mesh).
155pub fn library_kind(m: &Module) -> Result<u32> {
156    let entry = the_entry(m)?;
157    let properties = entry
158        .node(entry.maybe(4), "entry point properties")
159        .map_err(|_| entry.fail("a library entry point without properties"))?;
160    for pair in properties.ops.chunks(2) {
161        let [tag, value] = pair else {
162            return Err(properties.fail("a tag without a value"));
163        };
164        if properties.int_of(*tag, "property tag")? == 8 {
165            return properties.number_of(*value, "shader kind");
166        }
167    }
168    Err(properties.fail("a library entry point that states no shader kind"))
169}
170
171/// The one entry point's record. A library also has a record with no function, which holds its resources.
172fn the_entry(m: &Module) -> Result<Node<'_>> {
173    let (at, entries) =
174        named(m, "dx.entryPoints").ok_or_else(|| error(m.at, "the module has no dx.entryPoints metadata"))?;
175    let mut with_function = Vec::new();
176    for &id in entries {
177        let entry = Node::new(m, at, id, "entry point")?;
178        if entry.op(0)?.is_some() {
179            with_function.push(entry);
180        }
181    }
182    match with_function.as_slice() {
183        [entry] => Ok(*entry),
184        _ => Err(error(
185            at,
186            format!(
187                "dx.entryPoints: expected exactly one entry point with a function, found {}",
188                with_function.len()
189            ),
190        )),
191    }
192}
193
194/// The entry point's function value.
195pub fn entry(m: &Module) -> Result<ValueId> {
196    let entry = the_entry(m)?;
197    match entry.op(0)?.and_then(|i| m.metadata.get(i)) {
198        Some(Metadata::Value { value, .. }) => Ok(*value),
199        _ => Err(entry.fail("operand 0 names no function")),
200    }
201}
202
203/// The entry point's thread group size (`numthreads`): the value of the properties' tag 4 (a mesh or amplification
204/// program's, the first field of its state, tag 9 or 10). `None` when the entry point states none (every stage but
205/// compute, mesh and amplification).
206pub fn num_threads(m: &Module) -> Result<Option<[u32; 3]>> {
207    let entry = the_entry(m)?;
208    let Some(properties) = entry.maybe(4) else {
209        return Ok(None);
210    };
211    let properties = entry.node(Some(properties), "entry point properties")?;
212    for pair in properties.ops.chunks(2) {
213        let [tag, value] = pair else {
214            return Err(properties.fail("a tag without a value"));
215        };
216        let tag = properties.int_of(*tag, "property tag")?;
217        if tag != 4 && tag != 9 && tag != 10 {
218            continue;
219        }
220        let mut size = properties.node(*value, "numthreads")?;
221        // a mesh or amplification program states them first in its state
222        if tag == 9 || tag == 10 {
223            size = size.node(size.op(0)?, "numthreads")?;
224        }
225        return Ok(Some([
226            size.number(0, "numthreads x")?,
227            size.number(1, "numthreads y")?,
228            size.number(2, "numthreads z")?,
229        ]));
230    }
231    Ok(None)
232}
233
234/// The entry point's shader flags: the value of the properties' tag 0, a 64-bit mask kept as stored. `None` when the
235/// entry point states none (the properties are null, or have no tag 0). The compiler writes the tag in nearly every
236/// entry point of the games read; this reader attaches no meaning to its bits.
237pub fn shader_flags(m: &Module) -> Result<Option<u64>> {
238    flags_of(the_entry(m)?)
239}
240
241/// The shader flags of the module as dxc's `DxilModule` reads them: those of the first entry of `dx.entryPoints`, which
242/// for a library is the entry without a function that states the library's own properties.
243pub fn module_shader_flags(m: &Module) -> Result<Option<u64>> {
244    let (at, entries) =
245        named(m, "dx.entryPoints").ok_or_else(|| error(m.at, "the module has no dx.entryPoints metadata"))?;
246    let first = entries
247        .first()
248        .ok_or_else(|| error(at, "dx.entryPoints lists no entry point"))?;
249    flags_of(Node::new(m, at, *first, "entry point")?)
250}
251
252/// The flags (properties tag 0) of an entry point.
253fn flags_of(entry: Node<'_>) -> Result<Option<u64>> {
254    let Some(properties) = entry.maybe(4) else {
255        return Ok(None);
256    };
257    let properties = entry.node(Some(properties), "entry point properties")?;
258    for pair in properties.ops.chunks(2) {
259        let [tag, value] = pair else {
260            return Err(properties.fail("a tag without a value"));
261        };
262        if properties.int_of(*tag, "property tag")? == 0 {
263            let flags = properties.int_of(*value, "shader flags")?;
264            return Ok(Some(u64::from_le_bytes(flags.to_le_bytes())));
265        }
266    }
267    Ok(None)
268}
269
270/// The usage mask of each signature element (tag 3 of the element's trailing tag list: the components of the element
271/// the program reads or writes, bit 0 the element's first component, so a scalar packed into component w states 1,
272/// 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
273/// 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);
274/// any other tag is ignored (see [`element_tags`] for them).
275pub fn usage_masks(m: &Module) -> Result<[Vec<Option<u8>>; 3]> {
276    Ok(element_tags(m)?.map(|list| list.into_iter().map(|tags| tags.usage_mask).collect()))
277}
278
279/// The trailing tags of one signature element (the element record's operand 10, a list of tag and value pairs). The
280/// compiler writes tag 0 for an output of another stream than 0, tag 2 for an input indexed dynamically and tag 3 for
281/// the components in use. Of the 691,000 elements of skate.'s and Elden Ring's programs only tag 3 occurs.
282#[derive(Clone, Debug, Default, PartialEq, Eq)]
283pub struct ElementTags {
284    /// Tag 0: the stream (of a geometry program) the element is written to; `None` where the element states none.
285    pub stream: Option<u32>,
286    /// Tag 2: the components of the element that are indexed dynamically, bit 0 the element's first component.
287    pub dynamic_index_mask: Option<u8>,
288    /// Tag 3: the usage mask ([`usage_masks`]).
289    pub usage_mask: Option<u8>,
290    /// Every other tag with an integer value, as stored.
291    pub other: Vec<(i64, i64)>,
292}
293
294/// The trailing tags of each signature element, in the order of [`elements`]' inputs and outputs and
295/// [`patch_elements`]; an element with no tag list has [`ElementTags::default`].
296pub fn element_tags(m: &Module) -> Result<[Vec<ElementTags>; 3]> {
297    let entry = the_entry(m)?;
298    let Some(signatures) = entry.op(2)? else {
299        return Ok([Vec::new(), Vec::new(), Vec::new()]);
300    };
301    let signatures = entry.node(Some(signatures), "signatures")?;
302    let list = |i: usize| -> Result<Vec<ElementTags>> {
303        let Some(list) = signatures.op(i)? else {
304            return Ok(Vec::new());
305        };
306        let list = signatures.node(Some(list), "signature")?;
307        list.ops
308            .iter()
309            .map(|e| {
310                let e = list.node(*e, "signature element")?;
311                let Some(tail) = e.maybe(10) else {
312                    return Ok(ElementTags::default());
313                };
314                let tail = e.node(Some(tail), "element tags")?;
315                let (pairs, rest) = tail.ops.as_chunks::<2>();
316                if !rest.is_empty() {
317                    return Err(tail.fail("a tag without its value"));
318                }
319                let mut tags = ElementTags::default();
320                for [tag, value] in pairs {
321                    match tail.int_of(*tag, "element tag")? {
322                        0 => tags.stream = Some(tail.number_of(*value, "element stream")?),
323                        2 => tags.dynamic_index_mask = Some(tail.number_of(*value, "dynamic index mask")?),
324                        3 => tags.usage_mask = Some(tail.number_of(*value, "usage mask")?),
325                        other => {
326                            if let Ok(v) = tail.int_of(*value, "element tag value") {
327                                tags.other.push((other, v));
328                            }
329                        }
330                    }
331                }
332                Ok(tags)
333            })
334            .collect()
335    };
336    Ok([list(0)?, list(1)?, list(2)?])
337}
338
339/// The entry point's input and output elements, by id.
340pub fn elements(m: &Module) -> Result<(Vec<Element>, Vec<Element>)> {
341    let [inputs, outputs, _] = signature_lists(m)?;
342    Ok((inputs, outputs))
343}
344
345/// A hull or domain program's patch-constant elements, by id.
346pub fn patch_elements(m: &Module) -> Result<Vec<Element>> {
347    let [_, _, patch] = signature_lists(m)?;
348    Ok(patch)
349}
350
351/// What a hull program's entry point states about its patch (the properties' tag 3): the patch-constant function, the
352/// control-point counts, the tessellator's domain, partitioning and output primitive and the largest factor.
353#[derive(Clone, Debug, PartialEq)]
354pub struct HullState {
355    /// The patch-constant function.
356    pub patch_constant: ValueId,
357    /// The number of input control points.
358    pub input_control_points: u32,
359    /// The number of output control points.
360    pub output_control_points: u32,
361    /// The tessellator domain (`D3D11_TESSELLATOR_DOMAIN`).
362    pub domain: u32,
363    /// The partitioning (`D3D11_TESSELLATOR_PARTITIONING`).
364    pub partitioning: u32,
365    /// The output primitive (`D3D11_TESSELLATOR_OUTPUT_PRIMITIVE`).
366    pub output_primitive: u32,
367    /// The largest tessellation factor.
368    pub max_tess_factor: f32,
369}
370
371/// The value of a properties tag of the entry point, if the entry point states it.
372fn entry_property(m: &Module, wanted: i64) -> Result<Option<Node<'_>>> {
373    let entry = the_entry(m)?;
374    let Some(properties) = entry.maybe(4) else {
375        return Ok(None);
376    };
377    let properties = entry.node(Some(properties), "entry point properties")?;
378    for pair in properties.ops.chunks(2) {
379        let [tag, value] = pair else {
380            return Err(properties.fail("a tag without a value"));
381        };
382        if properties.int_of(*tag, "property tag")? == wanted {
383            return properties.node(*value, "property").map(Some);
384        }
385    }
386    Ok(None)
387}
388
389/// What a geometry program's entry point states (the properties' tag 1), as stored: the primitive it reads (`input`, a
390/// `D3D_PRIMITIVE`: 1 point, 2 line, 3 triangle, 6 line and 7 triangle with adjacency), how many vertices it emits at
391/// most, the streams it writes (a mask), the topology its stream makes (a `D3D_PRIMITIVE_TOPOLOGY`: 1 point list, 3
392/// line strip, 5 triangle strip) and how many invocations run for a primitive.
393#[derive(Clone, Copy, Debug, PartialEq, Eq)]
394pub struct GeometryState {
395    /// The input primitive (`D3D_PRIMITIVE`).
396    pub input: u32,
397    /// The most vertices it emits.
398    pub max_vertices: u32,
399    /// The streams it writes, a mask.
400    pub streams: u32,
401    /// The topology of its output stream (`D3D_PRIMITIVE_TOPOLOGY`).
402    pub topology: u32,
403    /// How many invocations run for a primitive.
404    pub instances: u32,
405}
406
407/// A geometry program's state.
408pub fn geometry_state(m: &Module) -> Result<GeometryState> {
409    let state = entry_property(m, 1)?.ok_or_else(|| {
410        the_entry(m).map_or_else(
411            |e| e,
412            |e| e.fail("a geometry program without its state (properties tag 1)"),
413        )
414    })?;
415    Ok(GeometryState {
416        input: state.number(0, "geometry input primitive")?,
417        max_vertices: state.number(1, "geometry maximum vertices")?,
418        streams: state.number(2, "geometry streams")?,
419        topology: state.number(3, "geometry topology")?,
420        instances: state.number(4, "geometry instances")?,
421    })
422}
423
424/// What a mesh program's entry point states (the properties' tag 9) after its thread group, as stored: the most
425/// vertices and primitives it writes, the topology of its primitives (1 line, 2 triangle) and the size in bytes of the
426/// payload it reads from a task program. A null field is zero.
427#[derive(Clone, Copy, Debug, PartialEq, Eq)]
428pub struct MeshState {
429    /// The most vertices it writes.
430    pub max_vertices: u32,
431    /// The most primitives it writes.
432    pub max_primitives: u32,
433    /// The topology of its primitives (1 line, 2 triangle).
434    pub topology: u32,
435    /// The size in bytes of the payload it reads from a task program.
436    pub payload: u32,
437}
438
439/// A mesh program's state.
440pub fn mesh_state(m: &Module) -> Result<MeshState> {
441    let state = entry_property(m, 9)?.ok_or_else(|| {
442        the_entry(m).map_or_else(
443            |e| e,
444            |e| e.fail("a mesh program without its state (properties tag 9)"),
445        )
446    })?;
447    // a null field is zero
448    let field = |k: usize, what: &str| -> Result<u32> {
449        match state.op(k)? {
450            None => Ok(0),
451            some => state.number_of(some, what),
452        }
453    };
454    Ok(MeshState {
455        max_vertices: field(1, "mesh maximum vertices")?,
456        max_primitives: field(2, "mesh maximum primitives")?,
457        topology: field(3, "mesh topology")?,
458        payload: field(4, "mesh payload size")?,
459    })
460}
461
462/// A hull program's patch state.
463pub fn hull_state(m: &Module) -> Result<HullState> {
464    let state = entry_property(m, 3)?.ok_or_else(|| {
465        the_entry(m).map_or_else(
466            |e| e,
467            |e| e.fail("a hull program without its patch state (properties tag 3)"),
468        )
469    })?;
470    let small = |k: usize, what: &str| -> Result<u32> { state.number_of(state.maybe(k), what) };
471    let patch_constant = match state.maybe(0).and_then(|i| m.metadata.get(i)) {
472        Some(Metadata::Value { value, .. }) => *value,
473        _ => return Err(state.fail("a hull program without its patch-constant function")),
474    };
475    let max = match state.maybe(6).and_then(|i| m.metadata.get(i)) {
476        Some(Metadata::Value { value, .. }) => match m.values.get(*value) {
477            Some(Value::Constant(Constant::Float(bits))) => {
478                let bits = u32::try_from(*bits)
479                    .map_err(|_| state.fail("the maximum tessellation factor is wider than a float"))?;
480                f32::from_bits(bits)
481            }
482            _ => return Err(state.fail("the maximum tessellation factor is not a float")),
483        },
484        _ => return Err(state.fail("hull state without a maximum tessellation factor")),
485    };
486    Ok(HullState {
487        patch_constant,
488        input_control_points: small(1, "hull input control points")?,
489        output_control_points: small(2, "hull output control points")?,
490        domain: small(3, "hull domain")?,
491        partitioning: small(4, "hull partitioning")?,
492        output_primitive: small(5, "hull output primitive")?,
493        max_tess_factor: max,
494    })
495}
496
497/// A domain program's patch state (tag 2): the tessellator domain and the input control point count.
498pub fn domain_state(m: &Module) -> Result<(u32, u32)> {
499    let state = entry_property(m, 2)?.ok_or_else(|| {
500        the_entry(m).map_or_else(
501            |e| e,
502            |e| e.fail("a domain program without its patch state (properties tag 2)"),
503        )
504    })?;
505    Ok((
506        state.number_of(state.maybe(0), "domain")?,
507        state.number_of(state.maybe(1), "domain input control points")?,
508    ))
509}
510
511/// One property of the entry point as stored: the tag and the metadata record holding the value (an integer constant
512/// or a node), `None` where the value is null. The tags the compiler writes: 0 shader flags, 1 geometry state, 2 domain
513/// state, 3 hull state, 4 thread group, 5 auto binding space, 6 ray payload size, 7 ray attribute size, 8 shader kind
514/// (libraries), 9 mesh state, 10 amplification state, 11 wave size, 12 root signature, 23 wave size range.
515#[derive(Clone, Copy, Debug, PartialEq, Eq)]
516pub struct EntryProperty {
517    /// The tag.
518    pub tag: i64,
519    /// The value's metadata record.
520    pub value: Option<MetadataId>,
521}
522
523/// Every property of the entry point, in the order stored; none where the entry point states none.
524pub fn entry_properties(m: &Module) -> Result<Vec<EntryProperty>> {
525    let entry = the_entry(m)?;
526    properties_of(entry)
527}
528
529fn properties_of(entry: Node<'_>) -> Result<Vec<EntryProperty>> {
530    let Some(properties) = entry.maybe(4) else {
531        return Ok(Vec::new());
532    };
533    let properties = entry.node(Some(properties), "entry point properties")?;
534    let (pairs, rest) = properties.ops.as_chunks::<2>();
535    if !rest.is_empty() {
536        return Err(properties.fail("a tag without a value"));
537    }
538    pairs
539        .iter()
540        .map(|[tag, value]| {
541            Ok(EntryProperty {
542                tag: properties.int_of(*tag, "property tag")?,
543                value: *value,
544            })
545        })
546        .collect()
547}
548
549/// The integer a property states directly (tags 6, 7 and 8), or the first integer of the node it states (tags 5 and
550/// 11); `None` where the entry point has no such property.
551fn property_integer(m: &Module, wanted: i64) -> Result<Option<u32>> {
552    let entry = the_entry(m)?;
553    for property in properties_of(entry)? {
554        if property.tag != wanted {
555            continue;
556        }
557        let record = entry.node(property.value, "property").ok();
558        return match record {
559            Some(node) => node.number(0, "property value").map(Some),
560            None => entry.number_of(property.value, "property value").map(Some),
561        };
562    }
563    Ok(None)
564}
565
566/// The size in bytes of the ray payload a ray tracing program's entry point states (tag 6); `None` for a program
567/// that states none (a ray generation program, or any other stage).
568pub fn ray_payload_size(m: &Module) -> Result<Option<u32>> {
569    property_integer(m, 6)
570}
571
572/// The size in bytes of the hit attributes a ray tracing program's entry point states (tag 7); `None` where it states none.
573pub fn ray_attribute_size(m: &Module) -> Result<Option<u32>> {
574    property_integer(m, 7)
575}
576
577/// The register space automatic bindings take, as the entry point states it (tag 5, a one-integer node); `None` where
578/// it states none. The stock libraries state 0 on the record that holds their resources (which has no function) and
579/// the compiler option `-auto-binding-space` changes that record's value: [`library_binding_space`] reads it.
580pub fn auto_binding_space(m: &Module) -> Result<Option<u32>> {
581    property_integer(m, 5)
582}
583
584/// The auto binding space the library's resource record (the entry record with no function) states (tag 5). `None`
585/// where the module has no such record or it states none.
586pub fn library_binding_space(m: &Module) -> Result<Option<u32>> {
587    let (at, entries) =
588        named(m, "dx.entryPoints").ok_or_else(|| error(m.at, "the module has no dx.entryPoints metadata"))?;
589    for &id in entries {
590        let entry = Node::new(m, at, id, "entry point")?;
591        if entry.op(0)?.is_some() {
592            continue;
593        }
594        for property in properties_of(entry)? {
595            if property.tag == 5 {
596                let node = entry.node(property.value, "auto binding space")?;
597                return node.number(0, "auto binding space").map(Some);
598            }
599        }
600    }
601    Ok(None)
602}
603
604/// The wave size a compute, mesh or amplification program's entry point requires (tag 11, a node): its integers as
605/// stored, a single one for a fixed size; `None` where the entry point states none.
606pub fn wave_size(m: &Module) -> Result<Option<Vec<u32>>> {
607    let Some(node) = entry_property(m, 11)? else {
608        return Ok(None);
609    };
610    node.ops
611        .iter()
612        .map(|id| node.number_of(*id, "wave size"))
613        .collect::<Result<Vec<u32>>>()
614        .map(Some)
615}
616
617/// The payload size in bytes an amplification program's entry point states (the second field of its state, tag 10);
618/// `None` for any other program. The first field is the thread group ([`num_threads`]).
619pub fn amplification_payload_size(m: &Module) -> Result<Option<u32>> {
620    let Some(state) = entry_property(m, 10)? else {
621        return Ok(None);
622    };
623    state.number(1, "amplification payload size").map(Some)
624}
625
626/// The denormal mode an entry function states for 32-bit floats, as the string attribute `fp32-denorm-mode` has it
627/// (`any`, `preserve` or `ftz`). `None` where the function carries no such attribute, which is the case in all of the
628/// 69,728 programs of skate. and Elden Ring: the compiler writes the attribute only where the option asks for a mode.
629pub fn fp32_denorm_mode(m: &Module) -> Result<Option<String>> {
630    let function = entry(m)?;
631    Ok(m.string_attribute(function, "fp32-denorm-mode")
632        .map(str::to_owned))
633}
634
635fn signature_lists(m: &Module) -> Result<[Vec<Element>; 3]> {
636    let entry = the_entry(m)?;
637    let Some(signatures) = entry.op(2)? else {
638        return Ok([Vec::new(), Vec::new(), Vec::new()]);
639    };
640    let signatures = entry.node(Some(signatures), "signatures")?;
641    let list = |i: usize| -> Result<Vec<Element>> {
642        let Some(list) = signatures.op(i)? else {
643            return Ok(Vec::new());
644        };
645        let list = signatures.node(Some(list), "signature")?;
646        list.ops
647            .iter()
648            .map(|e| {
649                let e = list.node(*e, "signature element")?;
650                let row = e.int(8, "row")?;
651                let element = Element {
652                    id: e.number(0, "element id")?,
653                    name: e.string_of(e.op(1)?, "element name")?,
654                    index: match e.op(4)? {
655                        Some(list) => match e.node(Some(list), "semantic indices")?.ops.first() {
656                            Some(first) => e.number_of(*first, "semantic index")?,
657                            None => 0,
658                        },
659                        None => 0,
660                    },
661                    component_type: e.number(2, "component type")?,
662                    system_value: e.number(3, "system value")?,
663                    interpolation: e.number(5, "interpolation")?,
664                    rows: e.number(6, "rows")?,
665                    columns: e.number(7, "columns")?,
666                    placed: row >= 0,
667                    row: u32::try_from(row.max(0))
668                        .map_err(|_| e.fail(format!("row {row} is out of range")))?,
669                    column: u8::try_from(e.int(9, "column")?.max(0))
670                        .map_err(|_| e.fail("column is out of range"))?,
671                };
672                // Four lanes per register; at most 32 registers.
673                if element.rows == 0
674                    || element.columns == 0
675                    || u32::from(element.column)
676                        .checked_add(u32::from(element.columns))
677                        .is_none_or(|end| end > 4)
678                    || element.row.checked_add(element.rows).is_none_or(|end| end > 32)
679                {
680                    return Err(e.fail("the element lies outside the 32 four-lane registers"));
681                }
682                Ok(element)
683            })
684            .collect()
685    };
686    Ok([list(0)?, list(1)?, list(2)?])
687}
688
689/// A resource's tag list (alternating tag and value): the element type (tag 0) and the structured stride (tag 1).
690fn tags(resource: Node<'_>, list: Option<MetadataId>) -> Result<(Option<u32>, Option<u32>)> {
691    let Some(list) = list else {
692        return Ok((None, None));
693    };
694    let ops = resource.node(Some(list), "resource tags")?;
695    let (pairs, rest) = ops.ops.as_chunks::<2>();
696    if !rest.is_empty() {
697        return Err(ops.fail("a tag without its value"));
698    }
699    let (mut element, mut stride) = (None, None);
700    for [tag, value] in pairs {
701        let value = ops.number_of(*value, "tag value")?;
702        match ops.int_of(*tag, "tag")? {
703            0 => element = Some(value),
704            1 => stride = Some(value),
705            _ => {}
706        }
707    }
708    Ok((element, stride))
709}
710
711/// The module global a resource record's variable names: the global itself, or the one it is a cast of.
712fn variable_of(m: &Module, id: Option<MetadataId>) -> Option<ValueId> {
713    let Some(Metadata::Value { value, .. }) = id.and_then(|i| m.metadata.get(i)) else {
714        return None;
715    };
716    match m.values.get(*value)? {
717        Value::Constant(Constant::Cast { value, .. }) => Some(*value),
718        Value::Global { .. } => Some(*value),
719        _ => None,
720    }
721}
722
723/// The bound resource ranges of `dx.resources` (SRVs, UAVs, constant buffers, samplers), each with its variable's name
724/// and the type its variable points to (the resource's handle type).
725pub fn ranges(m: &Module) -> Result<Vec<(Range, String, Option<TypeId>)>> {
726    let Some((at, [resources])) = named(m, "dx.resources") else {
727        return Ok(Vec::new());
728    };
729    let lists = Node::new(m, at, *resources, "dx.resources")?;
730    let mut out = Vec::new();
731    for (class, list) in lists.ops.iter().enumerate() {
732        let Some(list) = list else { continue };
733        let class =
734            u8::try_from(class).map_err(|_| lists.fail(format!("resource class {class} is out of range")))?;
735        let list = lists.node(Some(*list), "resource list")?;
736        for r in list.ops {
737            let r = list.node(*r, "resource")?;
738            // The record's variable is an undef pointer to the resource's type.
739            let ty = match r.op(1)?.and_then(|i| m.metadata.get(i)) {
740                Some(Metadata::Value { ty, .. }) => match m.types.get(*ty) {
741                    Some(Type::Pointer(t, _)) => Some(*t),
742                    _ => None,
743                },
744                _ => None,
745            };
746            let extra = r.number(6, "resource field")?;
747            let (element, stride) = match class {
748                0 => tags(r, r.maybe(8))?,
749                1 => tags(r, r.maybe(10))?,
750                _ => (None, None),
751            };
752            out.push((
753                Range {
754                    class,
755                    id: r.number(0, "resource id")?,
756                    lower: r.number(4, "lower bound")?,
757                    // an unbounded range is written -1 (all ones in 32 bits)
758                    count: {
759                        let v = r.int(5, "range size")?;
760                        u32::try_from(v)
761                            .or_else(|_| i32::try_from(v).map(|v| u32::from_ne_bytes(v.to_ne_bytes())))
762                            .map_err(|_| r.fail(format!("range size {v} is out of range")))?
763                    },
764                    extra,
765                    space: r.number(3, "register space")?,
766                    element,
767                    stride,
768                    // a UAV's record states whether it has a counter after the shape and the coherence
769                    counter: class == 1 && r.maybe(8).is_some() && r.int(8, "counter")? != 0,
770                    variable: variable_of(m, r.op(1)?),
771                },
772                r.string_of(r.op(2)?, "resource name")?,
773                ty,
774            ));
775        }
776    }
777    Ok(out)
778}
779
780/// The optional fields of a resource record that [`Range`] leaves out, keyed by the record's class and id.
781#[derive(Clone, Debug, Default, PartialEq, Eq)]
782pub struct ResourceExtras {
783    /// The class (0 SRV, 1 UAV, 2 constant buffer, 3 sampler), as in [`Range::class`].
784    pub class: u8,
785    /// The range id, as in [`Range::id`].
786    pub id: u32,
787    /// A shader resource view's sample count (operand 7): 0 for a single-sampled view, 4 for a `Texture2DMS<_, 4>`.
788    pub sample_count: Option<u32>,
789    /// An unordered-access view declared `globallycoherent` (operand 7).
790    pub globally_coherent: bool,
791    /// An unordered-access view that is rasterizer ordered (operand 9).
792    pub rasterizer_ordered: bool,
793    /// Tag 2 of the record's tag list: the kind of the sampler-feedback texture, where it states one.
794    pub sampler_feedback: Option<u32>,
795    /// Tag 3 of the record's tag list: the view is the target of a 64-bit atomic operation.
796    pub atomic64: bool,
797    /// Every tag of the record's tag list with its value, as stored (0 element type, 1 stride, 2 sampler feedback
798    /// kind, 3 64-bit atomics).
799    pub tags: Vec<(i64, i64)>,
800}
801
802/// The optional fields of every resource record, in the order of [`ranges`]. A constant buffer's and a sampler's record
803/// have a tag list as their last operand as well.
804pub fn resource_extras(m: &Module) -> Result<Vec<ResourceExtras>> {
805    let Some((at, [resources])) = named(m, "dx.resources") else {
806        return Ok(Vec::new());
807    };
808    let lists = Node::new(m, at, *resources, "dx.resources")?;
809    let mut out = Vec::new();
810    for (class, list) in lists.ops.iter().enumerate() {
811        let Some(list) = list else { continue };
812        let class =
813            u8::try_from(class).map_err(|_| lists.fail(format!("resource class {class} is out of range")))?;
814        let list = lists.node(Some(*list), "resource list")?;
815        for r in list.ops {
816            let r = list.node(*r, "resource")?;
817            // the tag list is the last operand: 8 for an SRV, 10 for a UAV, 7 for a constant buffer and a sampler
818            let tag_operand = match class {
819                0 => 8,
820                1 => 10,
821                _ => 7,
822            };
823            let mut tags = Vec::new();
824            if let Some(tag_list) = r.maybe(tag_operand) {
825                let tag_list = r.node(Some(tag_list), "resource tags")?;
826                let (pairs, rest) = tag_list.ops.as_chunks::<2>();
827                if !rest.is_empty() {
828                    return Err(tag_list.fail("a tag without its value"));
829                }
830                for [tag, value] in pairs {
831                    tags.push((
832                        tag_list.int_of(*tag, "tag")?,
833                        tag_list.int_of(*value, "tag value")?,
834                    ));
835                }
836            }
837            let tag_value = |wanted: i64| tags.iter().find(|(t, _)| *t == wanted).map(|(_, v)| *v);
838            out.push(ResourceExtras {
839                class,
840                id: r.number(0, "resource id")?,
841                sample_count: match (class, r.maybe(7)) {
842                    (0, Some(_)) => Some(r.number(7, "sample count")?),
843                    _ => None,
844                },
845                globally_coherent: class == 1 && r.maybe(7).is_some() && r.int(7, "globally coherent")? != 0,
846                rasterizer_ordered: class == 1
847                    && r.maybe(9).is_some()
848                    && r.int(9, "rasterizer ordered")? != 0,
849                sampler_feedback: match tag_value(2) {
850                    Some(v) => Some(
851                        u32::try_from(v)
852                            .map_err(|_| r.fail(format!("sampler feedback kind {v} is out of range")))?,
853                    ),
854                    None => None,
855                },
856                atomic64: tag_value(3).is_some_and(|v| v != 0),
857                tags,
858            });
859        }
860    }
861    Ok(out)
862}
863
864/// One field of a struct type, as its annotation in `dx.typeAnnotations` states it.
865#[derive(Clone, Debug, PartialEq, Eq)]
866pub struct FieldAnnotation {
867    /// The field's name.
868    pub name: String,
869    /// Byte offset in the constant buffer.
870    pub offset: u32,
871    /// Its `DxilComponentType`: LLVM integers carry no signedness, the annotation does.
872    pub component: Option<u32>,
873    /// A matrix field's rows, columns and orientation (1 row-major, 2 column-major): LLVM lowers a matrix to an array
874    /// of its lines, so only the annotation says it is one.
875    pub matrix: Option<(u32, u32, u32)>,
876}
877
878/// A struct type's annotation in `dx.typeAnnotations`: its size in a constant buffer and its fields.
879#[derive(Clone, Debug, PartialEq, Eq)]
880pub struct StructAnnotation {
881    /// The struct's size in bytes as a constant buffer lays it out (the annotation's first operand).
882    pub size: u32,
883    /// The annotation of each field, in the struct's order.
884    pub fields: Vec<FieldAnnotation>,
885}
886
887/// Fields of a struct type from `dx.typeAnnotations` (field tag 6 the name, 3 the constant-buffer offset, 7 the
888/// component type). Empty when the module has no annotation for it.
889pub fn fields(m: &Module, t: TypeId) -> Result<Vec<FieldAnnotation>> {
890    Ok(struct_annotation(m, t)?.map(|a| a.fields).unwrap_or_default())
891}
892
893/// The annotation `dx.typeAnnotations` states for a struct type (kind 0, then pairs of a struct-typed value and its
894/// annotation: the size, one node per field, and from DXIL 1.5 a node of template arguments after them), where it
895/// states one.
896pub fn struct_annotation(m: &Module, t: TypeId) -> Result<Option<StructAnnotation>> {
897    let Some((at, annotations)) = named(m, "dx.typeAnnotations") else {
898        return Ok(None);
899    };
900    let field_count = match m.types.get(t) {
901        Some(Type::Struct { elements, .. }) => elements.len(),
902        _ => return Ok(None),
903    };
904    for &a in annotations {
905        let a = Node::new(m, at, a, "type annotations")?;
906        // Struct annotations: kind 0, then (struct type, annotation) pairs.
907        if a.int(0, "annotation kind")? != 0 {
908            continue;
909        }
910        let (pairs, rest) = a.ops.get(1..).unwrap_or_default().as_chunks::<2>();
911        if !rest.is_empty() {
912            return Err(a.fail("a struct type without its annotation"));
913        }
914        for [struct_type, annotation] in pairs {
915            let ty = match struct_type.and_then(|i| m.metadata.get(i)) {
916                Some(Metadata::Value { ty, .. }) => *ty,
917                _ => continue,
918            };
919            if ty != t {
920                continue;
921            }
922            let annotation = a.node(*annotation, "struct annotation")?;
923            // an empty struct's annotation is its size alone
924            let fields = match annotation.ops.len() {
925                1 => &[][..],
926                n if n == field_count.saturating_add(1) || n == field_count.saturating_add(2) => {
927                    annotation.ops.get(1..=field_count).unwrap_or_default()
928                }
929                n => {
930                    return Err(annotation.fail(format!("{n} operands for a struct of {field_count} fields")));
931                }
932            };
933            let fields = fields
934                .iter()
935                .map(|f| {
936                    let f = annotation.node(*f, "field annotation")?;
937                    let (mut name, mut offset, mut component, mut matrix) = (String::new(), 0, None, None);
938                    let (tags, rest) = f.ops.as_chunks::<2>();
939                    if !rest.is_empty() {
940                        return Err(f.fail("a tag without its value"));
941                    }
942                    for [tag, value] in tags {
943                        match f.int_of(*tag, "field tag")? {
944                            6 => name = f.string_of(*value, "field name")?,
945                            3 => offset = f.number_of(*value, "field offset")?,
946                            2 => {
947                                let ops = f.node(*value, "matrix annotation")?;
948                                matrix = Some((
949                                    ops.number(0, "matrix rows")?,
950                                    ops.number(1, "matrix columns")?,
951                                    ops.number(2, "matrix orientation")?,
952                                ));
953                            }
954                            7 => component = Some(f.number_of(*value, "field component type")?),
955                            _ => {}
956                        }
957                    }
958                    Ok(FieldAnnotation {
959                        name,
960                        offset,
961                        component,
962                        matrix,
963                    })
964                })
965                .collect::<Result<Vec<_>>>()?;
966            return Ok(Some(StructAnnotation {
967                size: annotation.number(0, "struct size")?,
968                fields,
969            }));
970        }
971    }
972    Ok(None)
973}
974
975/// The shader model `dx.shaderModel` states: the kind as dxc's targets name it (`ps`, `vs`, `lib`, `ms`, ...) and the
976/// major and minor version.
977pub fn shader_model(m: &Module) -> Result<Option<(String, u32, u32)>> {
978    let Some((at, [model])) = named(m, "dx.shaderModel") else {
979        return Ok(None);
980    };
981    let model = Node::new(m, at, *model, "dx.shaderModel")?;
982    Ok(Some((
983        model.string_of(model.op(0)?, "shader kind")?,
984        model.number(1, "shader model major")?,
985        model.number(2, "shader model minor")?,
986    )))
987}
988
989/// The serialised view-id state `dx.viewIdState` holds (an `i32` array: the input scalar count, then per stream the
990/// output scalar count and its dependency tables), where the module has it.
991pub fn view_id_state(m: &Module) -> Result<Option<Vec<u32>>> {
992    let Some((at, [state])) = named(m, "dx.viewIdState") else {
993        return Ok(None);
994    };
995    let state = Node::new(m, at, *state, "dx.viewIdState")?;
996    let value = match state.op(0)?.and_then(|i| m.metadata.get(i)) {
997        Some(Metadata::Value { value, .. }) => *value,
998        _ => return Err(state.fail("expected an array constant")),
999    };
1000    let len = match m.value_types.get(value).and_then(|t| m.types.get(*t)) {
1001        Some(Type::Array(n, _)) => {
1002            usize::try_from(*n).map_err(|_| state.fail("an array past the address space"))?
1003        }
1004        _ => return Err(state.fail("expected an i32 array")),
1005    };
1006    let word = |v: &Value| -> Result<u32> {
1007        match v {
1008            // the low 32 bits of the sign-extended value
1009            Value::Constant(Constant::Integer(v)) => Ok(u32::try_from(v & 0xFFFF_FFFF).unwrap_or(0)),
1010            Value::Constant(Constant::Null) => Ok(0),
1011            _ => Err(state.fail("an element that is not an integer constant")),
1012        }
1013    };
1014    match m.values.get(value) {
1015        Some(Value::Constant(Constant::Null)) => Ok(Some(vec![0; len])),
1016        Some(Value::Constant(Constant::Data(words))) => {
1017            Ok(Some(words.iter().map(|w| (w & 0xFFFF_FFFF) as u32).collect()))
1018        }
1019        Some(Value::Constant(Constant::Aggregate(ids))) => ids
1020            .iter()
1021            .map(|id| word(m.values.get(*id).unwrap_or(&Value::Local)))
1022            .collect::<Result<Vec<_>>>()
1023            .map(Some),
1024        _ => Err(state.fail("expected an array constant")),
1025    }
1026}
1027
1028/// One element of an `ISG1`/`OSG1`/`PSG1` part as stored.
1029#[derive(Clone, Debug, PartialEq, Eq)]
1030pub struct SignatureElement {
1031    /// The geometry stream the element is written to (0 for every other element).
1032    pub stream: u32,
1033    /// The semantic name.
1034    pub name: String,
1035    /// The semantic index.
1036    pub index: u32,
1037    /// `D3D_NAME`.
1038    pub system_value: u32,
1039    /// `D3D_REGISTER_COMPONENT_TYPE`.
1040    pub component_type: u32,
1041    /// The register the element is packed into.
1042    pub register: u32,
1043    /// The components the element occupies, one bit each (x is bit 0).
1044    pub mask: u8,
1045    /// The components a program reads (inputs) or never writes (outputs), as stored.
1046    pub read_write_mask: u8,
1047    /// Where the record starts, in bits of the part.
1048    pub bit: usize,
1049}
1050
1051impl SignatureElement {
1052    /// The name dxc's listing gives the element's system value (`DxilProgramSigSemantic`, D3D's `D3D_NAME`): `POS`,
1053    /// `TARGET`, ...; `NONE` for none.
1054    pub fn system_value_name(&self) -> &'static str {
1055        match self.system_value {
1056            1 => "POS",
1057            2 => "CLIPDST",
1058            3 => "CULLDST",
1059            4 => "RTINDEX",
1060            5 => "VPINDEX",
1061            6 => "VERTID",
1062            7 => "PRIMID",
1063            8 => "INSTID",
1064            9 => "FFACE",
1065            10 => "SAMPLE",
1066            11 => "QUADEDGE",
1067            12 => "QUADINT",
1068            13 => "TRIEDGE",
1069            14 => "TRIINT",
1070            15 => "LINEDET",
1071            16 => "LINEDEN",
1072            23 => "BARYCEN",
1073            24 => "SHDINGRATE",
1074            25 => "CULLPRIM",
1075            64 => "TARGET",
1076            65 => "DEPTH",
1077            66 => "COVERAGE",
1078            67 => "DEPTHGE",
1079            68 => "DEPTHLE",
1080            69 => "STENCILREF",
1081            70 => "INNERCOV",
1082            _ => "NONE",
1083        }
1084    }
1085
1086    /// The name dxc's listing gives the element's component type (`DxilProgramSigCompType`): `float`, `uint`, ...
1087    pub fn format_name(&self) -> &'static str {
1088        match self.component_type {
1089            1 => "uint",
1090            2 => "int",
1091            3 => "float",
1092            4 => "uint16",
1093            5 => "int16",
1094            6 => "fp16",
1095            7 => "uint64",
1096            8 => "int64",
1097            9 => "double",
1098            _ => "unknown",
1099        }
1100    }
1101}
1102
1103/// The name dxc gives a DXIL component type (`CompType::GetName`: 4 `i32`, 5 `u32`, 9 `f32`, ...); `invalid` for 0
1104/// and unknown numbers.
1105pub fn component_name(component: u32) -> &'static str {
1106    match component {
1107        1 => "i1",
1108        2 => "i16",
1109        3 => "u16",
1110        4 => "i32",
1111        5 => "u32",
1112        6 => "i64",
1113        7 => "u64",
1114        8 => "f16",
1115        9 => "f32",
1116        10 => "f64",
1117        11 => "snorm_f16",
1118        12 => "unorm_f16",
1119        13 => "snorm_f32",
1120        14 => "unorm_f32",
1121        15 => "snorm_f64",
1122        16 => "unorm_f64",
1123        17 => "p32i8",
1124        18 => "p32u8",
1125        _ => "invalid",
1126    }
1127}
1128
1129/// The name dxc's listing gives a resource kind's dimension (`DxilResourceBase::GetResDimName`: 2 `2d`, 10 `buf`, 12
1130/// `structbuf`, ...); `invalid` for 0 and unknown numbers.
1131pub fn dimension_name(kind: u32) -> &'static str {
1132    match kind {
1133        1 => "1d",
1134        2 => "2d",
1135        3 => "2dMS",
1136        4 => "3d",
1137        5 => "cube",
1138        6 => "1darray",
1139        7 => "2darray",
1140        8 => "2darrayMS",
1141        9 => "cubearray",
1142        10 => "buf",
1143        11 => "rawbuf",
1144        12 => "structbuf",
1145        13 => "cbuffer",
1146        14 => "sampler",
1147        15 => "tbuffer",
1148        16 => "ras",
1149        17 => "fbtex2d",
1150        18 => "fbtex2darray",
1151        _ => "invalid",
1152    }
1153}
1154
1155/// The elements of a signature part (`ISG1`, `OSG1` or `PSG1`): a count and the offset of the first record, then
1156/// records of 32 bytes (stream, name offset, semantic index, system value, component type, register, mask and
1157/// read-write mask, minimum precision). A name ends within 256 bytes.
1158pub fn signature(part: &[u8]) -> Result<Vec<SignatureElement>> {
1159    let bit = |byte: usize| byte.saturating_mul(8);
1160    let v = primase_foundations::bytes::View::new(part);
1161    let word = |at: usize| -> Result<u32> {
1162        v.array::<4>(at)
1163            .map(u32::from_le_bytes)
1164            .map_err(|e| error(bit(at), e.to_string()))
1165    };
1166    // the word `delta` bytes after a record's start
1167    let field = |record: usize, delta: usize| -> Result<u32> {
1168        word(
1169            record
1170                .checked_add(delta)
1171                .ok_or_else(|| error(bit(record), "signature element offset"))?,
1172        )
1173    };
1174    let n = word(0)? as usize;
1175    let first = word(4)? as usize;
1176    // the records lie in the part: bound the count before anything is allocated
1177    let table = n
1178        .checked_mul(32)
1179        .and_then(|size| first.checked_add(size))
1180        .filter(|&end| end <= part.len())
1181        .ok_or_else(|| {
1182            error(
1183                0,
1184                format!(
1185                    "{n} records of 32 bytes from byte {first} do not fit the part's {} bytes",
1186                    part.len()
1187                ),
1188            )
1189        })?;
1190    let mut out = Vec::with_capacity(n);
1191    for i in 0..n {
1192        let e = i
1193            .checked_mul(32)
1194            .and_then(|o| o.checked_add(first))
1195            .filter(|&e| e < table)
1196            .ok_or_else(|| error(32, "signature element offset"))?;
1197        let name_at = field(e, 4)? as usize;
1198        let tail = part
1199            .get(name_at..)
1200            .ok_or_else(|| error(bit(name_at), "signature name offset"))?;
1201        let end = tail
1202            .iter()
1203            .position(|&b| b == 0)
1204            .filter(|&n| n <= 256)
1205            .ok_or_else(|| error(bit(name_at), "signature name"))?;
1206        let name = tail
1207            .get(..end)
1208            .and_then(|name| std::str::from_utf8(name).ok())
1209            .ok_or_else(|| error(bit(name_at), "signature name is not UTF-8"))?;
1210        let [mask, read_write_mask] = v
1211            .array::<2>(
1212                e.checked_add(24)
1213                    .ok_or_else(|| error(bit(e), "signature element offset"))?,
1214            )
1215            .map_err(|x| error(bit(e), x.to_string()))?;
1216        out.push(SignatureElement {
1217            stream: field(e, 0)?,
1218            name: name.to_owned(),
1219            index: field(e, 8)?,
1220            system_value: field(e, 12)?,
1221            component_type: field(e, 16)?,
1222            register: field(e, 20)?,
1223            mask,
1224            read_write_mask,
1225            bit: bit(e),
1226        });
1227    }
1228    Ok(out)
1229}