인라인 어셈블러

인라인 어셈블러(inline assembler)는 컴퓨터 프로그래밍에서 일부 컴파일러의 기능으로, 어셈블리어로 작성된 저수준 코드를 C 또는 에이다와 같은 고급 언어에서 컴파일된 코드 내부에 삽입할 수 있게 해준다.

 동기 및 대안 
어셈블리어 코드 삽입은 일반적으로 다음과 같은 이유 중 하나로 수행된다.

 최적화: 프로그래머는 어셈블리어 코드를 사용하여 프로그램의 알고리즘 중 성능에 가장 민감한 부분을 구현할 수 있으며, 이는 컴파일러가 생성하는 코드보다 더 효율적일 가능성이 높다.
 프로세서별 명령어에 대한 접근: 대부분의 프로세서는 비교와 교환 및 검사와 지정 명령어와 같은 특수 명령어를 제공하며, 이를 사용하여 세마포어 또는 기타 동기화 및 잠금 기본 요소를 구축할 수 있다. 거의 모든 현대 프로세서는 이러한 또는 유사한 명령어를 가지고 있는데, 이는 다중작업을 구현하는 데 필요하기 때문이다. 특수 명령어의 예로는 SPARC VIS, 인텔 MMX 및 SSE, 그리고 모토로라 Altivec 명령어 집합이 있다.
 컴파일러가 아직 지원하지 않는 특별한 호출 규약에 대한 접근.
 시스템 호출 및 인터럽트: 고급 언어는 임의의 시스템 호출을 직접 수행하는 기능을 거의 제공하지 않으므로 어셈블리 코드가 사용된다. 직접적인 인터럽트는 더욱 드물게 제공된다.
 링커나 어셈블러에 특수 지시문을 발행하여 섹션 변경, 매크로 또는 심볼 별칭을 만드는 등의 작업을 수행한다.

반면에 인라인 어셈블러는 각 변수에 대해 수행되는 작업의 분석을 복잡하게 만들어 레지스터 할당의 핵심 부분에 직접적인 문제를 제기한다. 이는 실제로 성능이 저하될 수 있음을 의미한다. 인라인 어셈블러는 또한 프로그램의 향후 포팅 및 유지 보수를 복잡하게 만든다.

컴파일러와 프로그래머 모두의 작업을 단순화하기 위한 대안적인 기능이 종종 제공된다. 특수 명령어를 위한 내장 함수는 대부분의 컴파일러에서 제공되며, 임의의 시스템 호출을 위한 C-함수 래퍼는 모든 유닉스 플랫폼에서 사용할 수 있다.

 문법 

 언어 표준에서 
ISO C++ 표준 및 ISO C 표준(부록 J)은 인라인 어셈블러에 대해 조건부로 지원되는 구문을 지정한다.
asm 선언은 다음 형식을 갖는다.
 asm-declaration:
 ( string-literal ) ;
asm 선언은 조건부로 지원되며, 그 의미는 구현 정의이다.

그러나 이 정의는 실제 C에서 거의 사용되지 않는데, 이는 동시에 너무 자유롭고(해석에 있어서) 너무 제한적이기(하나의 문자열 리터럴만 사용) 때문이다.

 실제 컴파일러에서 
실제로 값에 대해 작동하는 인라인 어셈블리는 자유롭게 떠다니는 코드로는 거의 독립적으로 존재하지 않는다. 프로그래머는 변수가 어떤 레지스터에 할당될지 예측할 수 없으므로, 컴파일러는 일반적으로 확장 기능을 통해 변수를 대체하는 방법을 제공한다.

일반적으로 C/C++ 컴파일러에서 지원하는 인라인 어셈블리에는 두 가지 유형이 있다.
 GCC의 (또는 ). GCC는 ISO 규칙의 직접적인 확장을 사용한다. 어셈블리 코드 템플릿은 문자열로 작성되며, 입력, 출력 및 클로버된 레지스터는 콜론으로 문자열 뒤에 지정된다. C 변수는 직접 사용되는 반면 레지스터 이름은 문자열 리터럴로 인용된다.
 Microsoft 비주얼 C++ (MSVC), Borland/Embarcadero C 컴파일러 및 그 파생 제품의 . 이 구문은 ISO 규칙에 전혀 기반하지 않는다. 프로그래머는 C 언어의 문법에 따를 필요 없이 블록 내부에 ASM을 작성한다. 변수는 레지스터처럼 사용 가능하며 일부 C 표현식이 허용된다. ARM 컴파일러도 유사한 기능을 가지고 있었다.

두 가지 확장 패밀리는 인라인 어셈블리를 처리하는 노동 분할에 대한 다른 이해를 나타낸다. GCC 형식은 언어의 전체 구문을 보존하고 컴파일러가 알아야 할 것(무엇이 필요하고 무엇이 변경되는지)을 구획화한다. 컴파일러는 레지스터 할당과 몇 가지 작업만 대체하여 입력 요구 사항을 처리하면 되므로, 명령어 이름을 명시적으로 이해할 것을 요구하지 않는다. 그러나 사용자가 클로버된 레지스터를 잘못 지정하기 쉽다. 포함된 도메인 특화 언어인 MSVC 형식은 작성하기 쉽지만, 컴파일러 자체가 opcode 이름과 그 클로버링 속성에 대해 알아야 하므로 유지 보수 및 포팅에 추가적인 주의가 필요하다. 명령어 집합에 대한 지식으로 GCC 스타일 어셈블리의 클로버 오류를 확인하는 것은 여전히 가능하다.

GNAT (GCC 스위트의 에이다 언어 프론트엔드)와 LLVM은 GCC 구문을 사용한다. D 프로그래밍 언어는 x86_64에 대해 공식적으로 MSVC 확장과 유사한 DSL을 사용하지만, LLVM 기반 LDC는 모든 아키텍처에서 GCC 스타일 구문도 제공한다. MSVC는 32비트 X86에서만 인라인 어셈블러를 지원한다.

러스트 언어는 LLVM (GCC 스타일) 버전보다 인라인 어셈블리 옵션을 더욱 추상화하는 구문으로 마이그레이션했다. 백엔드가 내장 어셈블리를 처리할 수 없는 경우 블록을 외부에서 어셈블된 함수로 변환할 수 있도록 충분한 정보를 제공한다.

 예시 

 GCC의 시스템 호출 

보호된 메모리를 사용하는 시스템에서는 운영 체제를 직접 호출하는 것이 일반적으로 불가능하다. OS는 사용자(사용자 모드)보다 더 특권이 있는 수준(커널 모드)에서 실행되며, 운영 체제에 요청을 하기 위해 (소프트웨어) 인터럽트가 사용된다. 이는 고급 언어에서는 드문 기능이므로, 시스템 호출을 위한 래퍼 함수는 인라인 어셈블러를 사용하여 작성된다.

다음 C 코드 예시는 GNU 어셈블러를 사용하여 AT&T 어셈블러 문법으로 작성된 X86 시스템 호출 래퍼를 보여준다. 이러한 호출은 일반적으로 매크로의 도움을 받아 작성되지만, 명확성을 위해 전체 코드가 포함되었다. 이 특정 경우에 래퍼는 호출자가 지정한 번호의 시스템 호출을 세 개의 피연산자와 함께 수행하고 결과를 반환한다.

요약하자면, GCC는 기본 어셈블리와 확장 어셈블리 모두를 지원한다. 전자는 텍스트를 어셈블러에 그대로 전달하는 반면, 후자는 레지스터 위치에 대한 일부 대체 작업을 수행한다.

extern int errno;

int syscall3(int num, int arg1, int arg2, int arg3)
{
 int res;
 __asm__ (
 "int $0x80" /* make the request to the OS */
 : "=a" (res), /* return result in eax ("a") */
 "+b" (arg1), /* pass arg1 in ebx ("b") [as a "+" output because the syscall may change it] */
 "+c" (arg2), /* pass arg2 in ecx ("c") [ditto] */
 "+d" (arg3) /* pass arg3 in edx ("d") [ditto] */
 : "a" (num) /* pass system call number in eax ("a") */
 : "memory", "cc", /* announce to the compiler that the memory and condition codes have been modified */
 "esi", "edi", "ebp"); /* these registers are clobbered [changed by the syscall] too */

/* The operating system will return a negative value on error;
 * wrappers return -1 on error and set the errno global variable */
 if (-125 <= res && res < 0) {
 errno = -res;
 res = -1;
 }
 return res;
}

 D의 프로세서별 명령어 
D 프로그래밍 언어의 인라인 어셈블리 예시는 X86의 FPU (X87) 명령어를 사용하여 x의 탄젠트를 계산하는 코드를 보여준다.

// Compute the tangent of x
real tan(real x)
{
 asm
 {
 fld x[EBP] ; // load x
 fxam ; // test for oddball values
 fstsw AX ;
 sahf ;
 jc trigerr ; // C0 = 1: x is NAN, infinity, or empty
 // 387's can handle denormals
SC18: fptan ;
 fstp ST(0) ; // dump X, which is always 1
 fstsw AX ;
 sahf ; // if (!(fp_status & 0x20)) goto Lret
 jnp Lret ; // C2 = 1: x is out of range, do argument reduction
 fldpi ; // load pi
 fxch ;
SC17: fprem1 ; // reminder (partial)
 fstsw AX ;
 sahf ;
 jp SC17 ; // C2 = 1: partial reminder, need to loop
 fstp ST(1) ; // remove pi from stack
 jmp SC18 ;
 }
trigerr:
 return real.nan;
Lret: // No need to manually return anything as the value is already on FP stack
 ;
}

 뒤에 조건 점프 관용구를 사용하여 x87 FPU 상태 워드 비트 C0 및 C2에 접근한다. 는 상태를 범용 레지스터에 저장하고, sahf는 FLAGS 레지스터를 레지스터의 상위 8비트로 설정하며, 점프는 FPU 상태 비트에 해당하는 플래그 비트를 판단하는 데 사용된다.

 각주 

 외부 링크 
 GCC-Inline-Assembly-HOWTO
 Clang 인라인 어셈블리
 GNAT 인라인 어셈블러
 GCC 인라인 어셈블러 참조
 컴파일러 익스플로러

분류:어셈블리어