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

时间:2016-06-21 23:31:09

标签: c gcc x86-64 inline-assembly att

从我的earlier question开始考虑此代码。

int main(){
    asm("movq $100000000, %rcx;"
            "startofloop: ; "
            "sub $0x1, %rcx; "
            "jne startofloop; ");
}

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

int main(){                                      
    int count = 100000000;                       
    asm("movq %0, %rcx;"                         
            "startofloop: ; "                    
            "sub $0x1, %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变量的值传递给程序集的正确方法是什么?

1 个答案:

答案 0 :(得分:6)

如果使用扩展汇编程序模板(带有输入,输出,clobbers等的模板),则需要在模板内的寄存器名称前加一个%。在这种情况下%%rcx。这将解决与此错误相关的问题:

  

错误:'asm'无效:%-letter

后缺少操作数

这会带来一个新问题。您将收到类似于以下错误:

  

'movq'

的操作数类型不匹配

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

movq %eax, %rcx

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

asm("mov %0, %%ecx;"                         
    "startofloop: ; "                    
    "sub $0x1, %%ecx; "                   
    "jne startofloop; ":: "r"(count));

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

mov $100000000, %ecx                         
startofloop:
sub $0x1, %ecx
jne startofloop

不是被迫将100000000放入寄存器并将其复制到 ECX ,而是可以使用立即值。

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

asm("mov %0, %%ecx;"                         
    "startofloop: ; "                    
    "sub $0x1, %%ecx; "                   
    "jne startofloop; ":: "ri"(count) : "rcx");

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

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

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

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

mov    $0x5f5e100,%ebx
startofloop:
sub    $0x1,%ebx
jne    startofloop

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

您可以采取其他技巧来改进模板。可以在GNU documentation

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

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

asm volatile("startofloop: ; "
    "sub $0x1, %0; "
    "jne startofloop; ":"+rm"(count): );

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

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

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

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