有没有办法以编程方式生成内联汇编?

Is there any way to generate inline assembly programmatically?

在我的程序中,我需要将 NOP 作为内联汇编插入到一个循环中,并且 NOP 的数量可以通过一个参数来控制。像这样:

char nop[] = "nop\nnop";

for(offset = 0; offset < CACHE_SIZE; offset += BLOCK_SIZE) {

    asm volatile (nop
            :
            : "c" (buffer + offset)
            : "rax");

}

有没有办法告诉编译器将上面的内联汇编转换成下面的?

   asm volatile ("nop\n"
                 "nop"
            :
            : "c" (buffer + offset)
            : "rax");

不,内联asm模板需要编译时常量,所以assembler可以assemble它到机器代码。

如果您想要一个在 运行 时修改的灵活模板,这称为 JIT 编译或代码生成。您通常直接生成机器代码,而不是将 assembler 源文本提供给 assembler.


例如,请参阅此完整示例,它生成一个由可变数量的 dec eax 指令组成的函数,然后执行它。 Code golf: The repetitive byte counter

顺便说一句,dec eax 运行s 在所有现代 x86 CPU 上每个时钟 1,不像 NOP 运行s 在每个时钟 4,或者在 Ryzen 上可能是 5。参见 http://agner.org/optimize/

对于微小的延迟,更好的选择可能是 pause 指令,或者一些可变数量的 imul 指令的依赖链,或者 sqrtps,以 lfence 来阻止乱序执行(至少在 Intel CPU 上)。我没有检查 AMD 的手册,看看 lfence 是否被记录为那里的执行障碍,但 Agner Fog 报告说它可以 运行 在 Ryzen 上每时钟 4。


但实际上,您可能根本不需要 JIT 任何代码。对于只需要在一个或几个系统上工作的一次性实验,用类似

的东西来破解延迟循环
for (int i=0 ; i<delay_count ; i++) {
    asm volatile("" : "r" (i));  // defeat optimization
}

这会强制编译器在每次迭代时将循环计数器保存在寄存器中,因此它无法优化循环或将其转换为乘法。你应该得到编译器生成的 asm,比如 delayloop: dec eax; jnz delayloop。您可能希望在循环之后放置 _mm_lfence()

好吧,你可以做这个技巧:

#define NOPS(n) asm volatile (".fill %c0, 1, 0x90" :: "i"(n))

此宏将所需数量的 nop 指令插入指令流。请注意,n 必须是编译时常量。您可以使用 switch 语句来 select 不同的长度:

switch (len) {
case 1: NOPS(1); break;
case 2: NOPS(2); break;
...
}

您也可以这样做以节省代码大小:

if (len & 040) NOPS(040);
if (len & 020) NOPS(020);
if (len & 010) NOPS(010);
if (len & 004) NOPS(004);
if (len & 002) NOPS(002);
if (len & 001) NOPS(001);

请注意,对于此类事情,您真的应该考虑使用 pause 指令而不是 nop 指令,因为 pause 是一种语义提示,表明您只是想打发时间。这会将宏的定义更改为:

#define NOPS(n) asm volatile (".fill %c0, 2, 0x90f3" :: "i"(n))