The last concept established that the length is a constant, and the immediate consequence is that everything else has to fit in the remaining 32 bits with no escape. There is no second word to fall back into. So the field map is not a convenience on this architecture — it is the curriculum, and a decoder is a decision tree over a fixed width rather than a length table followed by an opcode table.
Which makes the field map the thing worth being careful about, because it is exactly the thing a person reads out of a manual once and then trusts. This concept measures it instead: 58 field positions, each one obtained by assembling an instruction, changing one operand, and OR-ing the XOR over a sweep of variants. The guards in the artifact are built from that table, and the table is written to fields.txt so you can check it.
The reason a measurement rather than a reading, in one line: a field map read out of a manual is a claim, and a claim cannot be debugged.
Four bits at position 25 are the first thing a decoder looks at. The class names are quoted from the architecture's reference manual; the class membership is measured, by taking the instructions out of five object files, reading four bits out of each, and asking llvm-objdump-21 what that instruction is:
$ ./a64dec.py --section=3 | sed -n '/bits n /,/Empty here/p'
bits n what landed there, and how often the QUOTED name
00100 23 ldp:15 stp:8 Loads and Stores (pair)
00101 111 add:40 mov:34 subs:6 cmp:5 neg:4 Data Processing -- Register
01100 151 ldr:83 str:59 ldur:2 ldrb:2 ldrsw:2 Loads and Stores
01101 101 csel:22 cset:14 mul:10 madd:10 lsl:5 Data Processing -- Register
01011 142 ret:134 tbz:4 br:1 blr:1 tbnz:1 Branches, Exception Gen and Sys
01001 134 mov:45 and:28 movk:19 lsl:9 orr:8 Data Processing -- Immediate
01000 114 add:41 sub:31 cmp:20 subs:11 mov:4 Data Processing -- Immediate
01010 84 b:27 bl:10 b.ne:8 b.eq:7 cbz:6 Branches, Exception Gen and Sys
00110 1 ldp:1 Advanced SIMD (pair)
00111 6 add:3 movi:2 addv:1 Advanced SIMD
01111 1 fmov:1 Advanced SIMD / FP
11 of the 16 values carry at least one real instruction in this
corpus. Empty here: 00000, 00001, 00010, 00011, 01110.
Two numbers to take from that. Four bits, not two. The two-bit rule this decoder first used put ret in “Data Processing — Register”, because bits[28:26] of 0xd65f03c0 is 0b110; ret is 134 of the 868, the single largest entry in the distribution. And 01101 is a class a two-bit rule cannot see at all — it holds csel, cset, mul and madd, 101 instructions. That is not cosmetic: 01101 and 00101 are different code paths through a decoder, with different guards and different operand layouts.
And then the honest caveat, which is the part a caption usually leaves out. Five of the sixteen values are empty in this corpus, and four of those five carry SIMD. That is a fact about 25 functions of ordinary C, not about the architecture. The correct statement is that a decoder may implement 11 of the 16 classes and be correct on every instruction in this corpus — and the 5 empty values are values whose contents this corpus did not exercise, not "unimplemented classes". A class field is four bits wide whether or not a program uses it.
One line long: assemble an instruction, change one operand, assemble it again, and every bit that moved belongs to that operand.
The first version of this measurement was wrong in a way that looked like a result, and the mistake is the most useful thing on this page. A single pair marks the bits where the two values differ, which is a subset of the field and not the field. Measured with one pair, movz w0, #0x1111 against movz w0, #0x2222 gives an 8-bit imm16 — because 0x1111 and 0x2222 happen to differ in eight bit positions — when the field is sixteen bits wide. The table looked wrong because the table was.
$ ./a64dec.py --section=4 | sed -n '/^ field /,/imm16/p'
field xor mask the positions base instruction
Rd 0000000f bits[3:0] add w0, w1, w2
Rn 000001c0 bits[8:6] add w0, w1, w2
Rm 000f0000 bits[19:16] add w0, w1, w2
sf 80030001 bits[0,16,17,31] add w0, w1, w2
op 40000000 bits[30:30] add w0, w1, w2
S 20000000 bits[29:29] add w0, w1, w2
shift amt 0000fc00 bits[15:10] add x0, x1, x2, lsl #0
shift type 00e0e000 bits[13,14,15,21,22,23] add w0, w1, w2, lsl #3
opc 60000000 bits[30:29] and w0, w1, w2
imm12 003ffc00 bits[21:10] add w0, w1, #0x0
imm16 001fffe0 bits[20:5] movz w0, #0x0000
hw 00600000 bits[22:21] movz x0, #0x1111
The fix is to OR the XOR over a sweep of variants, one per single bit of the operand, so every position the operand can reach is marked. And the artifact is explicit about what the result is: a mask is a LOWER BOUND. Every bit in it really does belong to that operand; a bit not in it is a bit the sweep did not happen to move. The Rn row above is four bits wide and the field is five, because the sweep visited odd register numbers and never moved bit 5 — and the file prints the not-moved bits per row rather than rounding the mask up to a span.
$ ./a64dec.py --section=4 | sed -n '/^ pair Rt2/,/pair scale/p'
pair Rt 0000001f bits[4:0] stp x0, x1, [sp]
pair Rt2 00007c00 bits[14:10] stp x0, x1, [sp]
pair Rn 000003e0 bits[9:5] stp x0, x1, [sp]
pair imm 001f8000 bits[20:15] stp x0, x1, [sp]
pair mode 01bf0000 bits[16,17,18,19,20,21,23,24] stp x0, x1, [sp]
pair L 00400000 bits[22:22] stp x0, x1, [sp]
pair scale 84000000 bits[26,31] stp w0, x1, [sp]
Rt2 in the pair form is bits[14:10] and not bits[11:10]. This is the single most common AArch64 decoder bug in existence, and the reason is a coincidence that hides it: a decoder that guesses a two-bit field gets stp x0, x1 right, because for that pair the two candidate fields happen to hold the same value. Every other pair in the corpus is wrong. A field measurement that agrees with the first specimen and disagrees with the second is not a measurement — it is a coincidence with a number in it, which is why the sweep visits six register numbers instead of one.cond field and uses it for both gets every conditional instruction in the program wrong, in the same direction, and there is no way to tell from the output. Concept 4 is this row's consequences.Rd mask is bits[4:0] in each case. No field map can express that difference — only a decoder that says which group it is in can, which is why the artifact's register function takes a permission flag from the model and never guesses. The mistake the cross-check actually found was the mirror image: the logical-immediate model calling it with the default and printing sp where the encoding says zero. R15.imm12 is bits[21:10] and the scale on it is a separate two-bit field that is only read when it is 0b01. add w0, w1, #0x1000 and add w0, w1, #1, lsl #12 are the same 32 bits, which concept 1 measured, and add w0, w1, #0x1001 is refused — see concept 3, where the immediate story gets its own page.Two of the rows in that table are not contiguous, and the gaps are not measurement failures. In the conditional-select family bit 30 and bits[11:10] together pick the operation, and the file prints the gap so you can see the difference between "not measured" and "not one field". In the bitfield family the same bits[21:10] window holds immr for a BFXIL and imms for a BFM — two instructions sharing a field, which is not one operand.
And a field this method cannot find is a field that is never written: reserved bits, the 0b11 value of the add/sub scale (the assembler refuses it), and the top bit of a shift amount on a 32-bit register (lsl #32 on w registers is refused too). Those are measured in section 9 of the artifact by asking the assembler and printing its diagnostic verbatim.
Each of these was asserted in a draft of this course, measured, and retracted. They are here rather than at the end of the course because each one is a place where the obvious thing to write down is wrong, and knowing which three is worth more than knowing the three facts.
ret in the wrong class — 134 of 868, the largest single entry in the distribution — and could not see class 01101 at all, which is where csel, cset, mul and madd live. A two-bit field is what the older documentation says and a four-bit one is what 868 assembled words say.imm16 for a sixteen-bit field, and the table looked wrong because the table was. The fix is a sweep; the honesty is to say what a mask is not.sp in add/sub and zero in the logical groups, over an identical bits[4:0]. The class names in the table above are QUOTED from the manual; the membership is MEASURED, and the two are printed in separate columns because a course that blurs them cannot say which one a reader is trusting.Every model checks its own fixed bits first and returns false if they do not match, so the dispatch is a decision tree a reader can follow top to bottom rather than a table of magic numbers. --audit prints it with the guard each model checks, and the harness asserts that the table has 21 rows:
$ ./a64dec.py --audit | head -26
model the guard it checks claim
m_adr bits[28:24] = 0b10000, op = bit 31 ADR and ADRP
m_addsub_imm bits[28:24] = 0b10001 ADD/SUB, 12-bit immediate
m_logical_imm bits[28:23] = 0b100100 AND/ORR/EOR/ANDS, rotate+run
m_movewide bits[28:23] = 0b100101 MOVZ/MOVN/MOVK
m_bitfield bits[28:23] = 0b100110 SBFM/UBFM/BFM/BFXIL
m_extr bits[28:23] = 0b100111 EXTR, and ROR as its alias
m_bcond bits[31:24] = 0b01010100 B.cond, imm19 at 23:5
m_b_bl bits[30:26] = 0b00101 B and BL, imm26
m_cbz bits[30:25] = 0b011010 CBZ and CBNZ
m_tbz bits[30:25] = 0b011011 TBZ and TBNZ, imm14
m_br_blr_ret bits[31:23] = 0b110101100, opc=22:21 BR, BLR, RET
m_hint bits[31:5] = 0xd503201f >> 5 NOP, WFI, YIELD
m_ldst_uimm bits[29:27]=0b111, bits[26:24]=0b001 LDR/STR, 12-bit scaled
m_ldst_uimm9 bits[29:27]=0b111, bits[26:24]=0b000 LDUR/STUR, 9-bit unscaled
m_ldst_pair bits[29:27] = 0b101 LDP/STP, Rt2 at 14:10
m_addsub_shifted bits[28:24] = 0b01011 ADD/SUB shifted; Rd=31 is sp
m_logical_shifted bits[28:24] = 0b01010 AND/ORR shifted; Rn=31 is xzr
m_condselect bits[28:21] = 0b11010100 CSEL family
m_3src bits[30:24] = 0b0011011 MADD/MSUB/MNEG
m_2src bits[30:21] = 0b0011010110 LSLV..RORV, UDIV, SDIV
m_clz_rev bits[30:21] = 0b1011010110 CLZ, REV, REV16
21 models, and 21 claims -- the two numbers are printed
together because a model with no claim is a model nobody can check.
The order is the dispatch order, and it is not alphabetical and not arbitrary: the two models with the largest fixed patterns (m_adr at five bits and m_hint at twenty-seven) belong near the top, because a cheap-and-specific guard belongs above an expensive general one. Reordering this list changes what the decoder resolves in the overlap, and that is a design decision rather than a refactor.
Three guards in that table are worth expanding, because each is a place where a wrong assumption is silent rather than loud:
m_ldst_uimm9 is a separate model from m_ldst_uimm, and that is a fact about the encoding rather than about tidiness. ldr w0, [x1, #8] and ldur w0, [x1, #8] both assemble, and the difference is a scale: the first is a 12-bit field scaled by the access size, the second a 9-bit field unscaled and signed. The first version of the field sweep put both in one row and the mask came out with two odd bits in it, which is a measurement telling you that two things are in the list that are not one field.m_hint guards twenty-seven bits. bits[31:5] = 0xd503201f >> 5 is the most information this decoder ever discards, and the first version guarded bits[31:24] = 0b11010110, which is 0xd6 and not 0xd5 — and so decoded nothing in the family at all. A one-bit transcription error in a guard is indistinguishable from "this architecture has no hints".m_logical_shifted and m_addsub_shifted differ by one bit in the guard (01010 against 01011) and disagree about what register 31 means. One printed cmp w0, w1 for an orr and the other printed mvn w0, w1 for an orn, and both were right, because the encodings really are that close together.fields.txt. Pick pair imm and write the sweep yourself: stp x0, x1, [sp] against [sp, #8], [sp, #16], [sp, #32], [sp, #64], [sp, #128], [sp, #256]. (Expect mask 0x001f8000, bits[20:15], and expect the row to teach you why the reach is ±512 and not ±4095: the field is seven bits scaled by eight. Then ask the assembler for [sp, #4] and read the diagnostic — "index must be a multiple of 8 in range [-512, 504]" — which is a boundary stated in the words of the thing that enforces it.)a64dec.py, change the imm16 case in FIELD_CASES to a single variant, movz w0, #0x2222. (Expect the mask to come out 0x00066660 — eight bits — for a sixteen-bit field, and expect nothing else in the run to notice. That is the point: a measurement that is merely wrong looks exactly like a measurement that is right unless the sweep is part of the method.)m_hint, change the guard from 0xd503201f >> 5 to 0xd603201f >> 5. (Expect the three nops in the corpus to stop being named and to be counted as unmodelled instead, expect the section 5 bit budget to lose a row, and expect the section 12 cross-check to report fewer NAMED instructions rather than a disagreement — because an unmodelled word is out of scope, not wrong. A decoder that silently returns fewer instructions is more dangerous than one that returns a wrong name, and this is the case where you can see the difference.)Backwards, concept 1 is the reason this page has no length arithmetic in it, and isa-modrm is the contrast case: the ModRM byte exists because x86 needed to name a register-from-memory operand out of a fixed field, and AArch64's load/store pair spends five bits on its second register instead of two.
Forwards, concept 3 takes the imm12 row and the imm16 row and finds out what the twelve unreadable bits reach, and concept 4 takes the cond row and the two non-contiguous rows and finds that a fourth of the conditional-select group is UNDEFINED. Concept 5 is where the register-31 mistake in this page is actually found — it was not found by reading this table twice.