Conditionals with cmp and b.cond
prerequisite
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:
- Compare two values using the
cmpinstruction. - 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:
| Code | Branches when | Flags it reads |
|---|---|---|
eq | a == b | Z is 1 |
ne | a != b | Z is 0 |
gt | a > b | Z is 0, and N equals V |
ge | a >= b | N equals V |
lt | a < b | N and V differ |
le | a <= b | Z 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.
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 printfComplemented 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 test | Jump into the body when true | Jump past the body when false |
|---|---|---|
a == b | b.eq | b.ne |
a != b | b.ne | b.eq |
a > b | b.gt | b.le |
a >= b | b.ge | b.lt |
a < b | b.lt | b.ge |
a <= b | b.le | b.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.
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.
pitfall
Common mistakes from this lesson, each with a broken program and its fix that you can run:
Check yourself
- After
cmp w19, w20, which branch jumps whenw19andw20hold different values? - You want to skip the body of
if (count >= 10). Which conditional branch goes just before the body? - In the postage program, what does a weight of 2000 print, and what does 2001 print?
- In the if-else program, what is printed if the
b endline is deleted?
answers
show answers
b.ne.b.lt, the complement of>=.2000 g: parceland2001 g: freight: 2000 passes the testweight <= 2000, and 2001 fails every test.XYand thenXX: theelseblock runs, and execution falls on into theifblock.
Practice
- The absolute truth: an if with no else that flips the sign of a negative number.
- Pick the bigger one: one comparison decides which of two values to print.
- Rock, paper, scissors referee: an if-else chain that names the winner of one round.
- Basic quiz: branching: what
cmpleaves in the flags, complemented conditions, and signed against unsigned. Itscbzandcselquestions come with Decisions in one instruction: csel, cinc, and cbz, three lessons on.