GNU 内联 asm:允许不同输出操作数的相同寄存器?
GNU inline asm: same register for different output operands allowed?
我用 C 代码和简短的内联汇编语句编写了一个小函数。
在内联汇编语句中,我需要 2 个“临时”寄存器来加载和比较一些内存值。
为了让编译器选择“最佳临时寄存器”,我想避免对这些临时寄存器进行硬编码(并将它们放入破坏列表中)。
相反,我决定为此目的在周围的 C 函数中创建 2 个局部变量。我使用“=r”将这些局部变量添加到内联 asm 语句的输出操作数规范中,然后将它们用于我的 load/compare 目的。
这些局部变量没有在 C 函数的其他地方使用,并且(可能是因为这个事实)编译器决定将相同的寄存器分配给两个相关的输出操作数,这使得我的代码无法使用(比较总是正确的)。
是否允许编译器对不同的输出操作数使用重叠寄存器,或者这是一个编译器错误(我倾向于将其评为错误)?
我只找到了有关防止输入和输出寄存器重叠的早期破坏的信息......但没有关于输出操作数的声明。
解决方法是初始化我的临时变量,并在输出操作数规范中为它们使用“+r”而不是“=r”。但在这种情况下,编译器会发出我想避免的初始化指令。
是否有任何干净的方法让编译器选择彼此不重叠的最佳寄存器,仅用于“内部内联汇编使用”?
非常感谢!
P.S.: 我使用支持“GNU 内联汇编”的“非 GNU”编译器为一些“奇特”目标编码。
P.P.S.:在下面的示例中,我也不明白为什么编译器不为“int eq=0;”生成代码(例如 'mov d2, 0')。也许我完全误解了“=”约束修饰符?
完全无用又愚蠢下面的例子只是为了说明(重点)问题:
int foo(const int *s1, const int *s2)
{
int eq = 0;
#ifdef WORKAROUND
int t1=0, t2=1;
#else
int t1, t2;
#endif
__asm__ volatile(
"ld.w %[t1], [%[s1]] \n\t"
"ld.w %[t2], [%[s2]] \n\t"
"jne %[t1], %[t2], 1f \n\t"
"mov %[eq], 1 \n\t"
"1:"
: [eq] "=d" (eq),
[s1] "+a" (s1), [s2] "+a" (s2),
#ifdef WORKAROUND
[t1] "+d" (t1), [t2] "+d" (t2)
#else
[t1] "=d" (t1), [t2] "=d" (t2)
#endif
);
return eq;
}
在创建的 asm 中,编译器将寄存器 'd8' 用于两个操作数 't1' 和 't2':
foo:
; 'mov d2, 0' is missing
ld.w d8, [a4] ; 'd8' allocated for 't1'
ld.w d8, [a5] ; 'd8' allocated for 't2' too!
jne d8, d8, 1f
mov d2, 1
1:
ret16
使用“-DWORKAROUND”编译:
foo:
; 'mov d2, 0' is missing
mov16 d9,1
mov16 d8,0
ld.w d9, [a5]
jne d8, d9, 1f
mov d2, 1
1:
ret16
这台机器的 EABI:
- return 寄存器 (non-pointer/pointer): d2, a2
- 非指针参数:d4..d7
- 指针参数:a4..a7
我认为这是您的编译器中的错误。
如果它说它支持“GNU 内联汇编”,那么人们会期望它遵循 GCC,其 manual 是最接近正式规范的东西。现在 GCC 手册似乎没有明确说明“输出操作数不会彼此共享寄存器”,但正如 o11c 提到的那样,他们确实建议对暂存寄存器使用输出操作数,如果它们可以共享寄存器,那将行不通。
一个可能比你的更有效的变通方法是在你的内联 asm 之后加上第二个“使用”两个输出的虚拟 asm 语句。希望这将使编译器相信它们可能是不同的值,因此需要单独的寄存器:
int t1, t2;
__asm__ volatile(" ... code ..."
: [t1] "=d" (t1), [t2] "=d" (t2) : ...);
__asm__ volatile("" // no code
: : "r" (t1), "r" (t2));
幸运的话,这将避免为不必要的初始化等生成任何额外代码。
另一种可能性是对特定的暂存寄存器进行硬编码并将它们声明为已损坏。它为寄存器分配器留下了较少的灵活性,但取决于周围的代码和编译器的智能程度,它可能不会产生太大的差异。
我用 C 代码和简短的内联汇编语句编写了一个小函数。
在内联汇编语句中,我需要 2 个“临时”寄存器来加载和比较一些内存值。
为了让编译器选择“最佳临时寄存器”,我想避免对这些临时寄存器进行硬编码(并将它们放入破坏列表中)。
相反,我决定为此目的在周围的 C 函数中创建 2 个局部变量。我使用“=r”将这些局部变量添加到内联 asm 语句的输出操作数规范中,然后将它们用于我的 load/compare 目的。
这些局部变量没有在 C 函数的其他地方使用,并且(可能是因为这个事实)编译器决定将相同的寄存器分配给两个相关的输出操作数,这使得我的代码无法使用(比较总是正确的)。
是否允许编译器对不同的输出操作数使用重叠寄存器,或者这是一个编译器错误(我倾向于将其评为错误)?
我只找到了有关防止输入和输出寄存器重叠的早期破坏的信息......但没有关于输出操作数的声明。
解决方法是初始化我的临时变量,并在输出操作数规范中为它们使用“+r”而不是“=r”。但在这种情况下,编译器会发出我想避免的初始化指令。
是否有任何干净的方法让编译器选择彼此不重叠的最佳寄存器,仅用于“内部内联汇编使用”?
非常感谢!
P.S.: 我使用支持“GNU 内联汇编”的“非 GNU”编译器为一些“奇特”目标编码。
P.P.S.:在下面的示例中,我也不明白为什么编译器不为“int eq=0;”生成代码(例如 'mov d2, 0')。也许我完全误解了“=”约束修饰符?
完全无用又愚蠢下面的例子只是为了说明(重点)问题:
int foo(const int *s1, const int *s2)
{
int eq = 0;
#ifdef WORKAROUND
int t1=0, t2=1;
#else
int t1, t2;
#endif
__asm__ volatile(
"ld.w %[t1], [%[s1]] \n\t"
"ld.w %[t2], [%[s2]] \n\t"
"jne %[t1], %[t2], 1f \n\t"
"mov %[eq], 1 \n\t"
"1:"
: [eq] "=d" (eq),
[s1] "+a" (s1), [s2] "+a" (s2),
#ifdef WORKAROUND
[t1] "+d" (t1), [t2] "+d" (t2)
#else
[t1] "=d" (t1), [t2] "=d" (t2)
#endif
);
return eq;
}
在创建的 asm 中,编译器将寄存器 'd8' 用于两个操作数 't1' 和 't2':
foo:
; 'mov d2, 0' is missing
ld.w d8, [a4] ; 'd8' allocated for 't1'
ld.w d8, [a5] ; 'd8' allocated for 't2' too!
jne d8, d8, 1f
mov d2, 1
1:
ret16
使用“-DWORKAROUND”编译:
foo:
; 'mov d2, 0' is missing
mov16 d9,1
mov16 d8,0
ld.w d9, [a5]
jne d8, d9, 1f
mov d2, 1
1:
ret16
这台机器的 EABI:
- return 寄存器 (non-pointer/pointer): d2, a2
- 非指针参数:d4..d7
- 指针参数:a4..a7
我认为这是您的编译器中的错误。
如果它说它支持“GNU 内联汇编”,那么人们会期望它遵循 GCC,其 manual 是最接近正式规范的东西。现在 GCC 手册似乎没有明确说明“输出操作数不会彼此共享寄存器”,但正如 o11c 提到的那样,他们确实建议对暂存寄存器使用输出操作数,如果它们可以共享寄存器,那将行不通。
一个可能比你的更有效的变通方法是在你的内联 asm 之后加上第二个“使用”两个输出的虚拟 asm 语句。希望这将使编译器相信它们可能是不同的值,因此需要单独的寄存器:
int t1, t2;
__asm__ volatile(" ... code ..."
: [t1] "=d" (t1), [t2] "=d" (t2) : ...);
__asm__ volatile("" // no code
: : "r" (t1), "r" (t2));
幸运的话,这将避免为不必要的初始化等生成任何额外代码。
另一种可能性是对特定的暂存寄存器进行硬编码并将它们声明为已损坏。它为寄存器分配器留下了较少的灵活性,但取决于周围的代码和编译器的智能程度,它可能不会产生太大的差异。