【问题标题】:``continue`` breaks label placement``继续``中断标签放置
【发布时间】:2018-04-19 15:04:52
【问题描述】:

这很好用:

#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 之后的所有内容,因为它无法到达(而且它也不分析汇编代码,所以它不知道你在做什么)。
  • 这只是一个猜测,但我认为编译器会将 continue 之后的行视为死代码(永远不会被执行),因此它会在编译时将其删除。 C 编译器不知道“xbegin ABORT”是什么意思。
  • 使用__asm__ __volatile__ 修复了吗?
  • 你检查gcc -S rtm_simple.c的输出了吗?
  • 你也许能骗到它:continue; reachable: __asm__("ABORT:"); ++abort_counter; } ... if (abort_counter &lt; 0) goto reachable;

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


【解决方案1】:

你也许可以欺骗它:

        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"); 访问。它可能有点安全,因为abort_counter 是易失性的,但它可能在寄存器中包含其他内容,它希望在asm 语句之后存储这些内容。所以它可能适用于这个玩具示例,但很容易被真实的周围代码破坏。不使用 asm goto 跳转的 asm 语句通常安全。
【解决方案2】:

您在此代码中收到错误的原因:

    __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 有话要说:

请注意,即使是 volatile 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 有帮助,并且减少了代码中可能出错的向量。

【讨论】:

    猜你喜欢
    • 2018-04-13
    • 1970-01-01
    • 2011-10-02
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-19
    相关资源
    最近更新 更多