``continue``打破标签放置

时间:2017-11-07 09:20:20

标签: c gcc while-loop inline-assembly continue

这很好用:

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

2 个答案:

答案 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,并且可以减少代码中出错的向量。