D202 - A candidate operand field may not overlap anything the encoding table already reads

Status: decided (2026-08-21)

D109 discards a candidate operand field that overlaps the family's opcode bits. The rule is right and it was stated too narrowly: the opcode is not the only part of the first word already spoken for. The family's own mask is fixed by definition - those bits are what identify the family, they are constant across every instruction in it, and no operand can vary there either.

So the exclusion is a mask of everything the table already reads, and D109 becomes the case of it that happened to be found first.

Keyed by word, not one mask. This entry first said "a single bitmask, mask | opcode", which was true for about an hour - until D105's split opcode landed a fourth opcode bit in the second word, where a first-word mask cannot reach it. The exclusion is {word: bits}, and the general statement in this entry's title is the one to hold: not "the first word's fixed bits", but anything the encoding table already reads.

What the missing half cost

v_mov_b32_e32 and v_rcp_f32_e32 had no operand row at all, which is to say a move decoded with an empty operand list.

VOP1 on this target is [31:25] family, [24:17] destination, [16:9] opcode, [8:0] source. The destination is an eight-bit vector register at bit 17. A nine-bit window at the same position reaches bit 25 - the low bit of VOP1's 0xFE000000 mask, permanently 1 - so it reads v0 as 256, and 256 is exactly v0's code in the shared source numbering.

v_mov_b32_e32 v0,   v1     ->  0x7e000301
v_mov_b32_e32 v255, s101   ->  0x7ffe0265
v_mov_b32_e32 v100, -1     ->  0x7ec802c1
                                    ^ dst reads as vgpr 0/255/100, or source 256/511/356

Both readings explain every sample and neither can ever be eliminated, because the bit that separates them is a constant. The solver did the right thing and refused. It was right in a way nobody could see.

The same shape as D109's own example, which is the part worth noticing. D109 was found because v_cmp_lt_f32_e32 was unsolvable while its sibling v_cmp_eq_f32_e32 solved - a difference in one opcode bit. Here there is no sibling to compare against, because every VOP1 instruction has the same family bits. A fault with no asymmetry to expose it.

Verified the same way D109 was

Re-solving moved nothing: zero rows removed, fourteen added, purely additive. 68 of 71 probed opcodes solved before, 70 after.

The reason it went unnoticed for so long

every_decoded_operand_appears_in_the_reference iterates the operands the decoder produced and looks each one up in the reference disassembly. Decode none and the loop body never executes, so it passes - and the differential suite's whole point is catching exactly this class of error.

A one-directional check cannot see an empty answer. So there is now a converse: an_instruction_that_decodes_no_operands_is_a_listed_gap asserts the set of instructions decoding nothing is exactly a written-down list, each entry with its reason. Closing a gap fails the test until the entry is deleted; opening one fails it until the entry is added with a justification.

It started at twelve entries and is at seven: EXP and VINTRP's three have no layout established, MIMG's one needs a resource layout the guest side cannot supply, and s_waitcnt/s_clause are structured immediates. The five typed-buffer entries came off it the same day, which is the test working - the list shrinking is a thing somebody had to do deliberately, and it fails loudly if the list and the code disagree in either direction.

That is the durable half of this decision. The overlap rule fixes two opcodes; the converse test is what stops the next silent empty answer being found by accident.