인라인 어셈블리의 문법은 다음과 같으며, ARM 아키텍처를 위주로 설명한다.
asm asm-qualifiers ( AssemblerTemplate : OutputOperands [ : InputOperands [ : Clobbers ] ]) asm asm-qualifiers ( AssemblerTemplate : OutputOperands : InputOperands : Clobbers : GotoLabels)
Qualifiers
volatile
- gcc는 성능 향상(optimization)을 목적으로 상황에 따라 명령을 무시하거나 명령 위치를 변경할 수 있는데 asm 명령 다음에 volatile을 사용하면 optimizer가 asm() 문장 전체를 재배치하지 못하도록 한다.
- 참고: Volatile | 문c
inline
- inlining을 목적으로 최대한 사이즈를 작게 만든다.
goto
- 어셈블리 코드안에서 C에서 사용하는 라벨로 점프할 수 있게 한다.
Parameters(파라메터)
AssemblerTemplate(코드)
- 어셈블리 코드 문자열
- input/output 오퍼랜드와 %l(goto) 파라메터와 조합하여 사용한다.
- 예) ARM
- “mov r0, r0”
- “mov %0, #10”
- “ldr %0, [%1]”
- “bne %l[err1]”
- 어셈블리 명령 끝 처리
- “\n” 또는 “\n\t”을 사용하여 여러 개의 명령을 사용한다.
- 세미콜론(;)을 사용하여 명령을 구분할 수도 있는데 컴파일러에 따라 다르므로 “\n\t” 등이 권장된다.
- 예) ARM
- “mov r0, r0\n\t” “mov r1, r1”
- “mov r0, r0; mov r1, r1;”
- “%%”
- “%” Input/Output 오퍼랜드에서 사용하는 “%”와 혼동을 피하기 위하여 어셈블리 코드내에 x86 어셈블리와 같이 “%” 문자열을 사용해야 하는 경우 사용한다.
- 예) x86
- “movl %%eax, %0”
- “%{“, “%|” “%}”
- “%%”와 동일하게 AsmemblerTemplate 내에서 “{“, “|”, “}” 문자열을 사용하기 위함이다.
- Input/Output Operands 사용
- %n: n번째 인수에 매핑된 arm 32bit 레지스터를 지정한다.
- %Qn: n번째 64비트 인수 중 하위 비트에 매핑된 ARM 32bit 레지스터를 지정한다.
- %Rn: n번째 64비트 인수 중 상위 비트에 매핑된 ARM 32bit 레지스터를 지정한다.
- %Hn: n번째 64비트 인수 중 매핑된 2 개의 ARM 32bit 레지스터 중 레지스터 번호가 높은 레지스터를 지정한다.
- 예) ARM
- “mov %0, #10”
- “mov %Q0, %1, %2”
- “mov %R0, %R0, #0”
- “ldrd %H0, [%1]”
- %[foo]
- Input/Output Operands에서 %n과 같이 인덱스 번호를 직접 사용하지 않고 이름을 지정할 수 있다.
Input Operands(입력 인수) & Output Operands(출력 인수)
형식: [ [asmSymbolicName] ] constraint (C-expression)
- AssemblerTemplate(code)에 있는 명령에 의해 C 변수들에서(로) 입력/출력된다.
- 빈 항목도 허용한다.
- [ [asmSymbolicName] ]
- 생략 가능하고 지정하는 경우 %0, %1, …과 같이 인수의 순번을 사용하는 대신 심볼명을 사용할 수도 있다.
예) %n 스타일
uint32_t c = 1; uint32_t d; uint32_t *e = &c; asm ("mov %1, %0" : "=rm" (d) : "rm" (*e));
예) %[name] 스타일 – asmSymbolicName 사용
uint32_t c = 1; uint32_t d; uint32_t *e = &c; asm ("mov %[e], %[d]" : [d] "=rm" (d) : [e] "rm" (*e));
- constraint
- 자주 사용하는 constraint 항목
- “r”
- C-expression을 범용 레지스터에 배정한다.
- “I”~”P”
- Immediate 위치로 C-expression이 상수이어야 한다.
- 아키텍처마다 상수 표현 크기가 다르다.
- ARM:
- “I”: 0~255 값을 2의 차수(2^N) 단위로 만들 수 있는 상수 값
- 예) 0x81(o), 0x8100_0000(o), 0x101
- “J”: -4095~4095 범위의 상수
- “I”: 0~255 값을 2의 차수(2^N) 단위로 만들 수 있는 상수 값
- “m”
- C-expression이 유효 메모리 주소이어야 한다.
- “[digits]”
- Input Operands에 사용되며 Output Operands의 순서와 똑같은 항목을 지칭한다. 이렇게 하면 컴파일러의 optimization이 Output Operands의 값이 수정된 것 처럼 속인다.
- 예) __asm__ (““ : “=r“(__ptr) : “0“(ptr));
- Input Operands에 사용되며 Output Operands의 순서와 똑같은 항목을 지칭한다. 이렇게 하면 컴파일러의 optimization이 Output Operands의 값이 수정된 것 처럼 속인다.
- “r”
- 메모리 access용 clobber를 지정할 수 있는 constraint 항목
- “Q”, “Qo”
- ARM clobber for memory
- C-expression은 단일 레지스터에서 유효한 메모리 레퍼런스 주소이다.
- gcc의 ARM용 clobber for memory로 input/output 오퍼랜드에서 메모리 access를 위해 사용한다.
- ARM에서는 메모리 영역을 access 할 경우 clobber lists에서 “memory” 대신 “Q”를 사용한다.
- “Qo”: optimization이 추가되어 코드가 일부 생략된다.
- 보통 메모리 주소를 가리키는 레지스터 즉 “r”레지스터가 별도로 사용되면서 “r”과 “Q”에 대해 각각의 레퍼런스를 계산하기 위한 코드가 사용된다. 만일 “r”과 “Q”에서 사용되는 메모리 주소가 서로 같은 곳을 보는 경우 “Qo”를 사용하면 한 번의 계산을 생략할 수 있다.
- 예) 아래와 같이 v->counter의 위치가 서로 같은 경우 “Qo”를 사용하여 코드를 절약할 수 있다.
- asm (“ldrd %0, %H0, [%1]” : “=&r” (result) : “r” (&v->counter), “Qo” (v->counter);
- “Q”, “Qo”
- constraint modifiers
- “=”
- OutputOperands에서 쓰기(write only)만 가능하다.
- “+”
- OutputOperands에서 읽고(read) 쓰기(write)가 가능하다.
- “&”
- early clobber modifier
- OutputOperands에서 레지스터 할당 순서를 먼저 할 수 있도록 요청한다.
- 보통 input operands에 레지스터를 할당하고 그 후 output operands의 레지스터를 사용하기 때문에 input operands에서 사용했던 레지스터를 output operands 레지스터로 배치하는 경우도 생기는데 그러면서 문제가 될 수 있는 곳에 “&”를 사용한다.
- 보통 Output operands에 “&”를 사용하여 먼저 하나의 레지스터를 할당받아 사용하면서 다른 레지스터로 사용될 일을 막는다.
- “=”
- 사용 예)
- “=r”
- 쓰기만 하는 목적으로 해당 C-expression을 범용 레지스터에 배정한다.
- “+rm”
- 메모리 주소를 읽고 쓰는 목적으로 해당 C-expression을 범용 레지스터에 배정한다.
- “Ir”
- immediate 오퍼랜드 위치에서 사용하기 위하여 해당 C-expression을 범용 레지스터에 배정한다.
- “=&r”
- 쓰기만 하는 목적으로 해당 C-expression을 범용 레지스터에 먼저(early) 배정한다.
- “+r”
- 읽고 쓰는 목적으로 C-expression을 범용 레지스터에 배정한다.
- “+Qo”
- 읽고 쓰는 목적으로 C-expression을 메모리에 배정한다. (ARM clobber for memory)
- “=r”
- 자주 사용하는 constraint 항목
- (C-expression)
- C 표현이 가능하다.
- 예)
- (var+10)
- (*var)
- (&var)
Clobbers
- AssemblerTemplate 에 의해 변경되는 레지스터나 값들이다.
- 즉 InputOperands 와 OutputOperands가 C로 부터 영향을 받거나 주는 경우를 지정하였지만 Clobbers는 어셈블리 코드에서 영향을 주는 것을 의미한다.
- “cc”
- 플래그 레지스터를 수정할 수 있다.
- “memory”
- 메모리 주소를 변경시킬 수 있다.
- input/output operands에서 “Q” 또는 “Qo”를 사용하여 해당 항목에 사용할 수 있다. (최신 방법)
- “r7”
- AssemblerTemplate 내에서 r7 레지스터를 사용한다고 지정한다. 만일 asm() 사용 전에 r7 레지스터를 사용한 경우 컴파일러가 이의 사용을 하지 않도록 하여 사용자가 AssemblerTemplate 내에서 안전하게 r7 레지스터를 사용할 수 있다.
- “cc”
- 예) “r9”, “cc”, “memory”
- 어셈블리 코드로부터 r9 레지스터, 플래그 레지스터, 메모리가 수정되는 경우이다.
Goto Labels
- 라벨로 점프를 하는 기능이며, 이 기능을 사용할 경우 OutputOperands를 사용할 수 없으므로 비워둬야 한다.
goto.c
#include <stdio.h> int sub(int cnt) { int x = 0; asm goto ( "mov %0, %1\n\t" "cmp %0, #10\n\t" "bhi %l[err2]\n\t" "1: subs %0, #1\n\t" "bne 1b" : : "r" (x), "r" (cnt) : "cc" : err2); printf("cnt=%d\n", cnt); return x; err2: printf("err: cnt=%d\n", cnt); return x; } int main() { sub(5); sub(15); } $ ./goto cnt=5 err: cnt=15
기타
Clobber for Memory (“Q”)
- Q”는 “memory”를 대신하여 사용되는 오퍼랜드 항목의 clobber for memory 이다.
qo.c
#include <stdio.h> int loop5(int * addr) { int i; int tmp = 0; for (i = 0; i < 10; i++) { asm ("add %0, #2\n str %0, [%2]" : "=r" (tmp), "=Qo" (*addr) : "r" (addr)); } return tmp; } int loop6(int * addr) { int i; int tmp = 0; for (i = 0; i < 10; i++) { asm volatile ("add %0, #2\n str %0, [%2]" : "=r" (tmp), "=Qo" (*addr) : "r" (addr)); } return tmp; } int main() { int l5 = 1; int l6 = 1; loop5(&l5); loop6(&l6); }
- 위의 소스와 같이 “memory” clobber를 사용하지 않고 “Qo”를 사용하여 구현한 예이다.
- 메모리에 읽거나 쓰는 데이터는 레지스터가 아닌 값을 의미하므로 addr가 아닌 *addr이 된다.
- input operands에 사용된 addr은 읽기 용도로 변경되지 않으며 output operands에 사용된 *addr 값이 메모리에 기록되는 값이므로 “Qo” 앞에 기록 전용의 modifier인 “=”을 붙인다.