![](/img/trans.png)
[英]GNU inline asm: same register for different output operands allowed?
[英]Copy a byte to another register in GNU C inline asm, where the compiler chooses registers for both operands
我试图在 c 的内联 asm 中处理字符串。 我能够理解 strcpy 的工作原理(如下所示):
static inline char *strcpy(char *dest, char *src)
{
int d0, d1, d2;
char temp;
asm volatile(
"loop: lodsb;" /* load value pointed to by %si into %al, increment %si */
" stosb;" /* move %al to address pointed to by %di, increment %di */
" testb %%al, %%al;"
" jne loop;"
: "=&S" (d0), "=&D" (d1), "=&a" (d2)
: "0" (src), "1" (dest)
: "memory"
);
}
我正在尝试使用此结构来制作它,以便我可以在返回字符串之前修改字符串的各个字符。 因此,我正在尝试如下所示的内容:
static inline char *strcpy(char *dest, char *src)
{
int d0, d1, d2;
char temp;
asm volatile(
"loop: lodsb;" /* load value pointed to by %si into %al, increment %si */
" mov %2, %3;" /* move al into temp */
/*
*
* Do and comparisons and jumps based off how I want to change the characters
*
*/
" stosb;" /* move %al to address pointed to by %di, increment %di */
" testb %%al, %%al;"
" jne loop;"
: "=&S" (d0), "=&D" (d1), "=&a" (d2), "+r" (temp)
: "0" (src), "1" (dest)
: "memory"
);
}
我基本上是将lodsb
指令放入%al
的字节移动到临时变量中,然后在那里进行任何处理。 但是,由于某种我无法弄清楚的原因,该角色似乎从未真正存储在 temp 中。
你的第二个版本甚至不会组装,因为temp
和d2
的大小不同,所以你最终得到 GCC 的mov %eax, %dl
: https : //godbolt.org/z/tng4g4 。 当内联 asm 没有执行您想要的操作时,请始终查看编译器生成的 asm以查看编译器实际替换到您的模板中的内容(以及它为哪个操作数选择的寄存器)。
这与您描述的内容不匹配(运行但不起作用),因此它不是您正在做的事情的 MCVE。 但真正的问题仍然是可以回答的。
一种简单的方法是声明两个 C 临时文件的大小相同,以便 GCC 选择相同宽度的寄存器。
或者您可以使用大小覆盖,如mov %k2, %k3
来获取movl
或mov %b2, %b3
来获取movb
(8 位操作数大小)。
奇怪的是,您为"=a"
临时选择了int
所以编译器选择了 EAX,即使您只加载了一个char
。
我实际上建议movzbl %2b, %3k
使用与声明变量的方式相反的大小; 这比将一个字节合并到目标的低字节更有效,并且避免在 P6 系列、早期 Sandybridge 系列和不进行任何部分寄存器重命名的 CPU 上引入(或添加更多)部分寄存器问题。 另外,英特尔因为 Ivybridge 可以对它进行移动消除。
顺便说一句,您的第一个 strcpy 版本看起来安全且正确,不错。 是的, "memory"
破坏是必要的。
呃,至少内联汇编是正确的。 但是,由于在没有return
语句的情况下从非void
函数的末尾脱落,您有 C 未定义的行为。
您可以使用"+&S"(src)
读/写操作数而不是虚拟输出来简化 asm 操作数,因为您在包装函数中(因此可以修改此函数的本地src
)。 但是,具有匹配约束的虚拟输出是在要销毁的寄存器中获取输入的规范方法。
(如果你想像ISO C 设计糟糕的strcpy
,你需要在 asm 语句之前加上char *retval = dst
,如果你要使用上面的"+S"
和"+D"
操作数建议. 更好的主意是将其称为stpcpy
并返回指向目标末尾的指针。此外,您的 src 应该是const char*
。)
当然,在循环中使用 lodsb/stosb 并不是特别有效,尤其是在不将 AL 与 RAX 分开重命名的 CPU 上,因此每个负载也需要一个 ALU uop 来合并到 RAX 中。 但是一次字节一次比 SSE2 更糟糕,所以用movzx
加载优化它,也许索引寻址模式可能不值得麻烦。 见https://agner.org/optimize/和其他优化链接https://stackoverflow.com/tags/x86/info ,尤其是https://uops.info/的指令延迟/吞吐量/微指令计数。 ( stosb
是 3 个stosb
而mov
store + inc edi
总共是 2 个。)
如果您实际上正在优化代码大小而不是速度,只需使用 8 位或 32 位mov
来复制寄存器,而不是movzbl
。
顺便说一句,有这么多操作数,您可能希望在约束中使用命名操作数,例如[src] "+&S"(src)
,然后在模板中使用%[src]
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.