D105 - Hand-written assembly is a fixture source, and it caught a wrong row

Status: decided (2026-08-21) - the gap it recorded was described wrongly, then closed

orbistoun-gen fixtures now accepts .s sources alongside .ll, assembling them with llvm-mc instead of compiling with llc. The disassembly step afterwards is identical, so a hand-written fixture is verified exactly as a compiled one is.

It exists because SOPK, MTBUF and VINTRP could not be reached any other way. They arise from specific constant patterns, typed buffer formats and fragment interpolation, and no LLVM IR the generator can write makes the compiler emit them - so those three rows stayed transcribed-and-never-checked while the other fourteen had been verified against a reference (D085, D089).

A hand-written fixture is the weaker kind and the table now says so. The instruction is one somebody thought of rather than one a compiler reached for, so it shows the row is right about the instructions written, not that it covers everything the family can express. Compiled fixtures stay the default for exactly that reason.

It found a wrong row on the first run. VINTRP's encoding value was this family's value from the first two generations of the architecture, while the table targets a later one - so the decoder called v_interp_p1_f32 unrecognised the moment a fixture reached the family. The row had been sitting next to a comment saying it was unverified, which is a different thing from being verified as wrong. Corrected from the published documentation for this generation; the reference detected the error and did not supply the fix, which is the line D085 draws.

One gap left recorded rather than closed. MTBUF's opcode field is three bits in this table and four in the specification.

That gap was recorded wrongly, and it is bigger than it said

The original wording claimed the family defines eight opcodes, so the two readings could not disagree about any instruction that exists and no fixture could separate them. Both halves are wrong, and the reference for this generation says so plainly.

The fourth bit is not adjacent. The opcode is split: bits 18:16 of the first word and one more at bit 53, which is in the second word. So "widen it to four bits" would not have been the fix - bit 19 is not the opcode, and widening would have read a neighbouring field as part of it.

There are sixteen opcodes, not eight. Eight operations and their eight half-precision variants. The ninth opcode is not something to meet later; it exists now.

They are separable, and currently conflated. Assembling tbuffer_load_format_x and tbuffer_load_format_d16_x produces an identical first word - the only difference is the bit this table does not read. So every half-precision variant decodes today as the operation it is a variant of.

Closed on 2026-08-21

The stated reason for leaving it open was that a field cannot be described in two pieces in this table, and that inventing a schema for one family with no consumer would be building for a case that does not exist.

Both halves of that expired. Encoding now carries an optional opcode_extension, which is one field rather than a general mechanism for arbitrary splits - every split this instruction set actually has is a single continuation, and a list of pieces would be a shape to maintain for a case that has never occurred. And MTBUF is no longer refused pending a resource model: the resource model landed with MUBUF, so a typed buffer access is the same descriptor and the same addressing with a format conversion on top.

The pinned test did its job. the_typed_buffer_variants_are_known_to_be_conflated was written as a passing assertion that the conflation existed, so that closing the gap would fail and say which notes to update. It failed with exactly that message. It is kept, inverted, as the_typed_buffer_half_precision_variants_decode_distinctly - and it asserts the variant is its counterpart plus eight, not merely different, because a continuation shifted to the wrong place still produces two distinct numbers.

The generators duplicate this rule. classify() exists in orbistoun-gen fixtures and orbistoun-gen operands as well as in the decoder. Leaving those reading only the contiguous part would have classified a half-precision variant as its counterpart and emitted a second name for an opcode that already had one - which the name table refuses to load, so that particular drift would have surfaced loudly. Not every drift would, which is why they were changed together rather than when something broke.

The half-precision names are still unknown, deliberately. mnemonics.toml is generated from what a compiler actually emitted and a reference disassembler actually named, and no fixture contains a d16 variant yet. So one now decodes as a known family with an unrecognised opcode - which is honest - rather than as the wrong instruction, which is what it did before.

What changed is that it is now measured rather than guessed at, and the entry says where the bit is so the fix is an edit rather than an investigation.

And it fails loudly rather than quietly. Both names map to one opcode in the name table, which refuses to load when an opcode is named twice. A corpus containing a half-precision variant turns this into a startup failure naming the collision, not a wrong mnemonic in a report. That guard was already there for a different reason and covers this one for free.

The fixture list was hardcoded, so adding a file changed nothing. unreached.s generated its .bin and .txt and the suite reported green while never opening either - the same shape as a device test skipping quietly. There is now a test asserting the fixtures on disk and the fixtures the suite reads are the same set.