Gravitational plate of three masses and a slashed discABC0Static engraved plate. Three-dimensional view is unavailable or reduced motion is requested.

← back to fieldarchive

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.

./ARM-CPSR.png
./ARM-CPSR.png

  • 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	                        항상. 이 접미사는 일반적으로 생략됨
SuffixFlagsMeaning
EQZ setEqual
NEZ clearNot equal
CS/HSC setHigher or same (unsigned ≥)
CC/LOC clearLower (unsigned <)
MIN setNegative
PLN clearPositive or zero
VSV setOverflow
VCV clearNo overflow
HIC set and Z clearHigher (unsigned >)
LSC clear or Z setLower or same (unsigned ≤)
GEN and V the sameSigned ≥
LTN and V differSigned <
GTZ clear, N and V the sameSigned >
LEZ set or N and V differSigned ≤
ALAnyAlways (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
end

Seven 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     gcd

Besides 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 = 10

Exercise 2:

loop
	...
	SUBS         r1, r1, #1
	BNE          loop

Because 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, #1

Exercise 5:

if (a==4 || a==10) x=0;
 
CMP     r0, #4
CMPNE   r0, #10
MOVEQ   r1, #0

Calling convention

  • Arguments go in r0r3 in order.
  • More than four arguments spill to the stack.
  • BL stores the return address in LR.
  • 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.

related

  1. Feb 10, 2021/archiveARM: Loading Addresses into Registers
  2. Feb 10, 2021/archiveARM: loading constants into registers
  3. Feb 10, 2021/archiveARM Data Transfer: Pre/Post-Index and STR/LDR

graphfeed