本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。


博客内容主要围绕:
       5G协议讲解
       算力网络讲解(云计算,边缘计算,端计算)
       高级C语言讲解
       Rust语言讲解

GNU C/C++ 内联汇编——实例参考

下面的示例以 IBM 的 Power PC 为例。

Example 1

下面的示例说明了 volatile 关键字的用法:

#include <stdio.h>
inline bool acquireLock(int *lock){
	bool returnvalue = false;
  	int lockval;
  	asm volatile(
              /*--------a fence here-----*/
               
               	"  0: lwarx %0,0,%2   \n" // Loads the word and reserves
                                          // a memory location for the subsequent
                                          // stwcx. instruction.
				"     cmpwi %0,0      \n" // Compares the lock value to 0.
               	"     bne- 1f         \n" // If it is 0, you can acquire the 
                                          // lock. Otherwise, you did not get the
                                          // lock and must try again later.
                                           
               	"     ori %0,%0,1     \n" // Sets the lock to 1. 
               	"     stwcx. %0,0,%2  \n" // Tries to conditionally store 1
                                          // into the lock word to acquire 
                                          // the lock.
                                           
               	"     bne- 0b         \n" // Reservation was lost. Try again.
				"     isync           \n" // Lock acquired. The isync instruction 
                                          // implements an import barrier to 
                                          // ensure that the instructions that
                                          // access the shared region guarded by
                                          // this lock are executed only after 
                                          // they acquire the lock.
 
               	"     ori  %1,%1,1    \n" // Sets the return value for the 
                                          // function acquireLock to true.
				"  1:                 \n" // Did not get the lock. 
                                          // Will return false.
			/*------a fence here------*/
				:     "+r"   (lockval),
                      "+r"   (returnvalue) 
               	:     "r"    (lock)       // "lock" is the address of the lock in 
                                          // memory.
				:     "cr0"               // cr0 is clobbered by cmpwi and stwcx.
               );
                	         
	return returnvalue;
}

int main()
{
	int myLock;
  	if(acquireLock(&myLock)){
    	printf("got it!\n");
  	}else{ 		
       	printf("someone else got it\n");
  	} 
  	return 0;
}

在这个例子中,%0指的是第一个操作数“+r”(lockval), %1指的是第二个操作数“+r”(returnvalue), %2指的是第三个操作数“r”(lock)。

程序使用一个锁来控制对共享存储的访问; 在获取锁之前,没有指令可以访问共享存储。

volatile关键字意味着在汇编指令组周围设置栅栏,因此任何汇编指令都不能移出汇编代码块。 如果没有volatile关键字,编译器可以移动指令进行优化。 这可能会导致一些指令在不获取锁的情况下访问共享存储。

在这个汇编语句中,没有必要使用 memory clobber,因为这些指令不会以意想不到的方式修改内存。 如果您使用 memory clobber,程序在功能上仍然是正确的。 但是, memory clobber 会导致许多不必要的重新加载,从而造成性能损失。

Example 2

下面的示例演示了输入和输出操作数的符号名称的使用:

int a ;
int b = 1, c = 2, d = 3 ;  
__asm("   addc %[result], %[first], %[second]" 
       :  [result]      "=r"       (a)
       :  [first]       "r"        (b), 
          [second]      "r"        (d)
      );

在这个例子中,%[result]指的是输出操作数变量a, %[first]指的是输入操作数变量b, %[second]指的是输入操作数变量d。

Example 3

下面的例子展示了条件寄存器在 clobber 中的典型用法:

asm ("    add. %0,%1,%2  \n"     
      :   "=r"    (c)                    
      :   "r"     (a),
          "r"     (b)
      :   "cc"
      );

在本例中,除了程序的输入和输出中列出的寄存器外,add.指令还影响条件寄存器。 因此,您必须通过将 “cc” 添加到 clobbers 来通知编译器。

Example 4

下面的示例显示了 memory clobber 的使用情况:

 asm volatile ("   dcbz 0, %0      \n" 
               :   "=r"(b)
               : 
               :   "memory"
               );

在这个例子中,指令 “dcbz” 清除了一个缓存块(cache block),并且可能改变了内存位置中的变量。 编译器无法知道哪些变量被更改了。

因此,所有需要的东西都必须在程序执行完成后从内存中重新加载。memory clobber 以程序性能为代价来确保程序的正确性,因为编译器可能会重新加载与这段程序无关的数据。

Example 5

下面的例子展示了 ‘=’ 约束修饰符和 ‘r’ 约束的用法 :

int a ;  
int b = 100 ;  
int c = 200 ;  
asm("   add %0, %1, %2"
    :   "=r"     (a)
    :   "r"      (b),
        "r"      (c)
    );

加法指令将两个通用寄存器的内容相加。 %0、%1和%2操作数被输出/输入操作数字段中的C表达式替换。

输出操作数使用 ‘=’ 修饰符表示需要一个可修改的操作数。它使用 ‘r’ 约束来表示需要一个通用寄存器。 同样,输入操作数中的 ‘r’ 约束表明需要通用寄存器。 在这些限制中,编译器可以自由选择任何寄存器来替换%0、%1和%2。

注意:如果编译器选择 ‘r0’ 作为第二个操作数,则 add 指令使用立即数 0 并产生一个错误的结果。 因此,为了防止编译器选择 ‘r0’ 作为第二个操作数,可以为第二个操作数使用 ‘b’ 约束。

Example 6

下面的例子展示了 ‘+’ 修饰符和 ‘K’ 约束的用法:

 asm ("   addi %0,%0,%2"
      :   "+r"    (a)
      :   "r"     (a),
          "K"     (15)
      );

此程序将操作数%0和操作数%2相加,并将结果写入操作数%0。 输出操作数使用 ‘+’ 修饰符表示操作数%0可以被指令读取和写入。 ‘K’ 约束指出,加载到操作数%2中的值必须是一个无符号的16位常量值。

Example 7

下面的示例显示了 ‘%’ 修饰符和 ‘f’ 约束的用法:

asm("   fadd %0, %1, %2"
    :   "=f"    (c)
    :   "%f"    (a),
        "f"     (b)
    ); 

这个程序将操作数a和b相加,并将结果写入操作数c。’%’ 修饰符表示,如果编译器可以通过交换操作数a和b以生成更好的代码,那么可以交换操作数a和b。 每个操作数都有 ‘f’ 约束,表示需要一个浮点寄存器。

Example 8

下面的例子展示了 ‘b’ 约束的用法:

char res[8]={'a','b','c','d','e','f','g','h'};
char a='y';
int  index=7;
asm ("    stbx %0,%1,%2       \n"     \
     :                               \
     :    "r"    (a),
          "b"    (index),
          "r"    (res)
     ); 

在这个例子中,‘b’ 约束指示编译器为输入操作数%1选择一个普通寄存器,而不是 ‘r0’ 。 这个程序的输出结果是 “abcdefgy” 。 但是,如果使用 ‘r’ 约束,并且编译器为%1选择 ‘r0’ ,则该指令将生成一个不正确的结果字符串 “ybcdefgh” 。 因此,对于特殊处理 ‘r0’ 的指令,需要用 ‘b’ 来约束输入操作数。

Example 9

下面的例子展示了 ‘m’ 约束的用法:

 asm ("   stb %1,%0       \n"     \
      :   "=m"    (res)           \
      :   "r"     (a)
      );

在这个例子中,stb指令的语法是stb RS,D(RA),其中D是一个偏移,R是一个寄存器,D+RA为有效地址。 通过使用约束 ‘m’ ,你不需要通过分别指定寄存器和位移来手动构造有效地址。

您可以使用一个约束 ‘m’ 或 ‘o’ 来引用指令中的两个操作数,而不管正确的偏移量应该是多少,也不管它是堆栈的偏移量还是ToC(Table of Contents)的偏移量。 这允许编译器选择正确的寄存器(例如,r1是一个自动变量),并自动应用正确的位移。


这里是从善若水的博客,感谢您的阅读⌨🖥🖱


文章链接

《GNU C/C++ 内联汇编编程指南全集》
《GNU C/C++ 内联汇编——入门级》
《GNU C/C++ 内联汇编——进阶——语法详解》
《GNU C/C++ 内联汇编——进阶——约束详解》
《GNU C/C++ 内联汇编——补充介绍》
《GNU C/C++ 内联汇编——实例参考》
《GNU C/C++ 内联汇编——Intel与ATT汇编语法对比》



在这里插入图片描述


Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐