``continue`` 打破标签放置

``continue`` breaks label placement

这很好用:

#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 编译。)

你或许可以欺骗它:

        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

您在此代码中出现错误的原因:

    __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 语句的扩展程序集:

Goto Labels

asm goto allows assembly code to jump to one or more C labels. The GotoLabels section in an asm goto statement contains a comma-separated list of all C labels to which the assembler code may jump. GCC assumes that asm execution falls through to the next statement (if this is not the case, consider using the __builtin_unreachable intrinsic after the asm statement). Optimization of asm goto may be improved by using the hot and cold label attributes (see Label Attributes).

代码可以这样写:

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 语句,编译器可以重新排序并将其放在 [= 之前20=] 那不是你想要的。您可以使用虚拟约束,使编译器认为它依赖于变量 i 中的值,例如:

__asm__ ("xend" :: "rm"(i));

这将确保 i++; 将放置在该汇编块之前,因为编译器现在认为我们的 asm 块依赖于 [=21= 中的值]. GCC documentation 是这样说的:

Note that even a volatile asm instruction can be moved relative to other code, including across jump instructions. [snip] To make it work you need to add an artificial dependency to the asm referencing a variable in the code you don't want moved


GCC/ICC/CLANG 还有另一种替代方法,那就是重新编写逻辑。如果事务中止,您可以在程序集模板中增加 abort_counter。您会将其作为输入和输出约束传递。您还可以使用 GCC 的本地标签来定义唯一标签:

Local Labels

Local labels are different from local symbols. Local labels help compilers and programmers use names temporarily. They create symbols which are guaranteed to be unique over the entire scope of the input source code and which can be referred to by a simple notation. To define a local label, write a label of the form ‘N:’ (where N represents any non-negative integer). To refer to the most recent previous definition of that label write ‘Nb’, using the same number as when you defined the label. To refer to the next definition of a local label, write ‘Nf’. The ‘b’ stands for “backwards” and the ‘f’ stands for “forwards”.

循环代码可能如下所示:

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. This helps eliminate the use of inline assembly,这样可以减少代码中可能出错的向量。