On x86-64, producing a boolean from a comparison takes a compare, a SETcc, and usually a widening move. On AArch64 it takes one instruction: csel w0, w1, w2, eq reads the flags subs wrote and writes one of two registers. There is no branch, no branch-delay slot, and no separate compare. That is the whole argument for the conditional-select group, and it is why a compiler that reaches for it turns a four-instruction sequence into one.
The reason this gets its own concept rather than a paragraph is that the encoding has a trap in it that produces correct-looking output when you get it wrong, and the trap is the kind that survives review: a decoder that models cset as “a csinc with both sources zeroed” reports every condition in the program inverted, and the result is 0 or 1 either way. Nothing about the output says you are wrong.
Sixteen conditions in a four-bit field, and the table below is built by assembling one csel per value rather than by quoting a list of names:
$ ./a64dec.py --section=10 | sed -n '/10A\./,/Two names exist/p'
value bits name word decoded here
0 0000 eq 1a820020 csel w0, w1, w2, eq
1 0001 ne 1a821020 csel w0, w1, w2, ne
2 0010 cs 1a822020 csel w0, w1, w2, cs
3 0011 cc 1a823020 csel w0, w1, w2, cc
4 0100 mi 1a824020 csel w0, w1, w2, mi
5 0101 pl 1a825020 csel w0, w1, w2, pl
6 0110 vs 1a826020 csel w0, w1, w2, vs
7 0111 vc 1a827020 csel w0, w1, w2, vc
8 1000 hi 1a828020 csel w0, w1, w2, hi
9 1001 ls 1a829020 csel w0, w1, w2, ls
10 1010 ge 1a82a020 csel w0, w1, w2, ge
11 1011 lt 1a82b020 csel w0, w1, w2, lt
12 1100 gt 1a82c020 csel w0, w1, w2, gt
13 1101 le 1a82d020 csel w0, w1, w2, le
14 1110 al 1a82e020 csel w0, w1, w2, al
15 1111 nv 1a82f020 csel w0, w1, w2, nv
One structural fact is visible in the fourth column and worth stating because it is the cheapest possible way to check the field position: the sixteen words are identical except for the nibble at bits[15:12]. Everything else — the destination at bits[4:0], the first source at bits[9:5], the second at bits[20:16], the whole opcode — is held constant across all sixteen rows. The condition is four bits in the middle of the word and that is the entire claim, and the harness asserts it as a bit pattern rather than as a sentence.
And there is a naming wrinkle that is a fact about the ISA and a nuisance for a cross-check. Values 2 and 3 have two names each. In a select the assembler calls them hs and lo; in a branch it calls them cs and cc. Both were measured: csel w0, w1, w2, cs assembles to 0x1a822020 and the disassembler calls it hs, while b.cs T assembles to 0x54000020 and the disassembler calls it cs. The same four bits, two names, and the printer picks by position. This page uses the select spelling throughout and concept 5 normalises the other one — and says that it does, which is the rule for every normalisation in that section.
op2 is not the operationThe conditional-select group has more than one field selecting the operation, and getting the relationship wrong is the first thing that goes. Here is the whole table, built as words and then asked about:
$ ./a64dec.py --section=10 | sed -n '/bit30 (invert)/,/SAME/p'
bit30 (invert) bits[11:10] word the oracle calls it this decoder calls it
0 00 1a800022 csel w2, w1, w0, eq csel w2, w1, w0, eq
0 01 1a800422 csinc w2, w1, w0, eq csinc w2, w1, w0, eq
0 10 1a800822 (blank) (undefined)
0 11 1a800c22 (blank) (undefined)
1 00 5a800022 csinv w2, w1, w0, eq csinv w2, w1, w0, eq
1 01 5a800422 csneg w2, w1, w0, eq csneg w2, w1, w0, eq
1 10 5a800822 (blank) (undefined)
1 11 5a800c22 (blank) (undefined)
Two facts, and the first one corrects a very natural assumption.
There are four operations, not sixteen and not eight:csel, csinc, csinv and csneg, selected by the pair (bit 30, bits[11:10]) = (0,00), (0,01), (1,00) and (1,01). The first version of this decoder read bits[11:10] as the operation and got csinv and csneg wrong in the corpus — and those two disagreements were in the two instructions the branchless-csel discussion quotes, which is the worst possible place for a bug to land.
The other four cells of the 4x4 are UNDEFINED. The oracle prints <unknown> for all four and this decoder raises on all four. The first draft of the file's own comment called two of them “the SET forms”, which is a plausible-sounding wrong answer that would have taught a reader the group has six operations. (The second version of that table was wrong in a different instructive way: it assembled a line per cell, and since there is no mnemonic for (invert, op2) = (1, 0) the assembler silently produced a csinv for all four of the inv = 1 rows and the table printed csinv eight times. Building the cells as words is the only way a cell with no name can still be measured.)
Here is where the trap lives, and the table below is the evidence:
$ ./a64dec.py --section=10 | sed -n '/cset w0, eq /,/cneg w0, w1, ge/p'
cset w0, eq 1a9f17e0 csinc w0, wzr, wzr, ne 1a9f17e0 SAME
cset w0, ne 1a9f07e0 csinc w0, wzr, wzr, eq 1a9f07e0 SAME
csetm w0, eq 5a9f13e0 csinv w0, wzr, wzr, ne 5a9f13e0 SAME
csetm x0, ne da9f03e0 csinv x0, xzr, xzr, eq da9f03e0 SAME
cinc w0, w1, eq 1a811420 csinc w0, w1, w1, ne 1a811420 SAME
cinc w1, w2, eq 1a821441 csinc w1, w2, w2, ne 1a821441 SAME
cneg w0, w1, ge 5a81b420 csneg w0, w1, w1, lt 5a81b420 SAME
csinc w0, wzr, wzr, al 1a9fe7e0 cset w0, eq 1a9f17e0 DIFFERENT
csinc w0, wzr, wzr, ne 1a9f17e0 cset w0, eq 1a9f17e0 SAME
csinc w0, wzr, wzr, nv 1a9ff7e0 cset w0, eq 1a9f17e0 DIFFERENT
csinv w0, wzr, wzr, al 5a9fe3e0 csetm w0, eq 5a9f13e0 DIFFERENT
Read the first row and then the eighth. cset w0, eq is csinc w0, wzr, wzr, ne — the condition is inverted, and the two are different 32 bits that differ in the condition field and nowhere else. And the assembly text of csinc w0, wzr, wzr, al, which is the ninth row, is cset w0, ne: a different word again.
Why the inversion, in one sentence: “1 if the condition holds, else 0” is “0 + 0 if it does not hold, else 0 + 1”. Both sources are the zero register, so the sum is the constant, and the constant is 1 in the case where the condition is false. The assembler prints the user's condition and the encoding holds its negation, and both are right about what the instruction does.
So the shape of the bug is worth stating exactly, because it is the general form:
cset as “a csinc with Rn = Rm = 31” reports every condition in the program inverted.And note what a field map cannot show you. CSET reuses the same two field values as CSINC; what is different is that Rn and Rm are 31, and 31 means the zero register in this group. Four opcodes plus two register values give a sixth and a seventh instruction, and there is no bit anywhere that says “this is a set”. That is why the 4x4 table has no cset row and why the model in the artifact has to read the registers to name it.
Every condition is a function of four flag bits — Negative, Zero, Carry, Overflow. They are written by the S-suffixed instructions (adds, subs, ands, bics) and by nothing else a user-mode program can reach. There is no PUSHFLAGS, no LAHF, no mov rflags, rax.
The four bits are written by an operation and read by the next one, and the whole conditional group is one instruction wide from the write to the read. That is the structural reason csel is one instruction rather than two, and it is also the thing a user-mode program cannot inspect: you cannot read NZCV without consuming an operation's result, and there is no instruction whose sole effect is to put a value in those bits.
What cannot be measured here, and is not claimed anywhere in this course: the latency of the flag write, whether a particular core renames the flags, and whether a select is cheaper than a branch on a branch-free predictor. Those need an AArch64 machine and there is none on the build host. The artifact says so in section 15 in its own words rather than leaving it to a reader's discretion, and the landing page's limits block says it a second time.
And a B.cond is bits[31:24] = 0b01010100, imm19 at bits[23:5], and the condition at bits[4:0]:
$ ./a64dec.py --section=10 | sed -n '/10C\./,/A B.cond is/p'
value bits name word decoded here
0 0000 eq 54ffffc0 b.eq -8
14 1110 al 54ffffce b.al -8
15 1111 nv 54ffffcf b.nv -8
The same four bits are at position 4 here and at position 12 in a csel, and the two fields do not overlap. A decoder that finds one cond field and uses it for both gets every conditional instruction in the program wrong, in the same direction — and in this case the error is visible in the output, because b.eq and b.ne are different branches and a program that takes the wrong one misbehaves. That is the good case. The cset case above is the bad one, and it is the one that got its own concept's warning.
AArch64 does not have a single condition field. That is a cost of the encoding rather than an accident: two different places, two different widths, one meaning. Concept 2's field table shows both positions side by side, and this is what the two non-contiguous rows in it are about.
$ ./a64dec.py --section=10 | sed -n '/conditional selects:/,/half of/p'
conditional selects: 56 conditional branches: 49
and the 45-looking number is not a typo: 23 of the 56 selects are
the INVERTED forms -- cset, csetm, cinc, cinv, cneg -- which are
the ones whose condition field is the negation of the one the
mnemonic names. A decoder that misses the inversion decodes 23
instructions in this corpus with the condition backwards, and
every one of them still produces a correct boolean for half of
its inputs.
Both are real and both are used, and 23 of the 56 selects are the inverted forms this page is about. A decoder that missed the inversion would decode 23 of these with the condition backwards, and every one would still produce a correct boolean for half its inputs.
The family has nine names and the artifact's first version counted them with a startswith test — which misses csinc, csinv and csneg, because “csinc” starts with “csi” and not with “cse”. That printed 45 where the answer is 56, and the eleven missing instructions were three of the four operations in the group: the section's own count of its own family was short by exactly the operations whose selection depends on the bit-30/op2 pair. The list is now spelled out and the count printed as a sum of the numbers above it.
Which one a compiler reaches for is a decision about whether the two sides of the branch are equally likely — a property of the program, not of the architecture. So that number is a fact about this corpus and a fact about clang, and concept 5 measures what the choice costs in bytes rather than arguing about it.
a64dec.py, in m_condselect, change cond_text(cond ^ 1) to cond_text(cond) in the cset branch. (Expect the section 12 cross-check to report 14 disagreements — the 14 csets in the corpus — and expect the section 10 table to still print 16 rows of which 8 are now WRONG while looking exactly as it did before. That is the shape of the bug: a table of plausible names, a cross-check that finds it, and a reader who reads the table first and believes it.)csel words from the first table and check that the only bits that differ are bits[15:12]. (Expect the words to be 0x1a8_?_020 with the ? running 0 through f in order — sixteen words, one varying nibble, everything else identical. Then do the same for the sixteen b.cond words and expect the varying nibble to be in a different place entirely, at the bottom of the word. Two groups, one meaning, two positions.)0x1a800822 and 0x1a800c22 — the two op2 values the table above calls UNDEFINED — and ask the assembler to assemble csel w0, w1, w2, eq variants that would land there, or hand them to the decoder. (Expect <unknown> from the disassembler and an explicit UNDEFINED from this decoder, rather than an alias onto csinc or csneg. A decoder that lets a reserved value alias onto a real instruction is worse than one that admits it does not know, because the admission is visible and the alias is not.)Backwards, x86-flags is where the four-instruction-to-boolean sequence comes from, and concept 1 is where cset appeared as the last row of the alias table with a one-line warning. Concept 2 measured the condition field and the two op2 rows that are not contiguous.
Forwards, concept 5 is the cross-check, and this page is the reason it exists: a bug that inverts a condition produces output that is 0 or 1 either way, and the only instrument that finds it is a second reader that was not written by the same hand. Back to the field map if the op2 rows there stopped making sense.
Outward, and this is the concept a compiler backend is most likely to get wrong. Every code generator that emits CSET has to know that the field holds the inverted condition, because a backend that inverts it twice produces a correct program on the inputs where both tests agree and an inverted one everywhere else — and a boolean inverted is a boolean, so the failure rate looks like a flaky test rather than an encoding bug.