我有一个asm循环保证不会超过128次迭代,我想用PC相对跳转展开。我们的想法是以相反的顺序展开每个迭代,然后跳转到它需要的循环中。代码如下所示:
#define __mul(i) \
"movq -"#i"(%3,%5,8),%%rax;" \
"mulq "#i"(%4,%6,8);" \
"addq %%rax,%0;" \
"adcq %%rdx,%1;" \
"adcq $0,%2;"
asm("jmp (128-count)*size_of_one_iteration" // I need to figure this jump out
__mul(127)
__mul(126)
__mul(125)
...
__mul(1)
__mul(0)
: "+r"(lo),"+r"(hi),"+r"(overflow)
: "r"(a.data),"r"(b.data),"r"(i-k),"r"(k)
: "%rax","%rdx");
使用gcc内联汇编是否可以这样?
答案 0 :(得分:1)
很抱歉,我无法用ATT语法提供答案,希望您能轻松完成翻译。
如果您在RCX中有计数,并且您可以在__mul(0)之后有一个标签,那么您可以这样做:
; rcx must be in [0..128] range.
imul ecx, ecx, -size_of_one_iteration ; Notice the multiplier is negative (using ecx is faster, the upper half of RCX will be automatically cleared by CPU)
lea rcx, [rcx + the_label] ; There is no memory read here
jmp rcx
希望这有帮助。
编辑: 我昨天弄错了。我假设引用[rcx + the_label]中的标签被解析为[rcx + rip + disp],但它不是因为没有这样的寻址模式(仅存在[rip + disp32])
此代码应该可以工作,另外它会保持rcx不受影响并且会破坏rax和rdx(但是你的代码在首先写入它们之前似乎没有读取它们):
; rcx must be in [0..128] range.
imul edx, ecx, -size_of_one_iteration ; Notice the multiplier is negative (using ecx is faster, the upper half of RCX will be automatically cleared by CPU)
lea rax, [the_label] ; PC-relative addressing (There is no memory read here)
add rax, rdx
jmp rax
答案 1 :(得分:1)
这不是一个直接的答案,但你考虑使用的变体 Duff's Device而不是内联 部件?这将采用switch语句的形式:
switch(iterations) {
case 128: /* code for i=128 here */
case 127: /* code for i=127 here */
case 126: /* code for i=126 here */
/* ... */
case 1: /* code for i=1 here*/
break;
default: die("too many cases");
}
答案 2 :(得分:1)
在gcc内联汇编中,您可以使用标签并让汇编程序为您排序跳转目标。像(人为的例子):
int max(int a, int b)
{
int result;
__asm__ __volatile__(
"movl %1, %0\n"
"cmpl %2, %0\n"
"jeq a_is_larger\n"
"movl %2, %0\n"
"a_is_larger:\n" : "=r"(result), "r"(a), "r"(b));
return (result);
}
这是一回事。你可以做的另一件事是避免乘法是让汇编程序对齐你的块,比如说,32个字节的倍数(我不认为指令序列符合16个字节),像:
#define mul(i) \
".align 32\n" \
".Lmul" #i ":\n" \
"movq -" #i "(%3,%5,8),%%rax\n"\
"mulq " #i "(%4,%6,8)\n" \
"addq %%rax,%0\n" \
"adcq %%rdx,%1\n" \
"adcq $0,%2\n"
这将简单地用nop
填充指令流。如果您确实选择不对齐这些块,您仍然可以在主表达式中使用生成的本地标签来查找装配块的大小:
#ifdef UNALIGNED
__asm__ ("imul $(.Lmul0-.Lmul1), %[label]\n"
#else
__asm__ ("shlq $5, %[label]\n"
#endif
"leaq .Lmulblkstart, %[dummy]\n" /* this is PC-relative in 64bit */
"jmp (%[dummy], %[label])\n"
".align 32\n"
".Lmulblkstart:\n"
__mul(127)
...
__mul(0)
: ... [dummy]"=r"(dummy) : [label]"r"((128-count)))
对于count
是编译时常量的情况,你甚至可以这样做:
__asm__("jmp .Lmul" #count "\n" ...);
最后几点说明:
如果自动生成的_mul()
事物可以创建不同长度的序列,那么对齐块是个好主意。对于你使用的常量0..127
,情况并非如此,因为它们都适合一个字节,但是如果你将它们扩大到更大,它将会转到16位或32位值,指令块会和睦相处。通过填充指令流,仍然可以使用跳转技术。