如何将输入传递给扩展的 asm?

How do I pass inputs into extended asm?

考虑这段代码,来自我的

int main(){
    asm("movq 0000000, %rcx;"
            "startofloop: ; "
            "sub [=10=]x1, %rcx; "
            "jne startofloop; ");
}

我想将循环的迭代次数设为 C 变量,所以我在阅读 this document 后尝试了以下方法。

int main(){                                      
    int count = 100000000;                       
    asm("movq %0, %rcx;"                         
            "startofloop: ; "                    
            "sub [=11=]x1, %rcx; "                   
            "jne startofloop; ":: "r"(count));   
}                                                

不幸的是,编译失败,并出现以下错误。

asm_fail.c: In function ‘main’:
asm_fail.c:3:5: error: invalid 'asm': operand number missing after %-letter
     asm("movq %0, %rcx;"
     ^
asm_fail.c:3:5: error: invalid 'asm': operand number missing after %-letter

将 C 变量的值传递到程序集的正确方法是什么?

如果使用扩展汇编程序模板(具有输入、输出、破坏等的模板),那么您需要在模板内的寄存器名称上添加一个额外的 %%%rcx 在这种情况下。这将解决与此错误相关的问题:

error: invalid 'asm': operand number missing after %-letter

这会出现一个新问题。您将收到类似于以下内容的错误:

operand type mismatch for 'movq'

问题是 "r"(count) 输入约束告诉编译器它应该选择一个寄存器来包含 count 中的值。由于 count 定义为 int 类型,因此它将选择一个 32 位寄存器。为了论证,假设它选择 EAX。替换后它会尝试生成这条指令:

movq %eax, %rcx

您不能使用 movq 将 32 位寄存器的内容移动到 64 位寄存器,从而导致错误。更好的选择是使用 ECX 作为目标,这样两者将属于同一类型。修改后的代码如下所示:

asm("mov %0, %%ecx;"                         
    "startofloop: ; "                    
    "sub [=11=]x1, %%ecx; "                   
    "jne startofloop; ":: "r"(count));

或者您可以选择使用 "ri"(count) 的输入操作数。这将允许编译器选择寄存器或立即值。在更高的优化级别 (-O1-O2) 上,它可能会在这种情况下确定 count 保持不变 (100000000) 并生成如下代码:

mov 0000000, %ecx                         
startofloop:
sub [=12=]x1, %ecx
jne startofloop

与其被迫将 100000000 放入寄存器并将其复制到 ECX,不如使用立即数。


您的模板中的一个严重问题是您破坏了 ECX 的内容,但 GCC 对此一无所知。 GCC 实际上并不解析模板中的指令来确定代码的作用。它不知道你已经破坏了 ECX。编译器可能依赖 ECX 在模板前后具有相同的值。如果销毁输出操作数中未引用的寄存器,则必须在破坏列表中明确列出它。像这样的东西会起作用:

asm("mov %0, %%ecx;"                         
    "startofloop: ; "                    
    "sub [=13=]x1, %%ecx; "                   
    "jne startofloop; ":: "ri"(count) : "rcx");

现在 GCC 知道它不能依赖 RCX 中的值在模板执行前后是相同的值。

与其使用固定寄存器作为内部计数器,不如让 GCC 选择可用的东西。这样做意味着我们不再需要 clobber。您可以创建一个可用于计数的虚拟变量(临时变量)。为了避免这段代码被完全优化掉,我们可以在汇编程序模板上使用 volatile 属性。当汇编器模板没有输出操作数时,这不是必需的。这样的代码可以工作:

int count=100000000
int dummy;
asm volatile("mov %1, %0;"                         
    "startofloop: ; "                    
    "sub [=14=]x1, %0; "                   
    "jne startofloop; ":"=rm"(dummy): "ri"(count));

=rm 输出约束说明内存位置或寄存器可用于此操作数。将选择权交给编译器可以生成更好的代码。在 -O1 的优化级别,您可能会发现生成的代码如下所示:

mov    [=15=]x5f5e100,%ebx
startofloop:
sub    [=15=]x1,%ebx
jne    startofloop

在这种情况下,编译器选择使用立即操作数进行计数($0x5f5e100 = $100000000)。 dummy 变量被优化到寄存器 EBX.

您还可以使用其他技巧来改进模板。可以在 GNU documentation

中阅读更多关于扩展汇编程序模板的信息

您的代码似乎保留了变量 count 中的值。如果不要求 count 在执行模板之前具有相同的值,您可以对输入和输出使用 count。该代码可能如下所示:

asm volatile("startofloop: ; "
    "sub [=16=]x1, %0; "
    "jne startofloop; ":"+rm"(count): );

+rm 表示输出操作数也被用作输入操作数。在这种情况下,完成后 count 应始终为零。


如果您使用 GCC -S 选项输出生成的汇编代码,那么您可能希望更改模板以使输出看起来更清晰。不要使用 ;(分号),而是使用 \n\t。这会将汇编程序模板分解为多行并添加缩进。一个例子:

asm volatile("mov %1, %0\n\t"                         
    "startofloop:\n\t"                    
    "sub [=17=]x1, %0\n\t"                   
    "jne startofloop\n\t":"=rm"(dummy): "ri"(count));

一般来说,除非别无选择,否则不应使用内联汇编程序模板。在 C 中编码并引导编译器输出你想要的汇编程序,或者在需要时使用编译器内部函数。内联汇编程序应该作为最后的手段使用,或者如果您的作业需要它。 David Wohlferd 写了一篇关于这个主题的 Wiki article