有没有办法以编程方式生成内联汇编?
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))
在我的程序中,我需要将 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))