如何将输入传递给扩展的 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。
考虑这段代码,来自我的
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。