Decisions in one instruction: csel, cinc, and cbz
prerequisite
Read these first:
An if-else that only chooses between two values costs a cmp, two branches, and two labels. AArch64 has instructions that make the choice in one line after the cmp, and branches that test a register for zero with no cmp at all. This lesson covers both kinds and uses them in three short programs.
Picking a value with csel
csel w0, w1, w2, cond (conditional select) reads the flags the same way b.cond does. When the condition holds, it copies w1 into w0. When it does not, it copies w2. It never jumps. C writes the same choice with the ?: operator:
level = (raw < 0) ? 0 : raw;Here is that line in assembly twice, first with branches and then with csel:
// with branches: two jumps and two labels cmp raw_r, 0 b.ge keep_raw mov level_r, 0 b pickedkeep_raw: mov level_r, raw_rpicked: // with csel: one line after the cmp cmp raw_r, 0 csel level_r, wzr, raw_r, ltThe condition names are the ones b.cond uses: lt, le, gt and ge for signed values, lo, ls, hi and hs for unsigned ones, and eq and ne for both. wzr, the zero register, always reads as 0, so it hands csel a zero without using up a register. Both sources must be registers, so to choose any other constant, mov it into a register first.
The program below clamps each reading into the range 0 to 100, the way a volume knob stops at either end: below 0 becomes 0 and above 100 becomes 100. Two csel instructions do the work, so the clamp itself has no conditional branch (no b.cond). It prints:
raw -30 -> level 0raw 15 -> level 15raw 60 -> level 60raw 105 -> level 100raw 150 -> level 100note
w9 holds 100 only from its mov to the second csel, with no call in between, so a scratch register is safe there. raw_r and level_r must still hold their values after printf, so they live in w19 and w20.
Counting with cset and cinc
Two more instructions turn a condition into a number. cset w0, cond (conditional set) writes 1 to w0 when the condition holds and 0 when it does not. cinc w0, w1, cond (conditional increment) writes w1 + 1 when the condition holds and w1 unchanged when it does not. Written as cinc count_r, count_r, eq, it adds one to a count only when the last compare found the two values equal:
cmp w0, 0 cset w1, lt // w1 = 1 when w0 is negative, else 0 cinc w2, w2, ge // one more in w2 when w0 is not negativeThe program below counts the numbers from 1 to 100 that 3 divides, that 5 divides, and that both divide. The remainder comes from udiv and msub, as in Arithmetic, and the missing remainder, and a number is divisible when its remainder is 0. For both at once, the program adds the two remainders. Neither one is ever negative, so their sum is 0 only when both are 0. It prints:
1 to 100: 33 divisible by 3, 20 by 5, 6 by bothnote
cset and cinc are other names for csinc (conditional select increment), which gives its first source when the condition holds and its second source plus 1 when it does not. cinc w0, w1, eq is csinc w0, w1, w1, ne, with the condition turned around, so a disassembler may show you the csinc form.
Branching on zero with cbz and cbnz
Testing a register against 0 is common enough to have its own branches. cbz w19, label (compare and branch on zero) jumps when w19 is 0, and cbnz w19, label jumps when it is not. Each one does the work of a cmp w19, 0 followed by b.eq or b.ne, in one instruction.
The countdown below uses both. The cbz before the loop skips the loop when the count starts at 0, the job the jump to the test does in a pre-test loop. The cbnz at the bottom sends it round again until the count reaches 0. It prints:
5...4...3...2...1...liftoffSet START to 0 and the program prints only liftoff: the cbz jumps straight past the loop.
pitfall
cbz and cbnz compare with 0 and nothing else; to stop at 10, use cmp and b.cond. They also leave the flags alone, so a b.eq after a cbz reads whatever the last cmp left behind, not the result of the cbz.
Check yourself
- After
cmp w1, w2, which one instruction puts the smaller of the two inw0, reading both as signed? - After
cmp w3, w3, what docset w4, eqandcset w5, neleave inw4andw5? w6holds 7. What does it hold aftercmp w6, 10and thencinc w6, w6, gt?- Which one instruction does the work of
cmp w19, 0followed byb.ne again? - In the clamp program, the first
cselgets its 0 fromwzr. Why does the second one needw9?
answers
show answers
csel w0, w1, w2, lt. Withleit also works: when the two are equal, either one is the smaller.w4holds 1 andw5holds 0. The values are equal, so the compare set Z.- Still 7. The condition 7 > 10 is false, so
cincleaves the value as it was. cbnz w19, again.cselchooses between two registers.wzralways reads as 0, but no register always reads as 100, so the program puts 100 inw9first.
Practice
- Pick the bigger one: try it with one
cseland no branch. - Rock, paper, scissors referee: once the compares are done,
cselcan pick which message to print. - Basic quiz: branching: reading the flags, with
cbzandcselamong the questions. - Fill in the blank: branching (core): write the
csel,cset,cincorcbza line needs. - Predict: branching (challenge): what conditional selects and zero tests leave behind,
csincincluded. - Fill in the blank: loops (core) and Intermediate quiz: loops: each ends a countdown with
cbnz.