Inline-Assembly

인라인 어셈블리의 문법은 다음과 같으며, ARM 아키텍처를 위주로 설명한다.

     asm asm-qualifiers ( AssemblerTemplate
                      : OutputOperands
                      [ : InputOperands
                      [ : Clobbers ] ])
     
     asm asm-qualifiers ( AssemblerTemplate
                           : OutputOperands
                           : InputOperands
                           : Clobbers
                           : GotoLabels)

 

Qualifiers

volatile

  • gcc는 성능 향상(optimization)을 목적으로  상황에 따라 명령을 무시하거나 명령 위치를 변경할 수 있는데 asm 명령 다음에 volatile을 사용하면 optimizer가 asm() 문장 전체를 재배치하지 못하도록 한다.

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 범위의 상수
      • “m”
        • C-expression이 유효 메모리 주소이어야 한다.
      • “[digits]”
        • Input Operands에 사용되며 Output Operands의 순서와 똑같은 항목을 지칭한다. 이렇게 하면 컴파일러의 optimization이 Output Operands의 값이 수정된 것 처럼 속인다.
          • 예) __asm__ ( : =r(__ptr) : 0(ptr));
    • 메모리 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);
    • 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)
  • (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 레지스터를 사용할 수 있다.
  • 예) “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인 “=”을 붙인다.

 

참고

댓글 남기기