AArch64 Playground
4.13 · Decisions in one instruction: csel, cinc, and cbz

Decisions in one instruction: csel, cinc, and cbz

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, lt

The 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 100
loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 1try it: run it, or step one instruction at a timeOpen in playground

note

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 negative

The 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 both
loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 2try it: run it, or step one instruction at a timeOpen in playground

note

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...liftoff
loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 3try it: run it, or step one instruction at a timeOpen in playground

Set 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.

tbz and tbnz do the same job for a single bit: they jump when the bit you name is 0, or when it is 1. Every instruction in this lesson has a reference entry with an example to run: csel, cset, cinc, csinc, cbz and cbnz.

Check yourself

  1. After cmp w1, w2, which one instruction puts the smaller of the two in w0, reading both as signed?
  2. After cmp w3, w3, what do cset w4, eq and cset w5, ne leave in w4 and w5?
  3. w6 holds 7. What does it hold after cmp w6, 10 and then cinc w6, w6, gt?
  4. Which one instruction does the work of cmp w19, 0 followed by b.ne again?
  5. In the clamp program, the first csel gets its 0 from wzr. Why does the second one need w9?

answers

show answers
  1. csel w0, w1, w2, lt. With le it also works: when the two are equal, either one is the smaller.
  2. w4 holds 1 and w5 holds 0. The values are equal, so the compare set Z.
  3. Still 7. The condition 7 > 10 is false, so cinc leaves the value as it was.
  4. cbnz w19, again.
  5. csel chooses between two registers. wzr always reads as 0, but no register always reads as 100, so the program puts 100 in w9 first.

Practice