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::format::{OperandFormat, operand_format};
8use crate::opcodes::NAMES;
9use crate::{Result, error};
10
11/// The low byte of `v`.
12fn low8(v: u32) -> u8 {
13    let [byte, ..] = v.to_le_bytes();
14    byte
15}
16
17/// The low 16 bits of `v`.
18fn low16(v: u32) -> u16 {
19    let [a, b, ..] = v.to_le_bytes();
20    u16::from_le_bytes([a, b])
21}
22
23/// The kind of register an operand names.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Register {
26    /// A temporary register (`r<n>`).
27    Temp,
28    /// An input register (`v<n>`).
29    Input,
30    /// An output register (`o<n>`).
31    Output,
32    /// An indexable temporary (`x<n>[i]`).
33    IndexableTemp,
34    /// A constant buffer register (`cb<slot>[row]`).
35    Constant,
36    /// An immediate operand (`l(...)`), its values in [`Operand::immediate`].
37    Immediate,
38    /// A 64-bit immediate operand (`d(...)`): its values in [`Operand::immediate`] as pairs of words, low word first
39    /// (two words for one value, four for two).
40    Immediate64,
41    /// A subroutine label (`label <n>`), the operand of `label`, `call` and `callc`.
42    Label,
43    /// A function body of a program with class linkage (`fb<n>`).
44    FunctionBody,
45    /// A function table of a program with class linkage (`ft<n>`).
46    FunctionTable,
47    /// An interface pointer of a program with class linkage (`fp<n>[array][call site]`).
48    Interface,
49    /// A function's input register (`fi<n>`).
50    FunctionInput,
51    /// A function's output register (`fo<n>`).
52    FunctionOutput,
53    /// The `this` pointer of a class instance (`this[<n>]`).
54    ThisPointer,
55    /// `vCycleCounter`, the input clock counter.
56    CycleCounter,
57    /// `vInnerCoverage`, the input inner coverage.
58    InnerCoverage,
59    /// A shader resource view (`t<n>`).
60    Texture,
61    /// A sampler (`s<n>`).
62    Sampler,
63    /// The null register (`null`), a destination that discards its result.
64    Null,
65    /// An unordered-access view (`u<n>`).
66    Uav,
67    /// The immediate constant buffer (`icb[n]`), its rows the program's own.
68    ImmediateBuffer,
69    /// `vPrim`, the primitive id input.
70    PrimitiveId,
71    /// `vCoverage`, the input coverage mask.
72    InputCoverage,
73    /// `oDepth`.
74    OutputDepth,
75    /// `oDepthGE`, a depth output that is at least the rasterized depth.
76    OutputDepthGreaterEqual,
77    /// `oDepthLE`, a depth output that is at most the rasterized depth.
78    OutputDepthLessEqual,
79    /// `oMask`, the coverage output.
80    OutputCoverage,
81    /// `rasterizer`: the render target as `sample_info` and `sample_pos` query it (its sample count, a sample's position).
82    Rasterizer,
83    /// `oStencilRef`, the stencil reference output.
84    OutputStencilRef,
85    /// `g<n>`, thread-group shared memory.
86    GroupSharedMemory,
87    /// `vThreadID`, `vThreadGroupID`, `vThreadIDInGroup` and `vThreadIDInGroupFlattened`, a compute program's ids.
88    ThreadId,
89    /// `vThreadGroupID`.
90    ThreadGroupId,
91    /// `vThreadIDInGroup`.
92    ThreadIdInGroup,
93    /// `vThreadIDInGroupFlattened`.
94    ThreadIdInGroupFlattened,
95    /// `vOutputControlPointID`, `vForkInstanceID`, `vJoinInstanceID`: a hull program's phase numbers.
96    OutputControlPointId,
97    /// `vForkInstanceID`, a hull program's fork phase instance.
98    ForkInstanceId,
99    /// `vJoinInstanceID`, a hull program's join phase instance.
100    JoinInstanceId,
101    /// `vicp[point][register]`: an input control point of a hull or domain program.
102    InputControlPoint,
103    /// `vocp[point][register]`: an output control point a hull program's patch-constant phases read.
104    OutputControlPoint,
105    /// `vpc[register]`: a patch constant a domain program reads (or a hull program's join phase).
106    InputPatchConstant,
107    /// `vDomain`: a domain program's location in its patch.
108    InputDomainPoint,
109    /// `m<n>`: a geometry program's output stream.
110    Stream,
111    /// `vGSInstanceID`.
112    GsInstanceId,
113}
114impl Register {
115    /// How many index dimensions an operand of this register has in Shader Model 4 and 5.0.
116    fn dimensions(self) -> u32 {
117        match self {
118            Register::Immediate
119            | Register::Immediate64
120            | Register::CycleCounter
121            | Register::InnerCoverage
122            | Register::Null
123            | Register::PrimitiveId
124            | Register::InputCoverage
125            | Register::OutputDepth
126            | Register::OutputDepthGreaterEqual
127            | Register::OutputDepthLessEqual
128            | Register::OutputCoverage
129            | Register::Rasterizer
130            | Register::OutputStencilRef
131            | Register::ThreadId
132            | Register::ThreadGroupId
133            | Register::ThreadIdInGroup
134            | Register::ThreadIdInGroupFlattened
135            | Register::OutputControlPointId
136            | Register::ForkInstanceId
137            | Register::JoinInstanceId
138            | Register::InputDomainPoint
139            | Register::GsInstanceId => 0,
140            Register::Constant
141            | Register::IndexableTemp
142            | Register::InputControlPoint
143            | Register::OutputControlPoint
144            | Register::Interface => 2,
145            _ => 1,
146        }
147    }
148    fn from_type(n: u32) -> Option<Self> {
149        Some(match n {
150            0 => Register::Temp,
151            1 => Register::Input,
152            2 => Register::Output,
153            3 => Register::IndexableTemp,
154            4 => Register::Immediate,
155            5 => Register::Immediate64,
156            10 => Register::Label,
157            17 => Register::FunctionBody,
158            18 => Register::FunctionTable,
159            19 => Register::Interface,
160            20 => Register::FunctionInput,
161            21 => Register::FunctionOutput,
162            29 => Register::ThisPointer,
163            40 => Register::CycleCounter,
164            42 => Register::InnerCoverage,
165            6 => Register::Sampler,
166            7 => Register::Texture,
167            8 => Register::Constant,
168            9 => Register::ImmediateBuffer,
169            11 => Register::PrimitiveId,
170            12 => Register::OutputDepth,
171            13 => Register::Null,
172            14 => Register::Rasterizer,
173            15 => Register::OutputCoverage,
174            16 => Register::Stream,
175            22 => Register::OutputControlPointId,
176            23 => Register::ForkInstanceId,
177            24 => Register::JoinInstanceId,
178            25 => Register::InputControlPoint,
179            26 => Register::OutputControlPoint,
180            27 => Register::InputPatchConstant,
181            28 => Register::InputDomainPoint,
182            37 => Register::GsInstanceId,
183            30 => Register::Uav,
184            31 => Register::GroupSharedMemory,
185            32 => Register::ThreadId,
186            33 => Register::ThreadGroupId,
187            34 => Register::ThreadIdInGroup,
188            36 => Register::ThreadIdInGroupFlattened,
189            35 => Register::InputCoverage,
190            38 => Register::OutputDepthGreaterEqual,
191            39 => Register::OutputDepthLessEqual,
192            41 => Register::OutputStencilRef,
193            _ => return None,
194        })
195    }
196}
197
198/// The components of a register an operand selects.
199#[derive(Clone, Copy, Debug, PartialEq, Eq)]
200pub enum Selection {
201    /// A write mask: bit 0 is x, bit 3 is w.
202    Mask(u8),
203    /// A four-component swizzle: for each result lane the source lane (0 x to 3 w) it reads.
204    Swizzle([u8; 4]),
205    /// One source lane (0 x to 3 w).
206    Scalar(u8),
207    /// No selection: the operand has no components (a sampler, a resource).
208    None,
209}
210
211/// An instruction's or declaration's operand.
212#[derive(Clone, Debug)]
213pub struct Operand {
214    /// The kind of register it names.
215    pub register: Register,
216    /// Which components it selects.
217    pub selection: Selection,
218    /// The number of components the operand token states (0, 1 or 4): a one-component operand (`oDepth`, `l(1.0)`)
219    /// and a four-component register selecting one lane (`r0.x`) both have a [`Selection::Scalar`].
220    pub components: u8,
221    /// Its index dimensions, outermost first: none for an immediate, one for `r3`, two for `cb2[5]`.
222    pub indices: Vec<Index>,
223    /// The values of an immediate operand (one lane repeated, or four); zero for other registers. A 64-bit immediate's
224    /// four words are two doubles, low word first; one of one component is a single word repeated, as Microsoft's
225    /// disassembler reads it (fxc writes none).
226    pub immediate: [u32; 4],
227    /// 0 none, 1 negate, 2 absolute, 3 negated absolute (float sources); 4 two's-complement negate (an integer
228    /// instruction's negate; the encoding has no such value, so a caller that knows the instruction reads integers sets it).
229    pub modifier: u8,
230    /// The minimum-precision annotation of the operand's extended token (0 none; 1 float16, 2 float2_8, 4 sint16,
231    /// 5 uint16).
232    pub precision: u8,
233    /// The operand's extended token marks it as a non-uniform resource index.
234    pub non_uniform: bool,
235}
236
237/// One index dimension of an operand: a constant, a relative register, or both added.
238#[derive(Clone, Debug)]
239pub struct Index {
240    /// The constant part of the index.
241    pub base: u32,
242    /// The register whose component is added to `base`, where the index is relative.
243    pub relative: Option<Box<Operand>>,
244}
245
246/// An instruction as stored.
247#[derive(Clone, Debug)]
248pub struct Instruction {
249    /// The instruction's first word in the token stream.
250    pub word: usize,
251    /// The opcode token's low 11 bits (see [`opcode_name`]).
252    pub opcode: u16,
253    /// The opcode token's bits 11 to 23: saturate and the opcode-specific controls (the test of a conditional, a
254    /// `resinfo` return type, the interpolation of a declaration).
255    pub control: u32,
256    /// The opcode's extended tokens as stored (sample offsets, resource dimension, return type).
257    pub extensions: Vec<u32>,
258    /// The saturate bit of the opcode token.
259    pub saturate: bool,
260    /// A conditional (`if`, `breakc`, `continuec`) taken when its operand is nonzero (`_nz`), else when zero (`_z`).
261    pub nonzero: bool,
262    /// The operands, destination first where the instruction has one; none for an opcode the format reserves (107,
263    /// 209, 218, 235), whose words after the opcode tokens Microsoft's disassembler skips.
264    pub operands: Vec<Operand>,
265    /// The call site an `interface_call` stores before its operand (the entry of the interface's function tables it
266    /// calls); `None` for other instructions.
267    pub function: Option<u32>,
268}
269impl Instruction {
270    /// The opcode's name (`mov`, `add`, ...), or `unknown opcode`.
271    pub fn name(&self) -> &'static str {
272        opcode_name(self.opcode)
273    }
274    /// The sample offsets of the extended token of kind 1 (`_aoffimmi`), if the instruction has one.
275    pub fn sample_offset(&self) -> Option<[i32; 3]> {
276        self.extension(1).map(|e| {
277            let field = |shift: u32| (((e >> shift) & 15) as i32) << 28 >> 28;
278            [field(9), field(13), field(17)]
279        })
280    }
281    /// The resource dimension and structured stride of the extended token of kind 2.
282    pub fn resource_dimension(&self) -> Option<(u32, u32)> {
283        self.extension(2).map(|e| ((e >> 6) & 31, (e >> 11) & 0xf_ffff))
284    }
285    /// The return-type token of the extended token of kind 3 (four nibbles).
286    pub fn return_type(&self) -> Option<u32> {
287        self.extension(3).map(|e| (e >> 6) & 0xffff)
288    }
289    fn extension(&self, kind: u32) -> Option<u32> {
290        self.extensions.iter().copied().find(|e| e & 63 == kind)
291    }
292    /// The opcode's operand format (see [`operand_format`]); `decode` has checked the operand count against it.
293    pub fn format(&self) -> Option<OperandFormat> {
294        operand_format(self.opcode)
295    }
296    /// The destination operands and the source operands, split as the opcode's format says; `None` for an opcode
297    /// with no format.
298    pub fn destinations_and_sources(&self) -> Option<(&[Operand], &[Operand])> {
299        self.operands.split_at_checked(self.format()?.destinations.len())
300    }
301}
302
303/// The name of an opcode in Microsoft's tokenized program format, or `unknown opcode`.
304pub fn opcode_name(opcode: u16) -> &'static str {
305    NAMES
306        .get(usize::from(opcode))
307        .copied()
308        .unwrap_or("unknown opcode")
309}
310
311/// The type of a program's four return lanes in a resource declaration, as D3D_RETURN_TYPE codes (1 unorm, 2 snorm,
312/// 3 sint, 4 uint, 5 float, 6 mixed, 7 double, 8 continued, 9 unused), one nibble per lane.
313pub type ReturnCodes = [u8; 4];
314
315/// The registers a Shader Model 5.1 declaration binds and their register space (`T1[4:7], space=1`). Shader Model 5.0
316/// declares each register on its own, in space 0; 5.1 names the resource by an id (the declaration's `slot`) and
317/// instructions by the id and a register of the range.
318#[derive(Clone, Copy, Debug, PartialEq, Eq)]
319pub struct RegisterRange {
320    /// The first register.
321    pub lower: u32,
322    /// The last register; `u32::MAX` for an array of no stated size (`T0[0:*]`).
323    pub upper: u32,
324    /// The register space.
325    pub space: u32,
326}
327
328/// A declaration, typed. `word` is where it starts.
329#[derive(Clone, Debug)]
330pub struct Declaration {
331    /// The word of the token stream the declaration starts at.
332    pub word: usize,
333    /// The opcode token's low 11 bits (see [`opcode_name`]).
334    pub opcode: u16,
335    /// The opcode token's bits 11 to 23 as stored.
336    pub control: u32,
337    /// The declaration, typed.
338    pub kind: Declared,
339}
340
341/// A declaration, typed where this reader knows its layout.
342#[derive(Clone, Debug)]
343#[non_exhaustive]
344pub enum Declared {
345    /// `dcl_temps`.
346    Temps(u32),
347    /// `dcl_indexable_temp x<register>[size], components`.
348    IndexableTemp {
349        /// The register index (the `<n>` of `x<n>`).
350        register: u32,
351        /// The number of elements.
352        size: u32,
353        /// The number of components per element.
354        components: u32,
355    },
356    /// `dcl_globalFlags`: the control bits as stored (bit 0 refactoring allowed, 1 double precision, 2 forced early
357    /// depth-stencil, 3 raw and structured buffers, 4 skip optimization, 5 min precision, 6 double extensions, 7
358    /// shader extensions).
359    GlobalFlags(u32),
360    /// `dcl_constantbuffer cb<slot>[rows]`; `dynamic` when it is dynamically indexed.
361    ConstantBuffer {
362        /// The buffer slot (the `<slot>` of `cb<slot>`) in Shader Model 5.0, its id in 5.1 (its registers in `range`).
363        slot: u32,
364        /// The number of 16-byte rows; 0 where the declaration states none.
365        rows: u32,
366        /// The buffer is dynamically indexed.
367        dynamic: bool,
368        /// The number of index dimensions the operand had (1 or 2; 3 in Shader Model 5.1).
369        dimensions: usize,
370        /// The registers and space of a Shader Model 5.1 declaration; `None` before 5.1.
371        range: Option<RegisterRange>,
372    },
373    /// `dcl_sampler s<slot>`, its mode: 0 default, 1 comparison, 2 mono.
374    Sampler {
375        /// The sampler slot in Shader Model 5.0, its id in 5.1 (its registers in `range`).
376        slot: u32,
377        /// 0 default, 1 comparison, 2 mono.
378        mode: u32,
379        /// The registers and space of a Shader Model 5.1 declaration; `None` before 5.1.
380        range: Option<RegisterRange>,
381        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
382        /// Microsoft's listing writes after the register before Shader Model 5.1.
383        operand: Operand,
384    },
385    /// `dcl_resource_<dimension> t<slot>`: the D3D resource dimension code, the sample count of a multisampled one,
386    /// and the four return types.
387    Resource {
388        /// The resource slot in Shader Model 5.0, its id in 5.1 (its registers in `range`).
389        slot: u32,
390        /// The D3D resource dimension code.
391        dimension: u32,
392        /// The sample count of a multisampled resource.
393        samples: u32,
394        /// The return type of each lane.
395        returns: ReturnCodes,
396        /// The return type token as stored.
397        token: u32,
398        /// The registers and space of a Shader Model 5.1 declaration; `None` before 5.1.
399        range: Option<RegisterRange>,
400        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
401        /// Microsoft's listing writes after the register before Shader Model 5.1.
402        operand: Operand,
403    },
404    /// `dcl_resource_raw t<slot>`.
405    ResourceRaw {
406        /// The resource slot in Shader Model 5.0, its id in 5.1 (its registers in `range`).
407        slot: u32,
408        /// The registers and space of a Shader Model 5.1 declaration; `None` before 5.1.
409        range: Option<RegisterRange>,
410        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
411        /// Microsoft's listing writes after the register before Shader Model 5.1.
412        operand: Operand,
413    },
414    /// `dcl_resource_structured t<slot>, stride`.
415    ResourceStructured {
416        /// The resource slot in Shader Model 5.0, its id in 5.1 (its registers in `range`).
417        slot: u32,
418        /// The element stride in bytes.
419        stride: u32,
420        /// The registers and space of a Shader Model 5.1 declaration; `None` before 5.1.
421        range: Option<RegisterRange>,
422        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
423        /// Microsoft's listing writes after the register before Shader Model 5.1.
424        operand: Operand,
425    },
426    /// `dcl_uav_typed` and its control bits (dimension in bits 0 to 4, globally coherent bit 5).
427    UavTyped {
428        /// The UAV slot in Shader Model 5.0, its id in 5.1 (its registers in `range`).
429        slot: u32,
430        /// The D3D resource dimension code.
431        dimension: u32,
432        /// The return type of each lane.
433        returns: ReturnCodes,
434        /// The return type token as stored.
435        token: u32,
436        /// The opcode token's control bits.
437        control: u32,
438        /// The registers and space of a Shader Model 5.1 declaration; `None` before 5.1.
439        range: Option<RegisterRange>,
440        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
441        /// Microsoft's listing writes after the register before Shader Model 5.1.
442        operand: Operand,
443    },
444    /// `dcl_uav_raw u<slot>`.
445    UavRaw {
446        /// The UAV slot in Shader Model 5.0, its id in 5.1 (its registers in `range`).
447        slot: u32,
448        /// The opcode token's control bits.
449        control: u32,
450        /// The registers and space of a Shader Model 5.1 declaration; `None` before 5.1.
451        range: Option<RegisterRange>,
452        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
453        /// Microsoft's listing writes after the register before Shader Model 5.1.
454        operand: Operand,
455    },
456    /// `dcl_uav_structured u<slot>, stride`.
457    UavStructured {
458        /// The UAV slot in Shader Model 5.0, its id in 5.1 (its registers in `range`).
459        slot: u32,
460        /// The element stride in bytes.
461        stride: u32,
462        /// The opcode token's control bits.
463        control: u32,
464        /// The registers and space of a Shader Model 5.1 declaration; `None` before 5.1.
465        range: Option<RegisterRange>,
466        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
467        /// Microsoft's listing writes after the register before Shader Model 5.1.
468        operand: Operand,
469    },
470    /// `dcl_thread_group x, y, z`.
471    ThreadGroup([u32; 3]),
472    /// `dcl_tgsm_raw g<register>, bytes`.
473    GroupSharedRaw {
474        /// The shared memory register index.
475        register: u32,
476        /// Its size in bytes.
477        bytes: u32,
478        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
479        /// Microsoft's listing writes after the register.
480        operand: Operand,
481    },
482    /// `dcl_tgsm_structured g<register>, stride, count`.
483    GroupSharedStructured {
484        /// The shared memory register index.
485        register: u32,
486        /// The element stride in bytes.
487        stride: u32,
488        /// The number of elements.
489        count: u32,
490        /// The operand as stored: the register it names, and a component selection (fxc writes none), which
491        /// Microsoft's listing writes after the register.
492        operand: Operand,
493    },
494    /// An input register or special input: `dcl_input`, `_sgv`, `_siv`, `_ps`, `_ps_sgv`, `_ps_siv`. `interpolation` is
495    /// a pixel input's `D3D_INTERPOLATION_MODE`; `name` a system value's `D3D_NAME`.
496    Input {
497        /// The input register or special input the declaration names.
498        operand: Operand,
499        /// The interpolation mode of a pixel input.
500        interpolation: Option<u32>,
501        /// The system value (`D3D_NAME`) of a `_sgv` or `_siv` declaration: the low 16 bits of its token, the rest
502        /// being reserved.
503        name: Option<u32>,
504    },
505    /// An output register or special output: `dcl_output`, `_sgv`, `_siv`.
506    Output {
507        /// The output register or special output the declaration names.
508        operand: Operand,
509        /// The system value (`D3D_NAME`) of a `_siv` or `_sgv` declaration: the low 16 bits of its token, the rest
510        /// being reserved.
511        name: Option<u32>,
512    },
513    /// `dcl_indexrange`: registers from the operand's that a program indexes as one array, `count` of them.
514    IndexRange {
515        /// The first register of the range.
516        operand: Operand,
517        /// The number of registers.
518        count: u32,
519    },
520    /// `dcl_stream m<n>`: a geometry program's output stream that the declarations after it describe.
521    Stream(Operand),
522    /// `dcl_immediateConstantBuffer`.
523    ImmediateBuffer(Vec<[u32; 4]>),
524    /// A message the debug layer reports when the program runs (custom data of class 4,
525    /// `D3D11_SB_CUSTOMDATA_SHADER_MESSAGE`), which fxc writes for `printf` and `errorf`: the message and the operands
526    /// its format string formats.
527    ShaderMessage {
528        /// `D3D11_SB_SHADER_MESSAGE_ID`, the debug layer's message id: 0x200102 for `printf`, 0x200103 for `errorf`.
529        id: u32,
530        /// `D3D11_SB_SHADER_MESSAGE_FORMAT`: 0 text, 1 a `printf` format string.
531        format: u32,
532        /// The number of characters the message states, without the terminating zero.
533        characters: u32,
534        /// The message's characters as Microsoft's listing reads them: from the string's start to its first zero
535        /// byte, whatever `characters` says.
536        text: Vec<u8>,
537        /// The operands the format string formats, in order.
538        operands: Vec<Operand>,
539    },
540    /// Custom data that is not an immediate constant buffer or a shader message (a comment, debug information, ...) by
541    /// its class; of class 4, a shader message whose character count leaves no room in it for its string's terminating
542    /// zero, which Microsoft's listing calls undecipherable.
543    CustomData {
544        /// The custom data class.
545        class: u32,
546    },
547    /// `dcl_function_body fb<id>`: a function body of a program with class linkage.
548    FunctionBody(u32),
549    /// `dcl_function_table ft<id> = {fb..}`: the function bodies a class's table holds, one per call site.
550    FunctionTable {
551        /// The table's id.
552        id: u32,
553        /// The function bodies, by call site.
554        bodies: Vec<u32>,
555    },
556    /// `dcl_interface fp<id>[array][sites] = {ft..}`: an interface pointer, or an array of them.
557    Interface {
558        /// The interface pointer's id, its first where it is an array.
559        id: u32,
560        /// The pointer is indexed by a register (`dcl_interface_dynamicindexed`).
561        dynamic: bool,
562        /// The number of call sites, the length of each of its tables.
563        sites: u32,
564        /// The array length, 1 for one pointer.
565        length: u32,
566        /// The function tables of the classes that can be bound to it.
567        tables: Vec<u32>,
568    },
569    /// A declaration this decoder does not type: stage-specific (geometry, tessellation, compute).
570    Other {
571        /// The opcode.
572        opcode: u16,
573        /// The opcode token's control bits.
574        control: u32,
575        /// The declaration's words after its operands, as stored.
576        words: Vec<u32>,
577    },
578}
579
580impl Declared {
581    /// The operand of a declaration that names a register: a resource, sampler, view, shared memory, input, output,
582    /// index range or stream; `None` for the others (a constant buffer's is read into its fields).
583    pub fn operand(&self) -> Option<&Operand> {
584        match self {
585            Declared::Sampler { operand, .. }
586            | Declared::Resource { operand, .. }
587            | Declared::ResourceRaw { operand, .. }
588            | Declared::ResourceStructured { operand, .. }
589            | Declared::UavTyped { operand, .. }
590            | Declared::UavRaw { operand, .. }
591            | Declared::UavStructured { operand, .. }
592            | Declared::GroupSharedRaw { operand, .. }
593            | Declared::GroupSharedStructured { operand, .. }
594            | Declared::Input { operand, .. }
595            | Declared::Output { operand, .. }
596            | Declared::IndexRange { operand, .. }
597            | Declared::Stream(operand) => Some(operand),
598            _ => None,
599        }
600    }
601
602    /// The registers and space a resource, sampler, view or constant buffer declaration binds: its `range` in Shader
603    /// Model 5.1, its one register in space 0 before; `None` for the other declarations.
604    pub fn registers(&self) -> Option<RegisterRange> {
605        let (slot, range) = match self {
606            Declared::ConstantBuffer { slot, range, .. }
607            | Declared::Sampler { slot, range, .. }
608            | Declared::Resource { slot, range, .. }
609            | Declared::ResourceRaw { slot, range, .. }
610            | Declared::ResourceStructured { slot, range, .. }
611            | Declared::UavTyped { slot, range, .. }
612            | Declared::UavRaw { slot, range, .. }
613            | Declared::UavStructured { slot, range, .. } => (*slot, *range),
614            _ => return None,
615        };
616        Some(range.unwrap_or(RegisterRange {
617            lower: slot,
618            upper: slot,
619            space: 0,
620        }))
621    }
622}
623
624/// The kind of program a version token names.
625#[derive(Clone, Copy, Debug, PartialEq, Eq)]
626pub enum ProgramType {
627    /// A pixel program (`ps`).
628    Pixel,
629    /// A vertex program (`vs`).
630    Vertex,
631    /// A geometry program (`gs`).
632    Geometry,
633    /// A hull program (`hs`).
634    Hull,
635    /// A domain program (`ds`).
636    Domain,
637    /// A compute program (`cs`).
638    Compute,
639}
640impl ProgramType {
641    /// The target's stage prefix (`ps`, `vs`, ...).
642    pub fn name(self) -> &'static str {
643        match self {
644            ProgramType::Pixel => "ps",
645            ProgramType::Vertex => "vs",
646            ProgramType::Geometry => "gs",
647            ProgramType::Hull => "hs",
648            ProgramType::Domain => "ds",
649            ProgramType::Compute => "cs",
650        }
651    }
652}
653
654/// A decoded token stream: its target, declarations and instructions, each in program order.
655#[derive(Clone, Debug)]
656pub struct Decoded {
657    /// The program's stage.
658    pub kind: ProgramType,
659    /// The shader model's major version.
660    pub major: u32,
661    /// The shader model's minor version.
662    pub minor: u32,
663    /// The declarations, in program order.
664    pub declarations: Vec<Declaration>,
665    /// The instructions, in program order.
666    pub instructions: Vec<Instruction>,
667}
668impl Decoded {
669    /// The target's name (`ps_5_0`).
670    pub fn target(&self) -> String {
671        format!("{}_{}_{}", self.kind.name(), self.major, self.minor)
672    }
673}
674
675#[derive(Clone)]
676struct Cursor<'a> {
677    words: &'a [u32],
678    at: usize,
679    origin: usize,
680    /// The program's type: the input registers of a geometry, hull or domain program are indexed by vertex.
681    kind: ProgramType,
682    /// The program is Shader Model 5.1, whose resource, sampler, view and constant buffer operands carry the
683    /// resource's id as their first index.
684    sm51: bool,
685    /// The words are a declaration's, whose 5.1 binding operands give the id and the first and last register.
686    declaration: bool,
687}
688impl Cursor<'_> {
689    /// The word the cursor is at, in the token stream.
690    fn position(&self) -> usize {
691        self.origin.saturating_add(self.at)
692    }
693    fn take(&mut self) -> Result<u32> {
694        let value = *self
695            .words
696            .get(self.at)
697            .ok_or_else(|| error(self.position(), "truncated instruction"))?;
698        self.at = self.at.saturating_add(1);
699        Ok(value)
700    }
701    fn rest(&mut self) -> Vec<u32> {
702        let rest = self.words.get(self.at..).unwrap_or_default().to_vec();
703        self.at = self.words.len();
704        rest
705    }
706    fn operand(&mut self, depth: usize) -> Result<Operand> {
707        let pos = self.position();
708        if depth > 2 {
709            return Err(error(pos, "nested relative operand exceeds bound"));
710        }
711        let t = self.take()?;
712        let components = t & 3;
713        let selection = match components {
714            0 => Selection::None,
715            1 => Selection::Scalar(0),
716            2 => match (t >> 2) & 3 {
717                0 => Selection::Mask(low8((t >> 4) & 15)),
718                1 => Selection::Swizzle([4, 6, 8, 10].map(|shift| low8((t >> shift) & 3))),
719                2 => Selection::Scalar(low8((t >> 4) & 3)),
720                _ => return Err(error(pos, "reserved component selection")),
721            },
722            _ => return Err(error(pos, "unsupported N-component operand")),
723        };
724        let kind = (t >> 12) & 255;
725        let register = Register::from_type(kind)
726            .ok_or_else(|| error(pos, format!("unsupported operand register type {kind}")))?;
727        let (mut modifier, mut precision, mut non_uniform) = (0, 0, false);
728        if t >> 31 != 0 {
729            let mut ext = self.take()?;
730            loop {
731                // only the modifier extension (kind 1) exists in Shader Model 4 and 5
732                if ext & 63 != 1 {
733                    return Err(error(pos, "unsupported extended operand (precision/modifier)"));
734                }
735                modifier = low8((ext >> 6) & 255);
736                precision = low8((ext >> 14) & 7);
737                non_uniform = ext & (1 << 17) != 0;
738                if ext >> 31 == 0 {
739                    break;
740                }
741                ext = self.take()?;
742            }
743            if modifier > 3 {
744                return Err(error(pos, "unsupported extended operand (precision/modifier)"));
745            }
746        }
747        let dimensions = (t >> 20) & 3;
748        let expected = match (register, self.kind) {
749            (Register::Input, ProgramType::Geometry | ProgramType::Hull | ProgramType::Domain) => 2,
750            // Shader Model 5.1: `CB1[id][register][row]`, `T0[id][register]`; declared `CB1[id][lower:upper]`
751            (Register::Constant, _) if self.sm51 => 3,
752            (Register::Texture | Register::Sampler | Register::Uav, _) if self.sm51 => {
753                if self.declaration && depth == 0 { 3 } else { 2 }
754            }
755            _ => register.dimensions(),
756        };
757        if dimensions != expected {
758            return Err(error(pos, "unexpected operand index dimensions"));
759        }
760        let mut indices = Vec::new();
761        // the representation of each index dimension, three bits each from bit 22
762        for shift in [22, 25, 28].into_iter().take(dimensions as usize) {
763            let representation = (t >> shift) & 7;
764            if !matches!(representation, 0 | 2 | 3) {
765                return Err(error(pos, "unsupported 64-bit/reserved index"));
766            }
767            let base = if representation != 2 { self.take()? } else { 0 };
768            let relative = if representation != 0 {
769                Some(Box::new(self.operand(depth.saturating_add(1))?))
770            } else {
771                None
772            };
773            indices.push(Index { base, relative });
774        }
775        let mut immediate = [0; 4];
776        match (register, components) {
777            // one word even for a 64-bit immediate, as Microsoft's disassembler reads it
778            (Register::Immediate | Register::Immediate64, 1) => immediate = [self.take()?; 4],
779            // four 32-bit values, or two 64-bit values of two words each
780            (Register::Immediate | Register::Immediate64, 2) => {
781                for x in &mut immediate {
782                    *x = self.take()?;
783                }
784            }
785            (Register::Immediate | Register::Immediate64, _) => {
786                return Err(error(pos, "immediate requires one or four components"));
787            }
788            _ => {}
789        }
790        Ok(Operand {
791            register,
792            selection,
793            components: match components {
794                0 => 0,
795                1 => 1,
796                _ => 4,
797            },
798            indices,
799            immediate,
800            modifier,
801            precision,
802            non_uniform,
803        })
804    }
805}
806
807/// `interface_call`, whose first word after its token is a call site, not an operand.
808const INTERFACE_CALL: u16 = 120;
809const CALL: u16 = 4;
810const CALLC: u16 = 5;
811const LABEL: u16 = 44;
812/// The opcodes the format reserves (`reserved0`): Microsoft's disassembler reads no operand of them and skips the words
813/// after their opcode tokens.
814const RESERVED: [u16; 4] = [107, 209, 218, 235];
815
816/// `D3D10_SB_NAME_MASK`: the bits of a declaration's name token that hold its `D3D_NAME`.
817const NAME: u32 = 0xffff;
818
819fn return_codes(token: u32) -> ReturnCodes {
820    [0, 4, 8, 12].map(|shift| low8((token >> shift) & 15))
821}
822
823/// The opcodes that are declarations (and the phase markers of hull programs, which carry none): 88-106 and
824/// 143-162 of Direct3D 10 and 11, the hull phases 113-116 and `dcl_gs_instance_count` (206).
825fn is_declaration(opcode: u16) -> bool {
826    matches!(opcode, 88..=106 | 113..=116 | 143..=162 | 206)
827}
828
829/// A shader message's custom data after its two header words, `origin` the word of the stream it starts at: the
830/// message id, the format, the number of characters, the number of operands and of their words, the operands, then the
831/// characters (a terminating zero and padding after them).
832fn shader_message(body: &[u32], origin: usize, kind: ProgramType, sm51: bool) -> Result<Declared> {
833    let [id, format, characters, count, operand_words, ..] = *body else {
834        return Err(error(
835            origin,
836            format!(
837                "a shader message of {} words, shorter than its 5-word header",
838                body.len()
839            ),
840        ));
841    };
842    let start = 5usize;
843    let end = usize::try_from(operand_words)
844        .ok()
845        .and_then(|n| start.checked_add(n));
846    let Some(words) = end.and_then(|end| body.get(start..end)) else {
847        return Err(error(
848            origin.saturating_add(4),
849            format!(
850                "a shader message's {operand_words} words of operands do not fit its {} words",
851                body.len()
852            ),
853        ));
854    };
855    // Microsoft deciphers a message whose count leaves room for the string's zero, before it reads the operands
856    let string = body.get(start.saturating_add(words.len())..).unwrap_or_default();
857    if usize::try_from(characters).map_or(true, |n| n >= string.len().saturating_mul(4)) {
858        return Ok(Declared::CustomData { class: 4 });
859    }
860    let mut c = Cursor {
861        words,
862        at: 0,
863        origin: origin.saturating_add(start),
864        kind,
865        sm51,
866        declaration: false,
867    };
868    let mut operands = Vec::new();
869    while c.at < c.words.len() {
870        operands.push(c.operand(0)?);
871    }
872    if operands.len() != usize::try_from(count).unwrap_or(usize::MAX) {
873        return Err(error(
874            origin.saturating_add(3),
875            format!(
876                "a shader message of {count} operands whose {operand_words} words hold {}",
877                operands.len()
878            ),
879        ));
880    }
881    // Microsoft reads the string to its zero, not by its count; past the custom data, what it reads is not the program's
882    let mut text: Vec<u8> = Vec::new();
883    for byte in string.iter().flat_map(|w| w.to_le_bytes()) {
884        if byte == 0 {
885            return Ok(Declared::ShaderMessage {
886                id,
887                format,
888                characters,
889                text,
890                operands,
891            });
892        }
893        text.push(byte);
894    }
895    Err(error(
896        origin.saturating_add(start).saturating_add(words.len()),
897        "a shader message's string has no terminating zero in its custom data",
898    ))
899}
900
901/// Decode a complete token stream: version token, length, then declarations and instructions to its end.
902pub fn decode(words: &[u32]) -> Result<Decoded> {
903    let (Some(&version), Some(&length)) = (words.first(), words.get(1)) else {
904        return Err(error(0, "token stream shorter than its header"));
905    };
906    if usize::try_from(length).ok() != Some(words.len()) {
907        return Err(error(
908            1,
909            format!(
910                "the declared length is {length} words, the stream has {}",
911                words.len()
912            ),
913        ));
914    }
915    let kind = match version >> 16 {
916        0 => ProgramType::Pixel,
917        1 => ProgramType::Vertex,
918        2 => ProgramType::Geometry,
919        3 => ProgramType::Hull,
920        4 => ProgramType::Domain,
921        5 => ProgramType::Compute,
922        other => {
923            return Err(error(
924                0,
925                format!("program type {other} is not a Shader Model 4 or 5 stage"),
926            ));
927        }
928    };
929    let mut result = Decoded {
930        kind,
931        major: (version >> 4) & 15,
932        minor: version & 15,
933        declarations: Vec::new(),
934        instructions: Vec::new(),
935    };
936    let sm51 = (result.major, result.minor) >= (5, 1);
937    let mut at = 2;
938    while let Some(&t) = words.get(at) {
939        let opcode = low16(t & 0x7ff);
940        // customdata: its class in the opcode token's upper bits, its length (these two words included) the next word
941        if opcode == 53 {
942            let len = words
943                .get(at.saturating_add(1))
944                .and_then(|&n| usize::try_from(n).ok())
945                .unwrap_or(0);
946            let end = at.checked_add(len).filter(|&end| len >= 2 && end <= words.len());
947            let (Some(end), Some(body)) = (end, words.get(at.saturating_add(2)..at.saturating_add(len)))
948            else {
949                return Err(error(
950                    at,
951                    format!(
952                        "customdata of {len} words (at least 2) does not fit the stream's {} words",
953                        words.len()
954                    ),
955                ));
956            };
957            let class = t >> 11;
958            let kind = if class == 3 {
959                let (rows, rest) = body.as_chunks::<4>();
960                if !rest.is_empty() {
961                    return Err(error(
962                        at,
963                        format!(
964                            "an immediate constant buffer of {} words, expected whole rows of 4",
965                            body.len()
966                        ),
967                    ));
968                }
969                Declared::ImmediateBuffer(rows.to_vec())
970            } else if class == 4 {
971                shader_message(body, at.saturating_add(2), kind, sm51)?
972            } else {
973                Declared::CustomData { class }
974            };
975            result.declarations.push(Declaration {
976                word: at,
977                opcode,
978                control: class,
979                kind,
980            });
981            at = end;
982            continue;
983        }
984        let len = usize::try_from((t >> 24) & 127).unwrap_or(0);
985        if len == 0 {
986            return Err(error(at, "instruction length 0 (at least 1)"));
987        }
988        let end = at.checked_add(len).filter(|&end| end <= words.len());
989        let (Some(end), Some(body)) = (end, words.get(at.saturating_add(1)..at.saturating_add(len))) else {
990            return Err(error(
991                at,
992                format!(
993                    "instruction length {len} (at least 1) does not fit the stream's {} words",
994                    words.len()
995                ),
996            ));
997        };
998        let mut c = Cursor {
999            words: body,
1000            at: 0,
1001            origin: at.saturating_add(1),
1002            kind,
1003            sm51,
1004            declaration: is_declaration(opcode),
1005        };
1006        let mut extensions = Vec::new();
1007        let mut extended = t >> 31 != 0;
1008        while extended {
1009            let ext = c.take()?;
1010            extensions.push(ext);
1011            extended = ext >> 31 != 0;
1012        }
1013        let control = (t >> 11) & 0x1fff;
1014        if is_declaration(opcode) {
1015            let kind = declaration(opcode, control, &mut c)?;
1016            if c.at != c.words.len() {
1017                return Err(error(
1018                    at,
1019                    format!(
1020                        "{} words after the operands of {}",
1021                        c.words.len().saturating_sub(c.at),
1022                        opcode_name(opcode)
1023                    ),
1024                ));
1025            }
1026            result.declarations.push(Declaration {
1027                word: at,
1028                opcode,
1029                control,
1030                kind,
1031            });
1032        } else {
1033            let function = (opcode == INTERFACE_CALL).then(|| c.take()).transpose()?;
1034            let format = operand_format(opcode);
1035            let mut operands = Vec::new();
1036            let reads = !RESERVED.contains(&opcode);
1037            while reads && c.at < c.words.len() && format.is_none_or(|f| operands.len() < f.operands()) {
1038                operands.push(c.operand(0)?);
1039            }
1040            // fxc leaves words after the operands of some instructions, which Microsoft's disassembler skips: zeros after
1041            // `samplepos` and `sample_cl_s`, 0x2000 after `sample_d_cl_s`. Words that read as whole further operands are
1042            // instead an instruction with another operand count than its format's.
1043            if let Some(format) = format
1044                && c.words.get(c.at..).unwrap_or_default().iter().any(|&w| w != 0)
1045            {
1046                let mut extra = c.clone();
1047                let mut count = operands.len();
1048                while extra.at < extra.words.len() && extra.operand(0).is_ok() {
1049                    count = count.saturating_add(1);
1050                }
1051                if count > operands.len() && extra.at == extra.words.len() {
1052                    return Err(error(
1053                        at,
1054                        format!(
1055                            "{} has {count} operands, its format is {} destinations and {} sources",
1056                            opcode_name(opcode),
1057                            format.destinations.len(),
1058                            format.sources.len()
1059                        ),
1060                    ));
1061                }
1062            }
1063            // `call`, `callc` and `label` have no operand format, as their operands are a label and a condition rather
1064            // than registers a program declares, but they have a count: with fewer operands, Microsoft's disassembler
1065            // reads the words after the instruction as the missing ones.
1066            let taken = match opcode {
1067                CALL | LABEL => Some(1),
1068                CALLC => Some(2),
1069                _ => None,
1070            };
1071            if let Some(taken) = taken
1072                && operands.len() < taken
1073            {
1074                return Err(error(
1075                    at,
1076                    format!(
1077                        "{} has {} operands where it takes {taken}",
1078                        opcode_name(opcode),
1079                        operands.len()
1080                    ),
1081                ));
1082            }
1083            if let Some(format) = format
1084                && operands.len() != format.operands()
1085            {
1086                return Err(error(
1087                    at,
1088                    format!(
1089                        "{} has {} operands, its format is {} destinations and {} sources",
1090                        opcode_name(opcode),
1091                        operands.len(),
1092                        format.destinations.len(),
1093                        format.sources.len()
1094                    ),
1095                ));
1096            }
1097            result.instructions.push(Instruction {
1098                word: at,
1099                opcode,
1100                control,
1101                extensions,
1102                saturate: t & 0x2000 != 0,
1103                nonzero: t & 0x4_0000 != 0,
1104                operands,
1105                function,
1106            });
1107        }
1108        at = end;
1109    }
1110    Ok(result)
1111}
1112
1113fn declaration(opcode: u16, control: u32, c: &mut Cursor<'_>) -> Result<Declared> {
1114    let pos = c.origin.saturating_sub(1);
1115    let slot = |o: &Operand| -> Result<u32> {
1116        o.indices
1117            .first()
1118            .map(|i| i.base)
1119            .ok_or_else(|| error(pos, "missing declaration index"))
1120    };
1121    // A Shader Model 5.1 binding declaration's operand is `[id][lower][upper]`, and its last word the register space.
1122    let range = |o: &Operand, c: &mut Cursor<'_>| -> Result<Option<RegisterRange>> {
1123        if !c.sm51 {
1124            return Ok(None);
1125        }
1126        let [_, lower, upper] = o.indices.as_slice() else {
1127            return Err(error(
1128                pos,
1129                "a Shader Model 5.1 binding declaration without its three indices",
1130            ));
1131        };
1132        Ok(Some(RegisterRange {
1133            lower: lower.base,
1134            upper: upper.base,
1135            space: c.take()?,
1136        }))
1137    };
1138    Ok(match opcode {
1139        104 => Declared::Temps(c.take()?),
1140        105 => Declared::IndexableTemp {
1141            register: c.take()?,
1142            size: c.take()?,
1143            components: c.take()?,
1144        },
1145        106 => Declared::GlobalFlags(control),
1146        88 => {
1147            let o = c.operand(0)?;
1148            let token = c.take()?;
1149            Declared::Resource {
1150                slot: slot(&o)?,
1151                dimension: control & 31,
1152                samples: (control >> 5) & 127,
1153                returns: return_codes(token),
1154                token,
1155                range: range(&o, c)?,
1156                operand: o,
1157            }
1158        }
1159        89 => {
1160            let o = c.operand(0)?;
1161            // 5.1 states the rows in a word of their own; before, they are the operand's second index
1162            let rows = if c.sm51 {
1163                c.take()?
1164            } else {
1165                o.indices.get(1).map_or(0, |i| i.base)
1166            };
1167            Declared::ConstantBuffer {
1168                slot: slot(&o)?,
1169                rows,
1170                dynamic: control & 1 != 0,
1171                dimensions: o.indices.len(),
1172                range: range(&o, c)?,
1173            }
1174        }
1175        90 => {
1176            let o = c.operand(0)?;
1177            Declared::Sampler {
1178                slot: slot(&o)?,
1179                mode: control & 15,
1180                range: range(&o, c)?,
1181                operand: o,
1182            }
1183        }
1184        95..=100 => {
1185            let operand = c.operand(0)?;
1186            let pixel = matches!(opcode, 98..=100);
1187            let interpolation = pixel.then_some(control & 15);
1188            let name = matches!(opcode, 96 | 97 | 99 | 100)
1189                .then(|| c.take().map(|t| t & NAME))
1190                .transpose()?;
1191            Declared::Input {
1192                operand,
1193                interpolation,
1194                name,
1195            }
1196        }
1197        101..=103 => {
1198            let operand = c.operand(0)?;
1199            let name = (opcode != 101).then(|| c.take().map(|t| t & NAME)).transpose()?;
1200            Declared::Output { operand, name }
1201        }
1202        156 => {
1203            let o = c.operand(0)?;
1204            let token = c.take()?;
1205            Declared::UavTyped {
1206                slot: slot(&o)?,
1207                dimension: control & 31,
1208                returns: return_codes(token),
1209                token,
1210                control,
1211                range: range(&o, c)?,
1212                operand: o,
1213            }
1214        }
1215        157 => {
1216            let o = c.operand(0)?;
1217            Declared::UavRaw {
1218                slot: slot(&o)?,
1219                control,
1220                range: range(&o, c)?,
1221                operand: o,
1222            }
1223        }
1224        158 => {
1225            let o = c.operand(0)?;
1226            Declared::UavStructured {
1227                slot: slot(&o)?,
1228                stride: c.take()?,
1229                control,
1230                range: range(&o, c)?,
1231                operand: o,
1232            }
1233        }
1234        155 => Declared::ThreadGroup([c.take()?, c.take()?, c.take()?]),
1235        91 => Declared::IndexRange {
1236            operand: c.operand(0)?,
1237            count: c.take()?,
1238        },
1239        143 => Declared::Stream(c.operand(0)?),
1240        159 => {
1241            let o = c.operand(0)?;
1242            Declared::GroupSharedRaw {
1243                register: slot(&o)?,
1244                bytes: c.take()?,
1245                operand: o,
1246            }
1247        }
1248        160 => {
1249            let o = c.operand(0)?;
1250            Declared::GroupSharedStructured {
1251                register: slot(&o)?,
1252                stride: c.take()?,
1253                count: c.take()?,
1254                operand: o,
1255            }
1256        }
1257        161 => {
1258            let o = c.operand(0)?;
1259            Declared::ResourceRaw {
1260                slot: slot(&o)?,
1261                range: range(&o, c)?,
1262                operand: o,
1263            }
1264        }
1265        162 => {
1266            let o = c.operand(0)?;
1267            Declared::ResourceStructured {
1268                slot: slot(&o)?,
1269                stride: c.take()?,
1270                range: range(&o, c)?,
1271                operand: o,
1272            }
1273        }
1274        144 => Declared::FunctionBody(c.take()?),
1275        145 => {
1276            let id = c.take()?;
1277            let count = c.take()?;
1278            let bodies = c.rest();
1279            if bodies.len() != count as usize {
1280                return Err(error(
1281                    pos.saturating_add(2),
1282                    format!(
1283                        "dcl_function_table word 2 counts {count} function bodies, the declaration holds {}",
1284                        bodies.len()
1285                    ),
1286                ));
1287            }
1288            Declared::FunctionTable { id, bodies }
1289        }
1290        146 => {
1291            let id = c.take()?;
1292            let sites = c.take()?;
1293            let lengths = c.take()?;
1294            let tables = c.rest();
1295            // the array length in the high half, the table count in the low
1296            let count = lengths & 0xffff;
1297            if tables.len() != count as usize {
1298                return Err(error(
1299                    pos.saturating_add(3),
1300                    format!(
1301                        "dcl_interface word 3 counts {count} function tables in its low 16 bits, the declaration \
1302                         holds {}",
1303                        tables.len()
1304                    ),
1305                ));
1306            }
1307            Declared::Interface {
1308                id,
1309                dynamic: control & 1 != 0,
1310                sites,
1311                length: lengths >> 16,
1312                tables,
1313            }
1314        }
1315        // the stage declarations that hold a value (`dcl_maxout`, `dcl_hs_max_tessfactor`, the hull phases' instance
1316        // counts, `dcl_gsinstances`): without its word, Microsoft's disassembler writes the word after the declaration
1317        94 | 152 | 153 | 154 | 206 if c.at == c.words.len() => {
1318            return Err(error(
1319                pos,
1320                format!("{} has no word for its value", opcode_name(opcode)),
1321            ));
1322        }
1323        _ => Declared::Other {
1324            opcode,
1325            control,
1326            words: c.rest(),
1327        },
1328    })
1329}