GCC内嵌汇编语法

465 阅读7分钟

GCC内联汇编的语法在GCC官方文档中有详细介绍,特别是在关于"Using Assembly Language with C"这一章节。

具体内容可以在GCC文档的以下章节找到:

  1. Extended Asm (扩展汇编):

    • GCC文档中的“Extended Asm”部分详细描述了GCC的内联汇编语法,包括输入、输出操作数约束以及寄存器破坏声明。
    • 文档链接:GCC Inline Assembly Documentation
  2. Basic Asm (基本汇编):

    • 这部分介绍了基本汇编的语法,适合不需要指定操作数约束的简单汇编指令。
    • 文档链接:Basic Asm

Basic Asm (基本汇编)

Basic Asm 是 GCC 内联汇编中相对简单的形式,它允许在不与C代码的输入输出直接关联的情况下插入汇编代码。Basic Asm 只执行汇编指令,并且不涉及操作数的约束,也不返回结果。

在GCC中的语法格式如下:

asm asm-qualifiers ( AssemblerInstructions )

asm-qualifiers是用来修饰asm关键字的一些修饰符,帮助开发者更好地控制汇编代码的行为,确保汇编代码的预期行为不会被编译器优化或修改。

这些修饰符主要有以下几种:

  1. volatile(Implicitly)

    • 作用:告诉编译器这个汇编代码不能被优化或移除,即使它看起来没有任何副作用。编译器通常会认为没有副作用的代码可以优化掉,但加上volatile后,编译器必须保留这段汇编代码,无论它是否对程序的逻辑有明显的影响。这个修饰符是隐式的,即:不写volatile修饰符时,也是默认开启volatile这个修饰符的。
    • 常用场景:处理硬件相关的操作(如I/O端口读写),或者需要确保某些汇编代码不会被优化掉。 示例
    void delay() {
       asm volatile("nop");  // 这里使用 volatile,防止 nop 指令被优化掉
    }
    
    // flush the TLB.
    void sfence_vma()
    {
        // the zero, zero means flush all TLB entries.
        asm volatile("sfence.vma zero, zero");
    }
    
  2. inline

    • 作用:提示编译器将该函数或代码段内联展开,而不是生成一个实际的函数调用。在使用汇编代码时,这个修饰符的作用主要是让编译器尽可能将包含汇编的函数直接展开在调用点,而不是通过函数调用的方式。
    • 常用场景:希望避免函数调用的开销,尤其是包含很短的汇编指令的函数。

    示例

    inline void do_nothing() {
        asm ("nop");
    }
    

    注意inline与C/C++的函数内联概念一致,但在内联汇编中,它通常和编译器的优化行为配合使用。

  • volatile:防止编译器优化掉汇编代码,适合关键操作。
  • inline:提示编译器将包含汇编的函数内联,减少函数调用开销。

Extended Asm (扩展汇编)

在GCC中的语法格式如下:

asm asm-qualifiers ("汇编指令" 
                    : 输出操作数
                    [: 输入操作数
                    [: 被破坏的寄存器]]);

OR

asm asm-qualifiers ("汇编指令" 
                    : 输出操作数
                    : 输入操作数
                    : 被破坏的寄存器(依赖约束列表)
                    : 跳转的标签);
  • "汇编指令":实际的汇编代码,使用双引号包裹。
  • 输出操作数:C变量对应的输出寄存器。
  • 输入操作数:C变量对应的输入寄存器。
  • 被破坏的寄存器:汇编代码中可能会被修改的额外的寄存器(即:不包含输出操作数列出来的),GCC会避免将这些寄存器用于其他目的。

RISC-V 汇编示例1

以下是一个使用RISC-V汇编的简单示例,它将两个整数相加:

int add_riscv(int a, int b) {
    int result;
    asm volatile ("add %0, %1, %2"
                  : "=r"(result)  // 输出操作数:将结果存储在任意的寄存器中
                  : "r"(a), "r"(b)  // 输入操作数:a 和 b 传递到任意的寄存器
                  : /* no clobbered registers */);
    return result;
}

解释:

  1. "add %0, %1, %2":这是RISC-V的add指令,其中%0%1%2分别表示输出和输入操作数。

    • %0表示输出,即result变量。
    • %1%2分别是输入的ab
  2. : "=r"(result):将result绑定到任意可用的通用寄存器上,=r表示输出是写入的寄存器。

  3. : "r"(a), "r"(b):将输入变量ab绑定到通用寄存器上,"r"表示使用任意可用的寄存器。

  4. : /* no clobbered registers */:因为这个汇编代码不修改额外的寄存器,因此该部分为空。

另一种示例2:读写CSR寄存器

RISC-V有特殊的控制状态寄存器(CSR),我们可以通过内联汇编来读取这些寄存器。例如,读取cycle计数器的值:

unsigned long read_cycle() {
    unsigned long cycle;
    asm volatile ("csrr %0, cycle" 
                  : "=r"(cycle));  // 将 CSR 寄存器的值读取到 cycle 变量
    return cycle;
}

解释:

  1. csrr %0, cycle:这是RISC-V的CSR读取指令,%0表示将cycle寄存器的值放入通用寄存器。
  2. : "=r"(cycle):将CSR读取结果存储到C变量cycle中。

示例3:带有内存访问的例子

void store_value(int *ptr, int value) {
    asm volatile ("sw %1, 0(%0)"  // 将 value 存储到 ptr 指向的地址
                  :               // 无输出
                  : "r"(ptr), "r"(value)  // 输入:ptr 是指针,value 是值
                  : "memory");     // 告诉编译器,内存已被修改
}

解释:

  1. 汇编指令模板"sw %1, 0(%0)",这是RISC-V的sw(store word)指令,用于将value存储到ptr指向的内存地址。%0表示ptr%1表示value

  2. 输入操作数"r"(ptr), "r"(value),这两个操作数将分别放入寄存器,ptr是指针,value是需要存储的值。

  3. 被破坏的寄存器:这里声明了"memory",表示汇编代码修改了内存内容,防止编译器进行不当的优化。

示例4:goto的例子

void example(int condition) {
   if (condition) {
       asm goto("j %l0" :::: label);  // 跳转到 C 代码中的 label
   }
   return;
label:
   // 跳转到此处
   return;
}

解释:

goto:这是相比基础汇编,扩展汇编多出来的修饰符。

作用:允许在汇编代码中使用跳转标记,并可以在C代码中结合goto语句进行跳转。通常用于更复杂的控制流场景,比如手动管理跳转指令。

常用场景:结合复杂控制流,允许从汇编代码中跳转到C代码中的标签,适合某些嵌入式编程的情况。

Extended Asm 的语法要点

  1. 占位符

    • %0, %1, %2:这些是汇编指令中的占位符,分别对应操作数列表中的操作数。GCC会根据输入、输出的约束替换这些占位符。
  2. 约束符

    • "r":表示操作数存储在通用寄存器中。
    • "m":表示操作数存储在内存中。
    • "=r":表示输出操作数存储在寄存器中,并且是只写的。
    • "+r":表示读写操作,既是输入也是输出。
  3. volatile关键字

    • asm volatilevolatile告诉编译器不要优化掉这段汇编代码,因为编译器有时会认为一些汇编代码没有副作用,从而将其优化掉。volatile确保汇编代码在每次执行时都不会被移除或优化。
  4. 被破坏的寄存器

    • 如果汇编代码会修改某些寄存器(即使它们不在输入或输出操作数中),应该在“被破坏的寄存器列表”中显式声明它们。这样编译器就不会将它们用于其他操作。

总结

GCC内联汇编允许将RISC-V汇编指令直接嵌入到C代码中,以实现硬件相关的优化。通过合理地使用输入、输出约束和寄存器破坏声明,可以保证编译器生成正确的代码。