Skip to main content

dxil_reader/
reflection.rs

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