C 코드에서 인라인 어셈블리의 어셈블리 명령 템플릿을 사용하는 방법

어셈블리 지침 템플릿
어셈블리 템플릿 :
어셈블러 템플릿은 어셈블러 지침이 포함 된 텍스트 문자열입니다.
컴파일러는 템플릿에서 입력, 출력 및 goto 태그를 참조하는 태그를 대체 한 다음 생성 된 문자열을 어셈블러에 출력합니다.
문자열에는 인디케이터를 포함하여 어셈블러에서 인식하는 모든 명령어가 포함될 수 있습니다.
GCC는 어셈블러 명령어 자체를 분석하지 않으며, 그 의미 나 유효한 어셈블러 입력인지 여부도 알지 못합니다.
시스템 어셈블리 코드에서 일반적으로 사용되는 문자로 구분 된 asm 문자열에 여러 어셈블러 명령어를 넣을 수 있습니다.
대부분의 위치에서 작동하는 조합은 개행 문자와 지침 필드로 이동하는 탭 ( '\ n \ t'로 작성)입니다.
일부 어셈블러에서는 세미콜론을 줄 구분 기호로 허용합니다. 그러나 일부 어셈블리 언어는 세미콜론을 사용하여 주석을 시작합니다.
휘발성 한정자가 사용 된 경우에도 어셈블리 후에 asm 문 시퀀스가 ​​완전히 연속적으로 유지 될 것으로 기대하지 마십시오.
일부 명령어가 출력에서 ​​연속적이어야하는 경우 다중 명령어 asm 문에
입력하십시오. 입력 / 출력 피연산자를 사용하지 않는 경우 (예 : 어셈블러 템플릿에서 직접 전역 기호 사용) C 프로그램에서 데이터에 액세스 할 수 있습니다. 예상대로 작동하지 않습니다.
마찬가지로 어셈블러 템플릿에서 직접 함수를 호출하려면 대상 어셈블러와 ABI에 대한 자세한 이해가 필요합니다.
GCC는 어셈블러 템플릿을 구문 분석하지 않기 때문에 참조 된 기호에 표시되지 않습니다.
이로 인해 GCC는 이러한 기호가 입력, 출력 또는 goto 피연산자로 나열되지 않는 한 참조되지 않은 것으로 버릴 수 있습니다.

특수 형식 문자열 :
입력, 출력 및 goto 피연산자가 설명하는 토큰 외에도 다음 토큰은 어셈블러 템플릿에서 특별한 의미를 갖습니다.
'%%':
어셈블리에 단일 '%'출력 프로그램 코드에서.
'% =':
컴파일 프로세스 전체에서 asm 문의 각 인스턴스에 고유 한 번호를 출력합니다
.이 옵션은 여러 어셈블러 명령어를 생성하고 여러 번 인용하는 단일 템플릿에 로컬 태그를 만들 때 매우 유용합니다.
'% { ':
'% | ':
'%} ':
그에 따라'{ ','| and '}'를 어셈블러 코드에 출력합니다.

asm 템플릿의 여러 어셈블리 언어 :

출력 피연산자 :
asm 문에는 어셈블리 코드에 의해 수정 된 C 변수의 이름을 나타내는 0 개 이상의 출력 피연산자가 있습니다.
예 :
이 i386 예에서 old (템플릿 문자열에서 % 0이라고 함) 및 * Base (% 1)가 출력이고 오프셋 (% 2)이 입력입니다.
bool old;

		__asm__ ("btsl %2,%1\n\t" // Turn on zero-based bit #Offset in Base.
				 "sbb %0,%0"      // Use the CF to calculate old.
		   : "=r" (old), "+rm" (*Base)
		   : "Ir" (Offset)
		   : "cc");

		return old;

출력 피연산자의 구문 형식 :
피연산자는 쉼표로 구분됩니다.
제약 조건
|
V
[[asmSymbolicName]] 제약 조건 (cvariablename)

asmSymbolicName:

피연산자의 기호 이름을 지정하십시오.
이름을 대괄호로 묶어 어셈블러 템플릿에서 이름을 참조하십시오 (예 : '% [Value]').
이름의 범위는 이름을 정의하는 asm 문을 수용하는 것입니다.
주변 코드에 이미 정의 된 이름을 포함하여 유효한 C 변수 이름을 사용할 수 있습니다.
동일한 asm 문에있는 두 개의 피연산자는 동일한 기호 이름을 사용할 수 없습니다.
asmSymbolicName을 사용하지 않는 경우 어셈블러 템플릿의 피연산자 목록에서 피연산자의 위치 (0부터 시작)를 사용하십시오.
예를 들어 세 개의 출력 피연산자가있는 경우 '% 0'을 사용하여 첫 번째 피연산자를 참조하고 '% 1'을 사용하여 두 번째 피연산자를 참조하고 '% 2'를 사용하여 템플릿에서 세 번째 피연산자를 참조합니다.

constraint:

피연산자의 위치 제약 조건을 지정하는 문자열 상수입니다.
출력 제약 조건은 '='(기존 값을 덮어 쓰는 변수) 또는 '+'(읽기 및 쓰기 용)로 시작해야합니다.
'='를 사용할 때 피연산자가 입력에 바인드되지 않는 한 위치가 asm 항목의 기존 값을 포함한다고 가정하지 마십시오.
접두어 뒤에 값의 위치를 ​​설명하는 하나 이상의 추가 제약이 있어야합니다.
레지스터와 m의 경우 : 일반적인 제약 R 등이 있습니다. 메모리를
여러 가능한 위치를 나열하는 경우 (예 : "= RM"), 컴파일러는 현재의 상황에 따라 가장 효과적인 위치를 선택합니다.
만약을 asm 문의 범위 내에서 가능한 한 많은 대안을 나열하여 최적기가 최상의 코드를 생성 할 수 있도록합니다.
특정 레지스터를 사용해야하지만 시스템 제약 조건이 원하는 특정 레지스터를 선택하기에 충분한 제어를 제공하지 않는 경우 로컬 레지스터 변수가 솔루션을 제공 할 수 있습니다.

cvariablename:

출력을 저장할 C lvalue 표현식 (일반적으로 변수 이름)을 지정하십시오.
괄호는 문법의 필수 부분입니다.

输出操作数表达式必须是左值。使用“+”约束修饰符的操作数被计算为两个操作数(即同时作为输入和输出),每个asm语句的操作数最多为30个

입력과 겹칠 수없는 모든 출력 피연산자에 대해 '&'제약 조건 수정자를 사용합니다.
그렇지 않으면 어셈블리 코드가 출력을 생성하기 전에 입력을 사용하는 경우 GCC는 출력 피연산자를 동일한 레지스터의 관련없는 입력 피연산자로 할당 할 수 있습니다. 어셈블러 코드에 실제로 두 개 이상의 명령어가 포함 된 경우이 가정은 잘못된 것일 수 있습니다.
하나의 출력 매개 변수 (a)가 레지스터 제한을 허용하고 다른 출력 매개 변수 (b)가 메모리 제한을 허용하는 경우 동일한 문제가 발생합니다.
b의 메모리 주소에 액세스하기 위해 GCC에 의해 생성 된 코드는 a가 공유 할 수있는 레지스터를 포함 할 수 있으며 GCC는 이러한 레지스터를 asm의 입력으로 간주합니다.
위에서 언급했듯이 GCC는 출력을 쓰기 전에 이러한 입력 레지스터를 사용해야한다고 가정합니다.
이 가정은 b를 사용하여 asm 문을 작성하는 경우 잘못된 동작을 유발할 수 있습니다. 수정이 주소 참조에 영향을주지 않도록 "&"수정 자와 레지스터 제약 조건을 결합합니다. b. 그렇지 않으면 b를 사용하기 전에 수정하면 b는 정의되지 않은 위치입니다.
asm은 피연산자에 대한 피연산자 수정자를 지원합니다 (예 : 단순히 "% 2"대신 "% k2"). 일반적으로 이러한 한정자는 하드웨어에 따라 다릅니다.
asm 뒤에있는 C 코드가 출력 피연산자를 사용하지 않는 경우 volatile for asm 문을 사용하여 최적화 프로그램
필요하지 않을 때 asm 문을 삭제하지 않도록합니다. 이 코드는 선택적 asmSymbolicName을 사용하지 않습니다. 따라서 첫 번째 출력 피연산자를 % 0 (또는 두 번째 피연산자가있는 경우 % 1 등)으로 참조합니다. 첫 번째 입력 피연산자의 수가 마지막 출력 피연산자의 수보다 큽니다. 이 i386 예제에서는 마스크 참조를 % 1로 만듭니다.
uint32_t Mask = 1234;
uint32_t Index;

	  asm ("bsfl %1, %0"
		 : "=r" (Index)
		 : "r" (Mask)
		 : "cc");

该代码覆盖变量Index(' = '),将该值放在寄存器(' r ')中。

특정 레지스터 제약 조건 대신 일반 "r"제약 조건을 사용하면 컴파일러에서 사용할 레지스터를 선택할 수 있으므로보다 효율적인 코드를 생성 할 수 있습니다.
어셈블러 명령어에 특정 레지스터가 필요한 경우 이는 불가능할 수 있습니다.
아래의 i386 예제는 asmSymbolicName 구문을 사용합니다.
위의 코드와 동일한 결과를 생성하지만 어떤 사람들은 피연산자를 추가하거나 제거 할 때 인덱스 번호를 다시 정렬 할 필요가 없기 때문에 읽기가 더 쉽고 유지 관리가 더 쉽다고 생각할 수 있습니다.
aIndex 및 aMask라는 이름은이 예제에서 사용되는 이름을 강조하기 위해서만 사용됩니다. 이름 인덱스와 마스크는 재사용 할 수 있습니다.
uint32_t 마스크 = 1234;
uint32_t 인덱스;

	  asm ("bsfl %[aMask], %[aIndex]"
		 : [aIndex] "=r" (Index)
		 : [aMask] "r" (Mask)
		 : "cc");



下面是一些输出操作数的示例。
	uint32_t c = 1;
	uint32_t d;
	uint32_t *e = &c;

	asm ("mov %[e], %[d]"
	   : [d] "=rm" (d)
	   : [e] "rm" (*e));		

这里,d可以在寄存器中,也可以在内存中。
由于编译器可能已经在寄存器中有e所指向的uint32_t位置的当前值,所以可以通过指定这两个约束来让它为d选择最佳位置。

입력 피연산자 :
입력 피연산자는 C의 변수 및 표현식 값을 어셈블리 코드에 사용할 수 있도록합니다.
피연산자를 구분하려면 쉼표를 사용하십시오.
입력 피연산자의 구문 형식 :
[[asmSymbolicName]] 제약 조건 (cexpression)

asmSymbolicName:

피연산자에 대한 기호 이름을 지정하십시오.
이름을 대괄호로 묶어 어셈블러 템플릿에서 이름을 참조하십시오 (예 : '% [Value]').
이름의 범위는 이름을 정의하는 asm 문을 포함하는 것입니다.
주변 코드에 이미 정의 된 이름을 포함하여 유효한 C 변수 이름을 사용할 수 있습니다.
동일한 asm 문에있는 두 개의 피연산자는 동일한 기호 이름을 사용할 수 없습니다.
asmSymbolicName을 사용하지 않는 경우 어셈블러 템플릿의 피연산자 목록에서 피연산자의 위치 (0부터 시작)를 사용하십시오.
예를 들어, 두 개의 출력 피연산자와 세 개의 입력이있는 경우 '% 2'를 사용하여 템플릿의 첫 번째 입력 피연산자를 참조하고 '% 3'을 사용하여 두 번째를 참조하고 '% 4'를 사용하여 세 번째를 참조합니다.
constraint :
피연산자의 위치 제약을 지정하는 문자열 상수입니다.
입력 제약 문자열은 '='또는 '+'로 시작할 수 없습니다.
가능한 여러 위치 (예 : "irm")를 나열하면 컴파일러가 현재 컨텍스트에 따라 선택합니다. 가장 효과적인 위치.
특정 레지스터를 사용해야하지만 시스템 제약 조건이 원하는 특정 레지스터를 선택하는 데 충분한 제어를 제공하지 않는 경우 로컬 레지스터 변수가 솔루션을 제공 할 수 있습니다.
입력 제약 조건은 숫자 (예 : "0") 일 수도 있습니다. 이는 지정된 입력이 출력 제약 목록의 인덱스 (0부터 시작)에서 출력 제약과 동일한 위치에 있어야 함을 의미합니다. 출력 피연산자에 asmSymbolicName 구문을 사용할 때 숫자 대신 이러한 이름 (괄호 "[]"로 묶임)을 사용할 수 있습니다.
cexpression :
asm 문에 입력으로 전달 된 C 변수 또는 표현식입니다. 대괄호는 문법의 필수 부분입니다.
출력 피연산자가 없지만 입력 피연산자가있는 경우 출력 피연산자의 위치에 두 개의 연속 콜론을 배치합니다.
asm( "일부 명령"
: / * 출력 없음. * /
: "r"(오프셋 / 8));

警告:不要修改仅输入操作数的内容(与输出绑定的输入除外)。编译器假设从asm语句中退出时,这些操作数包含与执行语句之前相同的值。
不可能使用clobbers通知编译器这些输入中的值正在更改。一种常见的解决方法是将正在更改的输入变量绑定到从未使用过的输出变量。

그러나 asm 문 다음에 나오는 코드가 출력 피연산자를 사용하지 않는 경우 GCC 최적화 프로그램은 asm 문을 불필요한 문으로 폐기 할 수 있습니다 (Volatile 참조).
이 예에서는 입력 작업에 가상의 결합 명령이 사용됩니다. 숫자 1의 제약 조건 "0"은 출력 피연산자 0과 동일한 위치를 차지해야 함을 의미합니다. 입력 피연산자 만 제약 조건에서 숫자를 사용할 수 있으며 각각 출력 피연산자를 참조해야합니다.
제약 조건에서 하나의 숫자 (또는 기호 어셈블러 이름) 만 한 피연산자가 다른 피연산자와 같은 위치에 있음을 보장 할 수 있습니다.
foo가 두 피연산자의 값이라는 사실만으로는 생성 된 어셈블리 코드에서 동일한 위치에 있음을 보장 할 수 없습니다.
asm ( "결합 % 2, % 0"
: "= r"(foo)
: "0"(foo), "g"(bar));

下面是一个使用符号名称的示例
	asm ("cmoveq %1, %2, %[result]" 
	   : [result] "=r"(result) 
	   : "r" (test), "r" (new), "[result]" (old));

클로버 및 스크래치 레지스터 :
컴파일러는 출력 피연산자에 나열된 항목의 변경 사항을 알고 있지만 인라인 asm 코드는 출력 이상의 내용을 수정할 수 있습니다.
예를 들어 계산에는 추가 레지스터가 필요하거나 특정 어셈블리 명령어의 부작용으로 필요할 수 있습니다. 프로세서가 레지스터를 덮어 쓸 수 있습니다.
이러한 변경 사항을 컴파일러에 알리려면 clobber 목록에 나열하십시오.
Clobber 목록 항목은 레지스터 이름 또는 특수 Clobber (아래에 표시된대로) 일 수 있습니다. 각 clobber 목록 항목은 큰 따옴표로 묶고 쉼표로 구분 된 문자열 상수입니다.
Clobber 설명은 어떤 식 으로든 입력 또는 출력 피연산자와 겹칠 수 없습니다.
또 다른 제한은 clobber 목록에 스택 포인터 레지스터를 포함하지 않아야한다는 것입니다.
이는 컴파일러가 명령문 항목에서와 같이 asm 문 다음에 스택 포인터의 값이 동일해야하기 때문입니다.
그러나 이전 버전의 GCC는이 규칙을 적용하지 않아 스택 포인터가 목록에 표시되도록 허용했으며 의미가 명확하지 않았습니다. 이 동작은 더 이상 사용되지 않으며 스택 포인터를 나열하는 것은 향후 GCC 버전에서 오류가 될 수 있습니다.

下面是一个实际的VAX例子,展示了如何使用Clobber寄存器:
	asm volatile ("movc3 %0, %1, %2"
					   : /* No outputs. */
					   : "g" (from), "g" (to), "g" (count)
					   : "r0", "r1", "r2", "r3", "r4", "r5", "memory");

此外,还有两个特殊的clobber参数:

"Cc":
"cc"clobber는 어셈블러 코드 수정 플래그 레지스터를 의미합니다.
일부 컴퓨터에서 GCC는 특정 하드웨어 레지스터로 조건 코드를 나타내며 "cc"는이 레지스터의 이름을 지정하는 데 사용됩니다. 다른 시스템에서는 조건 코드 처리가 다르며 "cc"를 지정해도 효과가 없습니다. 그러나 목표가 무엇이든 효과적입니다.
"Memory":
"memory"clobber는 어셈블리 코드가 입력 및 출력 피연산자에 나열된 항목이 아닌 항목 (예 : 입력 매개 변수 중 하나가 가리키는 메모리에 대한 액세스)에 대해 메모리 읽기 및 쓰기를 수행함을 컴파일러에 알립니다.
메모리에 올바른 값이 포함되어 있는지 확인하기 위해 GCC는 asm을 실행하기 전에 특정 레지스터 값을 메모리에 플러시해야 할 수 있습니다.
또한 컴파일러는 asm 이전에 메모리에서 읽은 값이 asm 이후에 변경되지 않은 상태로 유지된다고 가정하지 않습니다. 필요에 따라 다시로드합니다. "memory"clobber를 사용하면 컴파일러에 대한 읽기 / 쓰기 메모리 장벽을 효과적으로 형성 할 수 있습니다.
이 clobber는 프로세서가 asm 문을 실행 한 후 추측 읽기를 방지하지 않습니다. 이를 방지하려면 프로세서 별 차단 명령이 필요합니다.
레지스터를 메모리로 플러시하면 성능에 영향을 미치며 시간에 민감한 코드에 문제가 될 수 있습니다. 아래 예와 같이 이러한 상황을 방지하기 위해 GCC에 더 나은 정보를 제공 할 수 있습니다.
최소한 앨리어싱 규칙을 통해 GCC는 어떤 메모리를 플러시 할 필요가 없는지 알 수 있습니다.
다음은 메모리의 부동 소수점 값에 대한 두 개의 포인터를 받아들이고 부동 소수점 레지스터 출력을 생성하는 가상의 제곱합 명령어입니다. x와 y는 모두 asm 매개 변수에 두 번, 액세스 할 메모리를 지정하기 위해 한 번, asm에서 사용하는 기본 레지스터를 지정하기 위해 한 번 나타납니다.
GCC는 두 가지 목적으로 동일한 레지스터를 사용할 수 있으므로 일반적으로 레지스터를 낭비하지 않습니다. 그러나 asm에서 x를 나타 내기 위해 % 1과 % 3을 모두 사용하고 같을 것으로 기대한다면 어리석은 일입니다.
실제로 % 3은 아마도 레지스터가 아닐 것입니다. x가 가리키는 객체에 대한 기호 메모리 참조 일 수 있습니다.
asm ( "sumsq % 0, % 1, % 2"
: "+ f"(결과)
: "r"(x), "r"(y), "m"(* x), "m"(* y ));

这里是一个虚构的*z++ = *x++ * *y++指令。注意,必须将x、y和z指针寄存器指定为输入/输出,因为asm会修改它们。
	asm ("vecmul %0, %1, %2"
		 : "+r" (z), "+r" (x), "+r" (y), "=m" (*z)
		 : "m" (*x), "m" (*y));	

一个x86示例,其中字符串内存参数的长度未知。
	asm("repne scasb"
		: "=c" (count), "+D" (p)
		: "m" (*(const char (*)[]) p), "0" (-1), "a" (0));


如果您知道上面只读取一个10字节数组,那么您可以使用类似于这样的内存输入:
		"m" (*(const char (*)[10]) p)

Goto 레이블 :
asm goto를 사용하면 어셈블리 코드가 하나 이상의 C 레이블로 이동할 수 있습니다.
asm goto 문의 GotoLabels 부분에는 어셈블리 코드가 이동할 수있는 모든 C 레이블의 쉼표로 구분 된 목록이 포함되어 있습니다.
GCC는 asm의 실행이 다음 문으로 계속 될 것이라고 가정합니다 (이 경우가 아니면 asm 문 다음에 __builtin_unreachable 사용을 고려하십시오)
. asm goto의 최적화는 hot 및 cold 태그 속성을 사용하여
출력없이 asm goto 문을 개선함으로써 개선 될 수 있습니다 .
이는 컴파일러의 내부 제한 때문입니다. 제어 전송 명령은 출력을 가질 수 없습니다.
어셈블리 코드가 아무것도 수정하지 않으면 "메모리"클로버를 사용하여 옵티마이 저가 모든 레지스터 값을 메모리로 새로 고치고 asm 문 이후에 필요에 따라 다시로드하도록합니다. 그들.
또한 asm goto 문은 항상 암시 적으로 휘발성 문으로 간주됩니다.
어셈블러 템플릿에서 레이블을 인용하려면 레이블
앞에 "% l"(소문자 "l") 을 추가하고 GotoLabels에서 해당 (0 기반) 위치와 입력 피연산자를 추가합니다.
예를 들어 asm에 세 개의 입력이 있고 두 개의 태그를 참조하는 경우 첫 번째 태그는 "% l3"으로 참조되고 두 번째 태그는 "% l4"로 참조됩니다.
또한 괄호 안에 실제 C 태그 이름을 사용하여 태그를 참조 할 수 있습니다. 예를 들어 carry라는 태그를 인용하려면 "% l [carry]"를 사용할 수 있습니다.
이 방법을 사용할 때 레이블은 GotoLabels 섹션에 계속 나열되어야합니다.
다음은 i386 용 asm goto의 예입니다.
asm goto (
"btl % 1, % 0 \ n \ t"
"jc % l2"
: / * 출력이 없습니다. * /
:“r”(p1),“r”(p2)
:“cc”
: 캐리);

	return 0;

	carry:
	return 1;


下面的例子显示了一个使用内存clobber的asm goto。	
	int frob(int x)
	{
	  int y;
	  asm goto ("frob %%r5, %1; jc %l[error]; mov (%2), %%r5"
				: /* No outputs. */
				: "r"(x), "r"(&y)
				: "r5", "memory" 
				: error);
	  return y;
	error:
	  return -1;
	}

추천

출처blog.csdn.net/wzc18743083828/article/details/100543968