AArch64 Playground
4.10 · Conditionals with cmp and b.cond

Conditionals with cmp and b.cond

Every program has to make decisions. In high-level languages a decision is written with if and else. ARMv8 AArch64 assembly has no such construct.

In a language such as C, a decision is an if statement. For example:

#include <stdio.h>int main() {    int a = 6;    int b = 7;    if (a > b) {        printf("XX\n");    } else {        printf("XY\n");    }    return 0;}

The code above compares the values stored in the variables a and b. "XX" will be printed if the value of a is greater than b; otherwise, "XY" is printed.

At the assembly level, the processor does not understand high-level abstractions such as if and else. Instead, a decision is made by steering the flow of execution directly, through comparisons, condition codes, and branch instructions.

The big picture

Conditional constructs in assembly are built by comparing values and changing the program's flow of execution based on the resulting condition flags. The process has two steps:

  1. Compare two values using the cmp instruction.
  2. Use a branch instruction (b.cond) to jump to a specific label when the condition is satisfied.

note

b.cond is not an actual assembly instruction. The cond part represents a placeholder for a condition code. Replacing cond with a specific condition code creates the actual branch instruction, such as b.eq, b.ne, or b.gt. See the b.cond entry in the Reference for an interactive breakdown of every condition code.

More about cmp and b.cond

The cmp instruction is the compare instruction. It compares the contents of two registers, or the contents of a register with an immediate value (a constant written into the instruction itself, such as the 5 in cmp w19, 5).

cmp performs the comparison and takes no action on the result. Instead it updates the condition flags, four one-bit records the processor keeps about the last result that set them:

  • N (negative) is 1 when the result was negative.
  • Z (zero) is 1 when the result was zero, which after a compare means the two values were equal.
  • C (carry) is 1 when the subtraction did not need to borrow, which means the first value was at least as large as the second when both are read as unsigned (never negative) numbers.
  • V (overflow) is 1 when the true signed result was too large or too small to fit in the register.

cmp a, b gets these flags by working out a - b and throwing the difference away. Instructions that use a condition code, such as b.cond, examine the flags. If the specified condition code (cond) is met, b.cond branches to the specified label. Otherwise, execution continues with the instruction immediately following b.cond.

note

cmp is not the only instruction that sets the flags. An s on the end of some arithmetic instructions makes them set the flags as well: subs subtracts and sets them, and adds adds and sets them. In fact cmp w19, w20 is another way to write subs wzr, w19, w20, a subtraction whose result goes to the zero register and is lost. Plain add and sub leave the flags alone.

Condition codes for signed values

Six condition codes cover the comparisons C writes with ==, !=, >, >=, < and <=. Each one reads the flags left by cmp a, b:

CodeBranches whenFlags it reads
eqa == bZ is 1
nea != bZ is 0
gta > bZ is 0, and N equals V
gea >= bN equals V
lta < bN and V differ
lea <= bZ is 1, or N and V differ

These six treat the values as signed, so -1 counts as less than 1. Four more codes, hi, hs, lo and ls, compare unsigned values instead; Binary arithmetic: flags, carries, and wide numbers covers when to reach for those.

Implementing a conditional

The C code above can be translated into assembly as follows. a_r and b_r are names for the registers w19 and w20, given with define at the top of the full program further down.

        mov     a_r, 6                          // int a = 6;        mov     b_r, 7                          // int b = 7;        cmp     a_r, b_r                        // if (a > b)        b.gt    greater_than                    // true: jump to the if part        // else part: b.gt did not jump, so execution carries on here        ldr     x0, =fmt_xy        bl      printf        // without this branch, execution falls into the if part below        b       endgreater_than:        ldr     x0, =fmt_xx        bl      printfend:

The assembly order does not have to match the order of the original if-else statement. In this example, the else block is placed first because b.gt only jumps when the condition is true. If the condition is false, execution simply continues to the next instruction, which is the else block. After completing the else block, an unconditional branch (b end) to the label end skips over the if block, so only one of the two blocks runs.

The b.gt instruction checks whether the first value being compared is greater than the second value. In this case, it reads the condition flags updated by cmp a_r, b_r to decide whether the value in a_r is greater than the value in b_r.

The whole program

The code below is the complete program, including the data section, the prologue (the lines that open main), and the epilogue (the lines that close it). Run it and it prints XY, because 6 is not greater than 7. Change the 6 to 8, run it again, and it prints XX.

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

a and b sit in w19 and w20 because printf, like any library function, is free to overwrite x0 to x18, while x19 to x28 must come back from a call unchanged. The rule runs both ways: a function that changes any of x19 to x28 must save it first and put it back before it returns. The main programs in these lessons skip that step to stay short. A subroutine you write yourself may not, as Subroutines: saved registers, pointers, and big arguments shows.

An if with no else

When there is no else, the shape is shorter, and it reads best in the same order as the C: the test, then the body, then the code after it. The trick is to branch on the opposite condition, called the complement, so that the branch skips the body whenever the C test is false.

This C caps a score at 100 before printing it:

if (score > 100) {    score = 100;}printf("%d\n", score);

In assembly the test flips from > to <=. When the score is 100 or less there is nothing to do, so b.le jumps straight past the body:

        cmp     score_r, 100                    // if (score > 100)        b.le    cap_done                        // the complement skips the body        mov     score_r, 100                    //     score = 100cap_done:        ldr     x0, =fmt_score                  // the code after the if        mov     w1, score_r        bl      printf

Complemented conditions

Every condition code has a complement that holds exactly when the first one does not. Branching into a body uses the condition itself; branching past a body uses its complement:

C testJump into the body when trueJump past the body when false
a == bb.eqb.ne
a != bb.neb.eq
a > bb.gtb.le
a >= bb.geb.lt
a < bb.ltb.ge
a <= bb.leb.gt

The if-else program above uses the middle column: it jumps into the if block when a > b. The capped score uses the right column. Both work. The right column keeps the blocks in the same order as the C, which makes a long chain easier to check against the original.

pitfall

The complement of > is <=, not <. Skipping the body with b.lt would still run it when the two values are equal, and equal is exactly the case where a > b is false and a < b is false too.

An else-if chain

An else-if chain tests several conditions in turn and runs the body of the first one that holds. This C sorts a parcel into a postage band by its weight in grams:

if (weight <= 100) {    printf("%d g: letter\n", weight);} else if (weight <= 500) {    printf("%d g: small packet\n", weight);} else if (weight <= 2000) {    printf("%d g: parcel\n", weight);} else {    printf("%d g: freight\n", weight);}

The assembly keeps the C order and uses the right column of the table. Each test branches on the complement: b.gt skips a band's body and moves on to the next test. Each body ends with b band_done, the same unconditional branch the if-else used, so exactly one band prints. The last else needs no test at all: execution only reaches try_freight when every test before it has failed.

The weight comes from one line near the top, define(WEIGHT, 750), so the program prints 750 g: parcel. Change the number and run it again to try each band. Try both sides of an edge too: 100 prints 100 g: letter, and 101 prints 101 g: small packet.

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

pitfall

Every body except the last needs its b band_done. Delete the first one, set the weight to 40, and run: the program prints 40 g: letter and then 40 g: small packet, because after the letter body, execution runs on into the next test, and 40 is also at most 500.

Check yourself

  1. After cmp w19, w20, which branch jumps when w19 and w20 hold different values?
  2. You want to skip the body of if (count >= 10). Which conditional branch goes just before the body?
  3. In the postage program, what does a weight of 2000 print, and what does 2001 print?
  4. In the if-else program, what is printed if the b end line is deleted?

answers

show answers
  1. b.ne.
  2. b.lt, the complement of >=.
  3. 2000 g: parcel and 2001 g: freight: 2000 passes the test weight <= 2000, and 2001 fails every test.
  4. XY and then XX: the else block runs, and execution falls on into the if block.

Practice