这很好用:
#include <stdio.h>
int main(){
volatile int abort_counter = 0;
volatile int i = 0;
while (i < 100000000) {
__asm__ ("xbegin ABORT");
i++;
__asm__ ("xend");
__asm__ ("ABORT:");
++abort_counter;
}
printf("%d\n", i);
printf("nof abort-retries: %d\n",abort_counter-i);
return 0;
}
然而,我最初写的是
#include <stdio.h>
int main(){
volatile int abort_counter = 0;
volatile int i = 0;
while (i < 100000000) {
__asm__ ("xbegin ABORT");
i++;
__asm__ ("xend");
continue;
__asm__ ("ABORT:");
++abort_counter;
}
printf("%d\n", i);
printf("nof abort-retries: %d\n",abort_counter);
return 0;
}
但这导致了
/tmp/cchmn6a6.o: In function `main':
rtm_simple.c:(.text+0x1a): undefined reference to `ABORT'
collect2: error: ld returned 1 exit status
为什么?
(使用gcc rtm_simple.c -o rtm_simple
编译。)
答案 0 :(得分:7)
你可能会欺骗它:
continue;
reachable:
__asm__ ("ABORT:");
++abort_counter;
}
printf("%d\n", i);
printf("nof abort-retries: %d\n",abort_counter);
if (abort_counter < 0) goto reachable;
return 0;
}
带有标签的goto
告诉gcc代码是可以访问的,并且abort_counter
是易变的,应该阻止gcc能够优化goto
。
答案 1 :(得分:7)
您在此代码中收到错误的原因:
__asm__ ("xbegin ABORT");
i++;
__asm__ ("xend");
continue;
__asm__ ("ABORT:");
++abort_counter;
是因为编译器在continue
语句之后看到所有内容,直到块(while循环)结束为死代码。 GCC不了解特定的asm块的作用,因此它不知道ABORT
中使用了标签__asm__ ("xbegin ABORT");
。通过消除死代码,跳转目标被消除,当链接器尝试解析标签时,它已经消失(未定义)。
作为另一个答案的替代方案 - 从GCC 4.5开始(在CLANG中仍然不支持),您可以使用带有asm goto语句的扩展程序集:
转到标签
asm goto 允许汇编代码跳转到一个或多个C标签。 asm goto语句中的GotoLabels部分包含汇编代码可能跳转到的所有C标签的逗号分隔列表。 GCC假定asm执行落到下一个语句(如果不是这种情况,请考虑在asm语句之后使用__builtin_unreachable内在函数)。可以通过使用热标签和冷标签属性来改进asm goto的优化(请参阅标签属性)。
代码可能是这样编写的:
while (i < 100000000) {
__asm__ goto("xbegin %l0"
: /* no outputs */
: /* no inputs */
: "eax" /* EAX clobbered with status if an abort were to occur */
: ABORT); /* List of C Goto labels used */
i++;
__asm__ ("xend");
continue;
ABORT:
++abort_counter;
}
由于编译器现在知道内联汇编可能使用标签ABORT
作为跳转目标,因此它不能仅仅优化它。同样,使用此方法我们不需要将ABORT
标签放在装配块中,可以使用正常的 C 标签来定义它。
使用上面的代码挑剔:尽管__asm__ ("xend");
是易失性的,因为它是一个基本的 asm 语句,编译器可以重新排序它并将它放在i++
之前不会是你想要的。您可以使用伪约束,使编译器认为它依赖于变量i
中的值:
__asm__ ("xend" :: "rm"(i));
这将确保在此程序集块之前放置i++;
,因为编译器现在会认为我们的 asm 块依赖于i
中的值。 GCC documentation可以这样说:
请注意,即使是易失性的asm指令也可以相对于其他代码移动,包括跨跳转指令。 [snip]为了使它工作,你需要在asm中添加一个人工依赖,引用你不想移动的代码中的变量
还有另一种选择应该适用于GCC / ICC / CLANG,那就是重写逻辑。如果事务中止,您可以增加程序集模板中的abort_counter
。您将其作为输入和输出约束传递。您还可以使用GCC的本地标签来定义唯一标签:
本地标签
本地标签与本地符号不同。本地标签可以帮助编译器和程序员临时使用名称。它们创建的符号在输入源代码的整个范围内保证是唯一的,并且可以通过简单的符号来引用。要定义本地标签,请写入“N:”形式的标签(其中N表示任何非负整数)。要引用该标签的最新先前定义,请使用与定义标签时相同的数字来写入“Nb”。要引用本地标签的下一个定义,请写入'Nf'。 'b'代表“向后”,'f'代表“向前”。
循环的代码可能如下所示:
while (i < 100000000) {
__asm__ __volatile__ ("xbegin 1f" : "+rm"(i) ::
: "eax");
/* EAX is a clobber since aborted transaction will set status */
/* 1f is the local label 1: in the second asm block below */
/* The "+rm"(i) constraint is a false dependency to ensure
this asm block will always appear before the i++ statement */
i++;
__asm__ __volatile__ ("xend\n\t"
"jmp 2f\n" /* jump to end of asm block, didn't abort */
"1:\n\t" /* This is the abort label that xbegin points at */
"incl %0\n" /* Increment the abort counter */
"2:" /* Label for the bottom of the asm block */
: "+rm"(abort_counter)
: "rm"(i)); /* The "rm"(i) constraint is a false dependency to ensure
this asm block will always appear after the i++ statement */
}
如果您的编译器支持它(GCC 4.8.x +),请使用GCC的transactional intrinsics。这有助于eliminate the use of inline assembly,并且可以减少代码中出错的向量。