Skip to main content

dxbc_reader/
decode.rs

1//! The token-level decoder of Shader Model 4 and 5 programs: any stage's version token, declarations as typed
2//! records, and instructions as an opcode, its control bits, its extended tokens and its operands. It decides nothing
3//! about which instructions a caller can use: they are all returned, and the caller refuses what it cannot handle. A token
4//! it cannot read at all (an operand register type it has no name for, a truncated instruction) is an error naming the
5//! word.
6
7use crate::opcodes::NAMES;
8use crate::{Result, error};
9
10/// The low byte of `v`.
11fn low8(v: u32) -> u8 {
12    let [byte, ..] = v.to_le_bytes();
13    byte
14}
15
16/// The low 16 bits of `v`.
17fn low16(v: u32) -> u16 {
18    let [a, b, ..] = v.to_le_bytes();
19    u16::from_le_bytes([a, b])
20}
21
22/// The kind of register an operand names.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum Register {
25    /// A temporary register (`r<n>`).
26    Temp,
27    /// An input register (`v<n>`).
28    Input,
29    /// An output register (`o<n>`).
30    Output,
31    /// An indexable temporary (`x<n>[i]`).
32    IndexableTemp,
33    /// A constant buffer register (`cb<slot>[row]`).
34    Constant,
35    /// An immediate operand (`l(...)`), its values in [`Operand::immediate`].
36    Immediate,
37    /// A shader resource view (`t<n>`).
38    Texture,
39    /// A sampler (`s<n>`).
40    Sampler,
41    /// The null register (`null`), a destination that discards its result.
42    Null,
43    /// An unordered-access view (`u<n>`).
44    Uav,
45    /// The immediate constant buffer (`icb[n]`), its rows the program's own.
46    ImmediateBuffer,
47    /// `vPrim`, the primitive id input.
48    PrimitiveId,
49    /// `vCoverage`, the input coverage mask.
50    InputCoverage,
51    /// `oDepth`.
52    OutputDepth,
53    /// `oDepthGE`, a depth output that is at least the rasterized depth.
54    OutputDepthGreaterEqual,
55    /// `oDepthLE`, a depth output that is at most the rasterized depth.
56    OutputDepthLessEqual,
57    /// `oMask`, the coverage output.
58    OutputCoverage,
59    /// `oStencilRef`, the stencil reference output.
60    OutputStencilRef,
61    /// `g<n>`, thread-group shared memory.
62    GroupSharedMemory,
63    /// `vThreadID`, `vThreadGroupID`, `vThreadIDInGroup` and `vThreadIDInGroupFlattened`, a compute program's ids.
64    ThreadId,
65    /// `vThreadGroupID`.
66    ThreadGroupId,
67    /// `vThreadIDInGroup`.
68    ThreadIdInGroup,
69    /// `vThreadIDInGroupFlattened`.
70    ThreadIdInGroupFlattened,
71    /// `vOutputControlPointID`, `vForkInstanceID`, `vJoinInstanceID`: a hull program's phase numbers.
72    OutputControlPointId,
73    /// `vForkInstanceID`, a hull program's fork phase instance.
74    ForkInstanceId,
75    /// `vJoinInstanceID`, a hull program's join phase instance.
76    JoinInstanceId,
77    /// `vicp[point][register]`: an input control point of a hull or domain program.
78    InputControlPoint,
79    /// `vocp[point][register]`: an output control point a hull program's patch-constant phases read.
80    OutputControlPoint,
81    /// `vpc[register]`: a patch constant a domain program reads (or a hull program's join phase).
82    InputPatchConstant,
83    /// `vDomain`: a domain program's location in its patch.
84    InputDomainPoint,
85    /// `m<n>`: a geometry program's output stream.
86    Stream,
87    /// `vGSInstanceID`.
88    GsInstanceId,
89}
90impl Register {
91    /// How many index dimensions an operand of this register has in Shader Model 5.0.
92    fn dimensions(self) -> u32 {
93        match self {
94            Register::Immediate
95            | Register::Null
96            | Register::PrimitiveId
97            | Register::InputCoverage
98            | Register::OutputDepth
99            | Register::OutputDepthGreaterEqual
100            | Register::OutputDepthLessEqual
101            | Register::OutputCoverage
102            | Register::OutputStencilRef
103            | Register::ThreadId
104            | Register::ThreadGroupId
105            | Register::ThreadIdInGroup
106            | Register::ThreadIdInGroupFlattened
107            | Register::OutputControlPointId
108            | Register::ForkInstanceId
109            | Register::JoinInstanceId
110            | Register::InputDomainPoint
111            | Register::GsInstanceId => 0,
112            Register::Constant
113            | Register::IndexableTemp
114            | Register::InputControlPoint
115            | Register::OutputControlPoint => 2,
116            _ => 1,
117        }
118    }
119    fn from_type(n: u32) -> Option<Self> {
120        Some(match n {
121            0 => Register::Temp,
122            1 => Register::Input,
123            2 => Register::Output,
124            3 => Register::IndexableTemp,
125            4 => Register::Immediate,
126            6 => Register::Sampler,
127            7 => Register::Texture,
128            8 => Register::Constant,
129            9 => Register::ImmediateBuffer,
130            11 => Register::PrimitiveId,
131            12 => Register::OutputDepth,
132            13 => Register::Null,
133            15 => Register::OutputCoverage,
134            16 => Register::Stream,
135            22 => Register::OutputControlPointId,
136            23 => Register::ForkInstanceId,
137            24 => Register::JoinInstanceId,
138            25 => Register::InputControlPoint,
139            26 => Register::OutputControlPoint,
140            27 => Register::InputPatchConstant,
141            28 => Register::InputDomainPoint,
142            37 => Register::GsInstanceId,
143            30 => Register::Uav,
144            31 => Register::GroupSharedMemory,
145            32 => Register::ThreadId,
146            33 => Register::ThreadGroupId,
147            34 => Register::ThreadIdInGroup,
148            36 => Register::ThreadIdInGroupFlattened,
149            35 => Register::InputCoverage,
150            38 => Register::OutputDepthGreaterEqual,
151            39 => Register::OutputDepthLessEqual,
152            41 => Register::OutputStencilRef,
153            _ => return None,
154        })
155    }
156}
157
158/// The components of a register an operand selects.
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub enum Selection {
161    /// A write mask: bit 0 is x, bit 3 is w.
162    Mask(u8),
163    /// A four-component swizzle: for each result lane the source lane (0 x to 3 w) it reads.
164    Swizzle([u8; 4]),
165    /// One source lane (0 x to 3 w).
166    Scalar(u8),
167    /// No selection: the operand has no components (a sampler, a resource).
168    None,
169}
170
171/// An instruction's or declaration's operand.
172#[derive(Clone, Debug)]
173pub struct Operand {
174    /// The kind of register it names.
175    pub register: Register,
176    /// Which components it selects.
177    pub selection: Selection,
178    /// Its index dimensions, outermost first: none for an immediate, one for `r3`, two for `cb2[5]`.
179    pub indices: Vec<Index>,
180    /// The values of an immediate operand (one lane repeated, or four); zero for other registers.
181    pub immediate: [u32; 4],
182    /// 0 none, 1 negate, 2 absolute, 3 negated absolute (float sources); 4 two's-complement negate (an integer
183    /// instruction's negate; the encoding has no such value, so a caller that knows the instruction reads integers sets it).
184    pub modifier: u8,
185    /// The minimum-precision annotation of the operand's extended token (0 none; 1 float16, 2 float2_8, 4 sint16,
186    /// 5 uint16).
187    pub precision: u8,
188    /// The operand's extended token marks it as a non-uniform resource index.
189    pub non_uniform: bool,
190}
191
192/// One index dimension of an operand: a constant, a relative register, or both added.
193#[derive(Clone, Debug)]
194pub struct Index {
195    /// The constant part of the index.
196    pub base: u32,
197    /// The register whose component is added to `base`, where the index is relative.
198    pub relative: Option<Box<Operand>>,
199}
200
201/// An instruction as stored.
202#[derive(Clone, Debug)]
203pub struct Instruction {
204    /// The instruction's first word in the token stream.
205    pub word: usize,
206    /// The opcode token's low 11 bits (see [`opcode_name`]).
207    pub opcode: u16,
208    /// The opcode token's bits 11 to 23: saturate and the opcode-specific controls (the test of a conditional, a
209    /// `resinfo` return type, the interpolation of a declaration).
210    pub control: u32,
211    /// The opcode's extended tokens as stored (sample offsets, resource dimension, return type).
212    pub extensions: Vec<u32>,
213    /// The saturate bit of the opcode token.
214    pub saturate: bool,
215    /// A conditional (`if`, `breakc`, `continuec`) taken when its operand is nonzero (`_nz`), else when zero (`_z`).
216    pub nonzero: bool,
217    /// The operands, destination first where the instruction has one.
218    pub operands: Vec<Operand>,
219}
220impl Instruction {
221    /// The opcode's name (`mov`, `add`, ...), or `unknown opcode`.
222    pub fn name(&self) -> &'static str {
223        opcode_name(self.opcode)
224    }
225    /// The sample offsets of the extended token of kind 1 (`_aoffimmi`), if the instruction has one.
226    pub fn sample_offset(&self) -> Option<[i32; 3]> {
227        self.extension(1).map(|e| {
228            let field = |shift: u32| (((e >> shift) & 15) as i32) << 28 >> 28;
229            [field(9), field(13), field(17)]
230        })
231    }
232    /// The resource dimension and structured stride of the extended token of kind 2.
233    pub fn resource_dimension(&self) -> Option<(u32, u32)> {
234        self.extension(2).map(|e| ((e >> 6) & 31, (e >> 11) & 0xf_ffff))
235    }
236    /// The return-type token of the extended token of kind 3 (four nibbles).
237    pub fn return_type(&self) -> Option<u32> {
238        self.extension(3).map(|e| (e >> 6) & 0xffff)
239    }
240    fn extension(&self, kind: u32) -> Option<u32> {
241        self.extensions.iter().copied().find(|e| e & 63 == kind)
242    }
243}
244
245/// The name of an opcode in Microsoft's tokenized program format, or `unknown opcode`.
246pub fn opcode_name(opcode: u16) -> &'static str {
247    NAMES
248        .get(usize::from(opcode))
249        .copied()
250        .unwrap_or("unknown opcode")
251}
252
253/// The type of a program's four return lanes in a resource declaration, as D3D_RETURN_TYPE codes (1 unorm, 2 snorm,
254/// 3 sint, 4 uint, 5 float, 6 mixed, 7 double, 8 continued, 9 unused), one nibble per lane.
255pub type ReturnCodes = [u8; 4];
256
257/// A declaration, typed. `word` is where it starts.
258#[derive(Clone, Debug)]
259pub struct Declaration {
260    /// The word of the token stream the declaration starts at.
261    pub word: usize,
262    /// The opcode token's low 11 bits (see [`opcode_name`]).
263    pub opcode: u16,
264    /// The opcode token's bits 11 to 23 as stored.
265    pub control: u32,
266    /// The declaration, typed.
267    pub kind: Declared,
268}
269
270/// A declaration, typed where this reader knows its layout.
271#[derive(Clone, Debug)]
272pub enum Declared {
273    /// `dcl_temps`.
274    Temps(u32),
275    /// `dcl_indexable_temp x<register>[size], components`.
276    IndexableTemp {
277        /// The register index (the `<n>` of `x<n>`).
278        register: u32,
279        /// The number of elements.
280        size: u32,
281        /// The number of components per element.
282        components: u32,
283    },
284    /// `dcl_globalFlags`: the control bits as stored (bit 0 refactoring allowed, 1 double precision, 2 forced early
285    /// depth-stencil, 3 raw and structured buffers, 4 skip optimization, 5 min precision, 6 double extensions, 7
286    /// shader extensions).
287    GlobalFlags(u32),
288    /// `dcl_constantbuffer cb<slot>[rows]`; `dynamic` when it is dynamically indexed.
289    ConstantBuffer {
290        /// The buffer slot (the `<slot>` of `cb<slot>`).
291        slot: u32,
292        /// The number of 16-byte rows; 0 where the declaration states none.
293        rows: u32,
294        /// The buffer is dynamically indexed.
295        dynamic: bool,
296        /// The number of index dimensions the operand had (1 or 2).
297        dimensions: usize,
298    },
299    /// `dcl_sampler s<slot>`, its mode: 0 default, 1 comparison, 2 mono.
300    Sampler {
301        /// The sampler slot.
302        slot: u32,
303        /// 0 default, 1 comparison, 2 mono.
304        mode: u32,
305    },
306    /// `dcl_resource_<dimension> t<slot>`: the D3D resource dimension code, the sample count of a multisampled one,
307    /// and the four return types.
308    Resource {
309        /// The resource slot.
310        slot: u32,
311        /// The D3D resource dimension code.
312        dimension: u32,
313        /// The sample count of a multisampled resource.
314        samples: u32,
315        /// The return type of each lane.
316        returns: ReturnCodes,
317        /// The return type token as stored.
318        token: u32,
319    },
320    /// `dcl_resource_raw t<slot>`.
321    ResourceRaw {
322        /// The resource slot.
323        slot: u32,
324    },
325    /// `dcl_resource_structured t<slot>, stride`.
326    ResourceStructured {
327        /// The resource slot.
328        slot: u32,
329        /// The element stride in bytes.
330        stride: u32,
331    },
332    /// `dcl_uav_typed` and its control bits (dimension in bits 0 to 4, globally coherent bit 5).
333    UavTyped {
334        /// The UAV slot.
335        slot: u32,
336        /// The D3D resource dimension code.
337        dimension: u32,
338        /// The return type of each lane.
339        returns: ReturnCodes,
340        /// The return type token as stored.
341        token: u32,
342        /// The opcode token's control bits.
343        control: u32,
344    },
345    /// `dcl_uav_raw u<slot>`.
346    UavRaw {
347        /// The UAV slot.
348        slot: u32,
349        /// The opcode token's control bits.
350        control: u32,
351    },
352    /// `dcl_uav_structured u<slot>, stride`.
353    UavStructured {
354        /// The UAV slot.
355        slot: u32,
356        /// The element stride in bytes.
357        stride: u32,
358        /// The opcode token's control bits.
359        control: u32,
360    },
361    /// `dcl_thread_group x, y, z`.
362    ThreadGroup([u32; 3]),
363    /// `dcl_tgsm_raw g<register>, bytes`.
364    GroupSharedRaw {
365        /// The shared memory register index.
366        register: u32,
367        /// Its size in bytes.
368        bytes: u32,
369    },
370    /// `dcl_tgsm_structured g<register>, stride, count`.
371    GroupSharedStructured {
372        /// The shared memory register index.
373        register: u32,
374        /// The element stride in bytes.
375        stride: u32,
376        /// The number of elements.
377        count: u32,
378    },
379    /// An input register or special input: `dcl_input`, `_sgv`, `_siv`, `_ps`, `_ps_sgv`, `_ps_siv`. `interpolation` is
380    /// a pixel input's `D3D_INTERPOLATION_MODE`; `name` a system value's `D3D_NAME`.
381    Input {
382        /// The input register or special input the declaration names.
383        operand: Operand,
384        /// The interpolation mode of a pixel input.
385        interpolation: Option<u32>,
386        /// The system value (`D3D_NAME`) of a `_sgv` or `_siv` declaration.
387        name: Option<u32>,
388    },
389    /// An output register or special output: `dcl_output`, `_sgv`, `_siv`.
390    Output {
391        /// The output register or special output the declaration names.
392        operand: Operand,
393        /// The system value (`D3D_NAME`) of a `_siv` or `_sgv` declaration.
394        name: Option<u32>,
395    },
396    /// `dcl_immediateConstantBuffer`.
397    ImmediateBuffer(Vec<[u32; 4]>),
398    /// Custom data that is not an immediate constant buffer (a comment, debug information, ...) by its class.
399    CustomData {
400        /// The custom data class.
401        class: u32,
402    },
403    /// A declaration this decoder does not type: stage-specific (geometry, tessellation, compute), interface tables.
404    Other {
405        /// The opcode.
406        opcode: u16,
407        /// The opcode token's control bits.
408        control: u32,
409        /// The declaration's words after its operands, as stored.
410        words: Vec<u32>,
411    },
412}
413
414/// The kind of program a version token names.
415#[derive(Clone, Copy, Debug, PartialEq, Eq)]
416pub enum ProgramType {
417    /// A pixel program (`ps`).
418    Pixel,
419    /// A vertex program (`vs`).
420    Vertex,
421    /// A geometry program (`gs`).
422    Geometry,
423    /// A hull program (`hs`).
424    Hull,
425    /// A domain program (`ds`).
426    Domain,
427    /// A compute program (`cs`).
428    Compute,
429}
430impl ProgramType {
431    /// The target's stage prefix (`ps`, `vs`, ...).
432    pub fn name(self) -> &'static str {
433        match self {
434            ProgramType::Pixel => "ps",
435            ProgramType::Vertex => "vs",
436            ProgramType::Geometry => "gs",
437            ProgramType::Hull => "hs",
438            ProgramType::Domain => "ds",
439            ProgramType::Compute => "cs",
440        }
441    }
442}
443
444/// A decoded token stream: its target, declarations and instructions, each in program order.
445#[derive(Clone, Debug)]
446pub struct Decoded {
447    /// The program's stage.
448    pub kind: ProgramType,
449    /// The shader model's major version.
450    pub major: u32,
451    /// The shader model's minor version.
452    pub minor: u32,
453    /// The declarations, in program order.
454    pub declarations: Vec<Declaration>,
455    /// The instructions, in program order.
456    pub instructions: Vec<Instruction>,
457}
458impl Decoded {
459    /// The target's name (`ps_5_0`).
460    pub fn target(&self) -> String {
461        format!("{}_{}_{}", self.kind.name(), self.major, self.minor)
462    }
463}
464
465struct Cursor<'a> {
466    words: &'a [u32],
467    at: usize,
468    origin: usize,
469    /// The program's type: the input registers of a geometry, hull or domain program are indexed by vertex.
470    kind: ProgramType,
471}
472impl Cursor<'_> {
473    /// The word the cursor is at, in the token stream.
474    fn position(&self) -> usize {
475        self.origin.saturating_add(self.at)
476    }
477    fn take(&mut self) -> Result<u32> {
478        let value = *self
479            .words
480            .get(self.at)
481            .ok_or_else(|| error(self.position(), "truncated instruction"))?;
482        self.at = self.at.saturating_add(1);
483        Ok(value)
484    }
485    fn rest(&mut self) -> Vec<u32> {
486        let rest = self.words.get(self.at..).unwrap_or_default().to_vec();
487        self.at = self.words.len();
488        rest
489    }
490    fn operand(&mut self, depth: usize) -> Result<Operand> {
491        let pos = self.position();
492        if depth > 2 {
493            return Err(error(pos, "nested relative operand exceeds bound"));
494        }
495        let t = self.take()?;
496        let components = t & 3;
497        let selection = match components {
498            0 => Selection::None,
499            1 => Selection::Scalar(0),
500            2 => match (t >> 2) & 3 {
501                0 => Selection::Mask(low8((t >> 4) & 15)),
502                1 => Selection::Swizzle([4, 6, 8, 10].map(|shift| low8((t >> shift) & 3))),
503                2 => Selection::Scalar(low8((t >> 4) & 3)),
504                _ => return Err(error(pos, "reserved component selection")),
505            },
506            _ => return Err(error(pos, "unsupported N-component operand")),
507        };
508        let kind = (t >> 12) & 255;
509        let register = Register::from_type(kind)
510            .ok_or_else(|| error(pos, format!("unsupported operand register type {kind}")))?;
511        let (mut modifier, mut precision, mut non_uniform) = (0, 0, false);
512        if t >> 31 != 0 {
513            let mut ext = self.take()?;
514            loop {
515                // only the modifier extension (kind 1) exists in Shader Model 4 and 5
516                if ext & 63 != 1 {
517                    return Err(error(pos, "unsupported extended operand (precision/modifier)"));
518                }
519                modifier = low8((ext >> 6) & 255);
520                precision = low8((ext >> 14) & 7);
521                non_uniform = ext & (1 << 17) != 0;
522                if ext >> 31 == 0 {
523                    break;
524                }
525                ext = self.take()?;
526            }
527            if modifier > 3 {
528                return Err(error(pos, "unsupported extended operand (precision/modifier)"));
529            }
530        }
531        let dimensions = (t >> 20) & 3;
532        let expected = match (register, self.kind) {
533            (Register::Input, ProgramType::Geometry | ProgramType::Hull | ProgramType::Domain) => 2,
534            _ => register.dimensions(),
535        };
536        if dimensions != expected {
537            return Err(error(pos, "unexpected operand index dimensions"));
538        }
539        let mut indices = Vec::new();
540        // the representation of each index dimension, three bits each from bit 22
541        for shift in [22, 25, 28].into_iter().take(dimensions as usize) {
542            let representation = (t >> shift) & 7;
543            if !matches!(representation, 0 | 2 | 3) {
544                return Err(error(pos, "unsupported 64-bit/reserved index"));
545            }
546            let base = if representation != 2 { self.take()? } else { 0 };
547            let relative = if representation != 0 {
548                Some(Box::new(self.operand(depth.saturating_add(1))?))
549            } else {
550                None
551            };
552            indices.push(Index { base, relative });
553        }
554        let mut immediate = [0; 4];
555        if register == Register::Immediate {
556            match components {
557                1 => immediate = [self.take()?; 4],
558                2 => {
559                    for x in &mut immediate {
560                        *x = self.take()?;
561                    }
562                }
563                _ => return Err(error(pos, "immediate requires one or four components")),
564            }
565        }
566        Ok(Operand {
567            register,
568            selection,
569            indices,
570            immediate,
571            modifier,
572            precision,
573            non_uniform,
574        })
575    }
576}
577
578fn return_codes(token: u32) -> ReturnCodes {
579    [0, 4, 8, 12].map(|shift| low8((token >> shift) & 15))
580}
581
582/// The opcodes that are declarations (and the phase markers of hull programs, which carry none): 88-106 and
583/// 143-162 of Direct3D 10 and 11, the hull phases 113-116 and `dcl_gs_instance_count` (206).
584fn is_declaration(opcode: u16) -> bool {
585    matches!(opcode, 88..=106 | 113..=116 | 143..=162 | 206)
586}
587
588/// Decode a complete token stream: version token, length, then declarations and instructions to its end.
589pub fn decode(words: &[u32]) -> Result<Decoded> {
590    let (Some(&version), Some(&length)) = (words.first(), words.get(1)) else {
591        return Err(error(0, "token stream shorter than its header"));
592    };
593    if usize::try_from(length).ok() != Some(words.len()) {
594        return Err(error(
595            1,
596            format!(
597                "the declared length is {length} words, the stream has {}",
598                words.len()
599            ),
600        ));
601    }
602    let kind = match version >> 16 {
603        0 => ProgramType::Pixel,
604        1 => ProgramType::Vertex,
605        2 => ProgramType::Geometry,
606        3 => ProgramType::Hull,
607        4 => ProgramType::Domain,
608        5 => ProgramType::Compute,
609        other => {
610            return Err(error(
611                0,
612                format!("program type {other} is not a Shader Model 4 or 5 stage"),
613            ));
614        }
615    };
616    let mut result = Decoded {
617        kind,
618        major: (version >> 4) & 15,
619        minor: version & 15,
620        declarations: Vec::new(),
621        instructions: Vec::new(),
622    };
623    let mut at = 2;
624    while let Some(&t) = words.get(at) {
625        let opcode = low16(t & 0x7ff);
626        // customdata: its class in the opcode token's upper bits, its length (these two words included) the next word
627        if opcode == 53 {
628            let len = words
629                .get(at.saturating_add(1))
630                .and_then(|&n| usize::try_from(n).ok())
631                .unwrap_or(0);
632            let end = at.checked_add(len).filter(|&end| len >= 2 && end <= words.len());
633            let (Some(end), Some(body)) = (end, words.get(at.saturating_add(2)..at.saturating_add(len)))
634            else {
635                return Err(error(
636                    at,
637                    format!(
638                        "customdata of {len} words (at least 2) does not fit the stream's {} words",
639                        words.len()
640                    ),
641                ));
642            };
643            let class = t >> 11;
644            let kind = if class == 3 {
645                let (rows, rest) = body.as_chunks::<4>();
646                if !rest.is_empty() {
647                    return Err(error(
648                        at,
649                        format!(
650                            "an immediate constant buffer of {} words, expected whole rows of 4",
651                            body.len()
652                        ),
653                    ));
654                }
655                Declared::ImmediateBuffer(rows.to_vec())
656            } else {
657                Declared::CustomData { class }
658            };
659            result.declarations.push(Declaration {
660                word: at,
661                opcode,
662                control: class,
663                kind,
664            });
665            at = end;
666            continue;
667        }
668        let len = usize::try_from((t >> 24) & 127).unwrap_or(0);
669        if len == 0 {
670            return Err(error(at, "instruction length 0 (at least 1)"));
671        }
672        let end = at.checked_add(len).filter(|&end| end <= words.len());
673        let (Some(end), Some(body)) = (end, words.get(at.saturating_add(1)..at.saturating_add(len))) else {
674            return Err(error(
675                at,
676                format!(
677                    "instruction length {len} (at least 1) does not fit the stream's {} words",
678                    words.len()
679                ),
680            ));
681        };
682        let mut c = Cursor {
683            words: body,
684            at: 0,
685            origin: at.saturating_add(1),
686            kind,
687        };
688        let mut extensions = Vec::new();
689        let mut extended = t >> 31 != 0;
690        while extended {
691            let ext = c.take()?;
692            extensions.push(ext);
693            extended = ext >> 31 != 0;
694        }
695        let control = (t >> 11) & 0x1fff;
696        if is_declaration(opcode) {
697            let kind = declaration(opcode, control, &mut c)?;
698            if c.at != c.words.len() {
699                return Err(error(
700                    at,
701                    format!(
702                        "{} words after the operands of {}",
703                        c.words.len().saturating_sub(c.at),
704                        opcode_name(opcode)
705                    ),
706                ));
707            }
708            result.declarations.push(Declaration {
709                word: at,
710                opcode,
711                control,
712                kind,
713            });
714        } else {
715            let mut operands = Vec::new();
716            while c.at < c.words.len() {
717                operands.push(c.operand(0)?);
718            }
719            result.instructions.push(Instruction {
720                word: at,
721                opcode,
722                control,
723                extensions,
724                saturate: t & 0x2000 != 0,
725                nonzero: t & 0x4_0000 != 0,
726                operands,
727            });
728        }
729        at = end;
730    }
731    Ok(result)
732}
733
734fn declaration(opcode: u16, control: u32, c: &mut Cursor<'_>) -> Result<Declared> {
735    let pos = c.origin.saturating_sub(1);
736    let slot = |o: &Operand| -> Result<u32> {
737        o.indices
738            .first()
739            .map(|i| i.base)
740            .ok_or_else(|| error(pos, "missing declaration index"))
741    };
742    Ok(match opcode {
743        104 => Declared::Temps(c.take()?),
744        105 => Declared::IndexableTemp {
745            register: c.take()?,
746            size: c.take()?,
747            components: c.take()?,
748        },
749        106 => Declared::GlobalFlags(control),
750        88 => {
751            let o = c.operand(0)?;
752            let token = c.take()?;
753            Declared::Resource {
754                slot: slot(&o)?,
755                dimension: control & 31,
756                samples: (control >> 5) & 127,
757                returns: return_codes(token),
758                token,
759            }
760        }
761        89 => {
762            let o = c.operand(0)?;
763            Declared::ConstantBuffer {
764                slot: slot(&o)?,
765                rows: o.indices.get(1).map_or(0, |i| i.base),
766                dynamic: control & 1 != 0,
767                dimensions: o.indices.len(),
768            }
769        }
770        90 => {
771            let o = c.operand(0)?;
772            Declared::Sampler {
773                slot: slot(&o)?,
774                mode: control & 15,
775            }
776        }
777        95..=100 => {
778            let operand = c.operand(0)?;
779            let pixel = matches!(opcode, 98..=100);
780            let interpolation = pixel.then_some(control & 15);
781            let name = matches!(opcode, 96 | 97 | 99 | 100)
782                .then(|| c.take())
783                .transpose()?;
784            Declared::Input {
785                operand,
786                interpolation,
787                name,
788            }
789        }
790        101..=103 => {
791            let operand = c.operand(0)?;
792            let name = (opcode != 101).then(|| c.take()).transpose()?;
793            Declared::Output { operand, name }
794        }
795        156 => {
796            let o = c.operand(0)?;
797            let token = c.take()?;
798            Declared::UavTyped {
799                slot: slot(&o)?,
800                dimension: control & 31,
801                returns: return_codes(token),
802                token,
803                control,
804            }
805        }
806        157 => {
807            let o = c.operand(0)?;
808            Declared::UavRaw {
809                slot: slot(&o)?,
810                control,
811            }
812        }
813        158 => {
814            let o = c.operand(0)?;
815            Declared::UavStructured {
816                slot: slot(&o)?,
817                stride: c.take()?,
818                control,
819            }
820        }
821        155 => Declared::ThreadGroup([c.take()?, c.take()?, c.take()?]),
822        159 => {
823            let o = c.operand(0)?;
824            Declared::GroupSharedRaw {
825                register: slot(&o)?,
826                bytes: c.take()?,
827            }
828        }
829        160 => {
830            let o = c.operand(0)?;
831            Declared::GroupSharedStructured {
832                register: slot(&o)?,
833                stride: c.take()?,
834                count: c.take()?,
835            }
836        }
837        161 => {
838            let o = c.operand(0)?;
839            Declared::ResourceRaw { slot: slot(&o)? }
840        }
841        162 => {
842            let o = c.operand(0)?;
843            Declared::ResourceStructured {
844                slot: slot(&o)?,
845                stride: c.take()?,
846            }
847        }
848        _ => Declared::Other {
849            opcode,
850            control,
851            words: c.rest(),
852        },
853    })
854}