archiveFeb 9, 2021
ARM conditional execution
How ARM and Thumb update CPSR flags, which condition codes exist, and how predicated instructions shrink Euclid's GCD compared with branch-heavy code.
ARM conditional execution
Conditional execution
In ARM state, each data-processing instruction can optionally update the ALU status flags in the CPSR (Current Program Status Register) from its result. Append the S suffix to make that happen.
Do not put S on CMP, CMN, TST, or TEQ. Those compare instructions always update the flags.
Thumb state has no such option. Except for MOV and ADD when one or more high registers are involved, every data-processing instruction updates the CPSR ALU flags. In those high-register cases, MOV and ADD cannot update the flags.
Behavior in ARM state
- Update the CPSR ALU flags from a data operation's result.
- Run other data operations without touching the flags.
- Execute or skip later instructions based on the flags from the first operation.
In Thumb state, most data ops always update flags, and conditional execution is limited to the conditional branch B. The branch suffix matches ARM state; other instructions cannot be predicated.
ALU status flags
The CPSR holds the ALU status flags. Branches follow those condition flags. Every ARM instruction carries a condition field that decides whether it runs.

- N flag: set when the result is negative
- Z flag: set when the result is zero
- C flag: set when the operation produces a carry — add result ≥ 2^32, subtract result positive, or carry out of an inline barrel-shifter on a move/logical instruction
- V flag: set on overflow — add/subtract/compare result greater than 2^31 or less than -2^31
- Q flag: sticky flag introduced in ARM architecture v5E
접미사 플래그 의미
EQ Z Set 같은
NE Z clear 같지 않음
CS/HS C Set 높거나 같음 (Unsigned> =)
CC/LO C clear 낮은 (Unsigned <)
MI N Set 부정
PL N clear 양수 or 0
VS V Set Overflow
VC V clear No Overflow
HI C set and Z clear 더 높음 (Unsigned>)
LS C clear or Z set 낮거나 같음 (Unsigned <=)
GE N and V the same Signed > =
LT N and V differ Signed <
GT Z clear N and V the same Signed >
LE Z set N and V differ Signed <=
AL Any 항상. 이 접미사는 일반적으로 생략됨| Suffix | Flags | Meaning |
|---|---|---|
| EQ | Z set | Equal |
| NE | Z clear | Not equal |
| CS/HS | C set | Higher or same (unsigned ≥) |
| CC/LO | C clear | Lower (unsigned <) |
| MI | N set | Negative |
| PL | N clear | Positive or zero |
| VS | V set | Overflow |
| VC | V clear | No overflow |
| HI | C set and Z clear | Higher (unsigned >) |
| LS | C clear or Z set | Lower or same (unsigned ≤) |
| GE | N and V the same | Signed ≥ |
| LT | N and V differ | Signed < |
| GT | Z clear, N and V the same | Signed > |
| LE | Z set or N and V differ | Signed ≤ |
| AL | Any | Always (usually omitted) |
ADD r0, r1, r2 ; r0 = r1 + r2, don't update flags
ADDS r0, r1, r2 ; r0 = r1 + r2, and update flags
ADDCSS r0, r1, r2 ; If C flag set then r0 = r1 + r2, and update flags
CMP r0, r1 ; update flags based on r0-r1.Conditional execution in ARM state
Predicated ARM instructions cut the number of branches and improve code density. Branches are expensive in cycles too. On ARM processors without branch prediction, filling the pipeline after a taken branch typically costs about three cycles.
Some cores (ARM10, StrongARM, and similar) do have branch prediction. On those, you only flush and refill when the prediction is wrong.
Conditional execution exercises
Two implementations of Euclid's GCD show how predication improves density and speed. The cycle counts below apply to ARM7.
int gcd(int a, int b)
{
while(a != b) do
{
if (a > b)
a -= b;
b =- a;
}
return a;
}GCD with conditional branches:
gcd CMP r0, r1
BEQ end
BLT less
less
SUB r0, r0, r1
B gcd
endSeven instructions, mostly because of the branches. Each taken branch forces a pipeline refill. Other instructions and untaken branches cost one cycle each.
With ARM predication, the same function fits in four instructions:
gcd
CMP r0, r1
SUBGT r0, r0, r1
SUBLT r1, r1, r0
BNE gcdBesides the smaller footprint, this usually runs faster. For r0=1, r1=2, replacing branches with predicated ops saves three cycles:
r0: a r1: b Instruction Cycles (ARM7)
1 2 CMP r0, r1 1
1 2 BEQ end 1 (not executed)
1 2 BLT less 3
1 2 SUB r1, r1, r0 1
1 2 B gcd 3
1 1 CMP r0, r1 1
1 1 BEQ end 3
Total = 13
r0: a r1: b Instruction Cycles (ARM7)
1 2 CMP r0, r1 1
1 2 SUBGT r0,r0,r1 1 (not executed)
1 1 SUBLT r1,r1,r0 1
1 1 BNE gcd 3
1 1 CMP r0,r1 1
1 1 SUBGT r0,r0,r1 1 (not executed)
1 1 SUBLT r1,r1,r0 1 (not executed)
1 1 BNE gcd 1 (not executed)
Total = 10Exercise 2:
loop
...
SUBS r1, r1, #1
BNE loopBecause of the S suffix, the SUB result updates CPSR. When Z is set, the loop stops.
Exercise 3:
if (a1 == 0) func(1);
CMP r0, #0
MOVEQ r0, #1
BLEQ func
Exercise 4:
if (a2 == 0) x = 0;
if (a2 > 0) x = 1;
CMP r0, #0
MOVEQ r1, #0
MOVGT r2, #1Exercise 5:
if (a==4 || a==10) x=0;
CMP r0, #4
CMPNE r0, #10
MOVEQ r1, #0Calling convention
- Arguments go in
r0–r3in order. - More than four arguments spill to the stack.
BLstores the return address inLR.- The return value comes back in
r0.
Thumb conversion
In Thumb, B is the only instruction that can run conditionally, so GCD must use conditional branches. Like the ARM branch version, that needs seven instructions. Each Thumb instruction is 16 bits, so the whole sequence is 14 bytes.
On a 16-bit memory system, each Thumb instruction needs one fetch, while each ARM instruction needs two. In that setup the Thumb version can outrun the second ARM implementation.