【问题标题】:Is there any way to generate inline assembly programmatically?有没有办法以编程方式生成内联汇编?
【发布时间】:2018-05-20 22:18:09
【问题描述】:

在我的程序中,我需要将 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");

【问题讨论】:

  • 您不能将程序集模板字符串作为 char * 变量传入。
  • 你真正想做什么?为什么需要这个?
  • @EricPostpischil 操作数无关紧要。需要通过在 for 循环中插入 NOP 来控制缓存启动率。
  • “缓存启动率”是什么意思?您是否试图限制某些代码的执行速度?除了插入 nop 之外,还有其他方法。而且,如果您确实需要执行可变数量的 nop,您可以计算跳转到它们的序列中。这闻起来像XY problem。你应该充分解释上下文。

标签: c gcc assembly x86 inline-assembly


【解决方案1】:

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

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

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

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))

【讨论】:

    【解决方案2】:

    不行,内联 asm 模板需要是编译时常量,所以汇编器可以把它汇编成机器码。

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


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

    顺便说一句,dec eax 在所有现代 x86 CPU 上以每时钟 1 次运行,而 NOP 以每时钟 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
    }
    

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

    【讨论】:

    • @Johan :他说 4 或 5 每个时钟
    猜你喜欢
    • 2015-11-24
    • 1970-01-01
    • 1970-01-01
    • 2011-10-12
    • 2019-08-07
    • 2013-04-02
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多