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