内联汇编约束可能会被覆盖的值

时间:2018-08-20 20:25:22

标签: assembly arm inline-assembly

我正在调试一些汇编代码,在阅读了一些文档之后,我不确定我是否100%理解约束。我想知道是否有人可以让我挺直。如果我有以下代码(arm32):

int foo(int in1, int *ptr1) {
    int out1=123;

    asm volatile (
        "   cmp     %[in1],  #0;"
        "   bne     1b;"
        "   dmb;"
        "   mov     %[out1], #0;"
        "1: strex   %[in1], [%[ptr1]];"
        : [out1]"=r", [ptr1]"+r"(ptr1),
        : [in1]"r"(in1),
        : "memory" );

    return out1;
}

我不清楚几件事:首先,我将out1标记为输出,但是只有in1为零时,它才是输出。我担心=r约束被解释为“此值总是 设置”,从而告诉优化器任何先前的值都不相关。当然,我不确定如何为可能更改的内容写一个约束...

我也关心ptr1。指针本身实际上并没有设置,但是指向的却是。我想知道这是否应具有读取约束,并想知道是否有适当的方法来设置此约束。

请注意,我在多个编译器(gcc和clang,以及每个编译器的不同版本)上使用此代码,因此我想避免对特定优化器进行任何假设。

1 个答案:

答案 0 :(得分:3)

是的,"=r"表示只写。寄存器在输入时无效。编译器不会费心在asm之前的选定寄存器中放入任何特定内容,因为它将被覆盖。编译器将进行优化,就像您在嵌入式asm之外编写out1 = asm_result;一样。

"+r"是输入/输出操作数。如果可能被修改,则您需要编译器假定它一直存在。

查看该函数的编译器生成的asm,例如在Godbolt编译器资源管理器上。 (https://godbolt.org/)。您可以查看编译器围绕内联汇编生成的代码,包括内联到另一个函数之后。


  

我也关心ptr1。指针本身实际上并没有设置,但是它指向的是。

是的,您值得关注。 "+r"(ptr1)告诉编译器指针值已修改,但 not 并不暗示指向的值已修改。 "memory"破坏符是执行此操作的一种繁重方法,或者就像Jester所说的那样,您应该只使用"=m"(*ptr1)约束来让编译器选择寻址模式,并告诉它指向的内存是无条件写的。


或者更好,https://gcc.gnu.org/wiki/DontUseInlineAsm

在没有前面的LDREX的情况下STREX甚至有意义吗?我不这么认为,但是如果我错了,那么您只需要为该一条指令使用内联汇编,因为ARM编译器甚至对原子存储也使用普通的str

如果此函数执行LL / SC的后半部分,那么这很奇怪。

您确定无法使用内置的__atomic_store(ptr1, value, __ATOMIC_RELAXED) +可选挡板或C11 atomic_store_explicit做您想要的吗?

#include <stdatomic.h>
int foo(int in1, int *ptr1) {
    int out1=123;

    if (in1 != 0) {
        out1 = 0;
        //asm("dmb" ::: "memory");
        atomic_thread_fence(memory_order_release);  // make the following stores release-stores wrt. earlier operations
    }
    atomic_store_explicit((_Atomic int*)ptr1, in1, memory_order_relaxed);
    return out1;
}

使用gcc6.3,on the Godbolt compiler explorer进行编译:

@ gcc6.3 -O3 -mcpu=cortex-a53  (ARM mode)
foo:
        subs    r3, r0, #0    @ copy in1 and set flags from it at the same time
        moveq   r0, #123      @ missed-optimization: since we still branch, no point hoisting this out of the if with predication
        bne     .L5
        str     r3, [r1]      @ if()-not-taken path
        bx      lr
.L5:
        dmb     ish           @ if()-taken path
        mov     r0, #0        @ makes the moveq doubly silly, because we do it again inside the branch.
        str     r3, [r1]
        bx      lr          @ out1 return value in r0

因此它运行的指令与实现相同(除了str而不是strex),但是使用尾部重复的方式进行分支,并且可能节省了整体指令(可能具有较大的代码大小,但动态性较低 指令计数,因为我们使用了-O3。)在-Os中,我们获得了非常紧凑的asm,它更像是您的嵌入式asm(跳过mov和dmb)。

Clang使用itte(在拇指模式下)断定dmbne sy来使整个事物变得无分支。 (请参阅其在Godbolt上的输出。)


请注意,如果要将单独的障碍移植到AArch64,则效率通常较低。您希望编译器能够使用AArch64的stlr版本存储(即使它是顺序版本,而不是较弱的普通版本)。 dmb ish是一个完整的内存屏障。另外,ARMv8的32位代码可以使用stl

请注意,完整的dmb以后将订购 other 存储wrt。较早的存储,因此这在AArch64(或带有ARMv8指令的32位)上并不完全等效,在AArch64上,编译器生成的代码未使用dmb

此版本可编译为适用于所有体系结构的非常不错的asm:我看到的一个错失的优化是编译器无法将dmbstr分开,在条件str之后留下一个公用dmb。 (对于必须使用dmb的情况。)

// recommended version
int foo_ifelse(int in1, int *ptr1) {
    int out1=123;
    if (in1 != 0) {
        out1 = 0;
        atomic_store_explicit((_Atomic int*)ptr1, in1, memory_order_release);
    } else {
        atomic_store_explicit((_Atomic int*)ptr1, in1, memory_order_relaxed);
    }
    return out1;
}

AArch64 gcc6.3 -O3输出(Godbolt compiler explorer):

foo_ifelse:
    cbnz    w0, .L9       @ compare-and-branch-non-zero
    str     wzr, [x1]     @ plain (relaxed) store
    mov     w0, 123
    ret
.L9:
    stlr    w0, [x1]      @ release-store
    mov     w0, 0
    ret

您可以 order参数设置为变量,以简化源代码,但是gcc用它做的很不好。 (叮当声将其变成分支)。即使在这种情况下只有两个选项被放宽并释放,GCC仍将其增强为seq_cst。

// don't do this, gcc just strengthen variable-order to seq_cst
int foo_variable_order(int in1, int *ptr1) {
    int out1=123;
    memory_order order = memory_order_relaxed;

    if (in1 != 0) {
        out1 = 0;
        order = memory_order_release;
    }
    // SLOW AND INEFFICIENT with gcc
    // but clang distributes it over the branch
    atomic_store_explicit((_Atomic int*)ptr1, in1, order);
    return out1;
}

非恒定order要求在asm中分支,或增强到最大程度。

我们真的可以看到过度增强x86的效果,其中gcc为此使用mfence,而其他人仅使用普通的mov(在x86 asm中具有释放语义)。同样在ARM32 gcc输出中,我们在存储区之前的dmb和存储区之后的看到seq-cst而不是仅仅发布。

@ gcc6.3 -Os -mcpu=cortex-m4 -mthumb
foo_variable_order:
    dmb     ish
    str     r0, [r1]
    dmb     ish             @ barrier after for seq-cst

    cmp     r0, #0
    ite     eq              @ branchless out1 = in1 ? 0 : 123
    moveq   r0, #123
    movne   r0, #0
    bx      lr