[ARM] conditional execution이란
Overview
거의 모든 ARM instruction은 conditional execution이라는 재밌는 특징을 가집니다. 이에 대해 알아봅시다.
conditional execution은 ARM의 레지스터인 CPSR의 상태에 따라 조건부로 instruction을 실행하는 것을 말합니다.
ADD r1, r2, r3
흔히 볼 수 있는 어셈블리 명령어입니다. 위의 명령어를 수행하면 r2와 r3 값을 더해서 r1에 저장하겠죠. 특별한 문제가 없다면 항상 실행될 것입니다.
ADDEQ r1, r2, r3
그렇다면 이 명령어는 어떠할까요? ADD명령어 뒤에 EQ가 붙었습니다. 이 명령어는 항상 실행되지 않습니다. 명령어를 수행하기 전 CPSR의 30 번 째 비트인 Z를 확인하고 이 값이 1인 경우에만 실행합니다.
이렇듯 CPSR 레지스터를 확인하고 조건 부로 실행하는 것을 conditional execution이라 합니다. 명령어에 접미사를 붙여서 조건부로 실행할 수 있습니다. 위에서는 EQ(Equal)를 붙였는데 그 외에도 GT(Greater Than), GE(Greater Equal), LE(Less Equal), LT(Less Than), NE(Not Equal)와 같이 다른 접미사들를 붙일 수도 있습니다. 물론 각 접미사 마다 의미하는 바는 다릅니다.
conditional execution을 사용하면 branch instruction의 수를 줄일 수 있습니다. 임베디드 시스템 같은 경우 메모리가 넉넉지 않아 코드의 양이 적은 것도 중요한데, ARM의 이러한 특징이 도움이 됩니다.
CPSR (Current Program Status Register)
CPSR에는 ALU Status Flags를 가지고 있습니다. 인스트럭션의 처리 결과에 따라 CPSR의 ALU status flags는 업데이트됩니다. 이 상태 플래그 중 우리가 봐야할건 condition code flag라 불리는 N, Z, C, V 입니다. N, Z, C, V가 의미하는 바는 다음과 같습니다.
- V (bit 28) is the overflow bit.
- C (bit 29) is the carry/borrow/extend bit.
- Z (bit 30) is the zero bit.
- N (bit 31) is the negative/less than bit.
즉, instruction의 연산 실행 후 상태가 CSPR에 남게 되는 데 연산의 결과가 음수면 N bit가 1로 아니면 N bit가 0이 됩니다. 마찬가지로, 연산 결과가 0이면 Z bit가 1이 되고 그렇지 않은 경우는 0이 됩니다. C bit는 연산 결과에 자리 올림이 발생한 경우에 set되고 V bit는 연산 결과에 오버플로가 발생할 경우 set 됩니다.
그렇다면 모든 인스트럭션이 실행될 때 마다 CPSR이 바뀌는가하면 그렇진 않습니다. CPSR의 상태를 바꾸고 싶다면 명령어에 접미사 "S"를 붙이면 됩니다. 예를들어 ADD 명령어를 status bit에 변화가 없지만 ADDS 명령어를 실행하면 status bit이 결과에 맞게 변할 것입니다. 참고로 CMP, CMN, TST, TEQ 같은 명령어에는 접미사 S를 사용하면 안됩니다. 이 명령어들은 항상 플래그를 업데이트합니다.
conditional field
ARM instruction은 어떻게 conditional execution이 가능할까요? ARM instruction에 그 비밀이 있습니다.
ARM instruction 상위 4비트는 conditional field로 사용되며 이를 이용해 conditional execution을 수행합니다.
그림에서 빨간색 박스 안에 있는 Cond가 conditional filed입니다. 보시는 것처럼 모든 instruction에 conditional field가 존재합니다.
conditional field 4비트가 의미하는 바는 다음과 같습니다.
위에서 봤던 ADDEQ 명령어의 conditional field 값은 0000입니다. 그렇다면 ADD 명령어는? ADD 명령어는 항상 실행되야 하니 접미사 AL인 1110이 들어갑니다.
이제 이들이 어떻게 사용되는지 감이 오시나요? 예제를 통해 살펴보도록 하죠!
예제
메모리에 있는 배열의 숫자를 더하는 assembly-language program 예제입니다.
먼저, 일반적인 RISC ISA에 기반한 예제를 살펴봅니다.
Load R2, N // Load count into R2
Clear R3
Move R4, #NUM1 // Load address NUM1 into R4
Loop: Load R5, (R4) // Load number into R5
Add R3, R3, R5 // Add number into R3
Add R4, R4, #4
Substract R2, R2, #1 // Decrement loop counter R2
Branch_if_[R2]>0 Loop
Store R3, SUM // Store sum
N은 NUM1 배열의 길이입니다. N번 반복해서 NUM1 배열에 저장된 값들을 모두 더하는 프로그램입니다.
NUM1은 배열의 주소입니다. 배열의 각 요소를 순차적으로 접근해서 이를 R3에 더합니다.
이를 ARM ISA 기반으로 바꾸면 어떻게 될까요? 분기문 부분을 conditional execution을 사용할 수 있습니다.
LDR R1, N // Load count into R1
LDR R2, =NUM1 // Load address NUM1 into R2
MOV R0, #0 // Clear accumulator R0
Loop: LDR R3, [R2], #4 // Load next numbner into R3
ADD R0, R0, R3 // Add number into R0
SUBS R1, R1, #1 // Decrement loop counter R1
BGT Loop // Branch back if not done.
STR R0, SUM // Store sum
6번 째 라인에 SUB가 아닌 SUBS 명령어가 있습니다. 이를 통해 CPSR의 상태를 업데이트합니다. 다음 라인의 BGT 명령어는 CPSR 레지스터의 condition code flag를 확인하고 이를 통해 분기를 할지 말지 결정합니다.
그 외 예제
if (a==0) func(1); // C code
CMP r0, #0
MOVEQ r0, #1
BLEQ func
CMP 명령어는 S를 붙이지 않아도 항상 플래그를 업데이트 합니다. r0와 0을 비교하고 이들이 같을 때만 아래의 명령어를 수행하게 됩니다.
if (a==0) x=0;
if (a>0) x=1; // C code
CMP r0, #0
MOVEQ r1, #0
MOVGT r1, #1
r0와 0을 비교하고 이 값이 같은 경우는 r1에 0을 넣고 r0과 0보다 큰 경우는 r1에 1을 넣습니다.