代码之家  ›  专栏  ›  技术社区  ›  HardcoreHenry

可能被覆盖的值的内联程序集约束

  •  -1
  • HardcoreHenry  · 技术社区  · 7 年前

    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 约束被解释为'this value is' 总是 可以 更改。。。

    我也很关心 ptr1 . 指针本身并没有实际设置,但它指向的是。我想知道这是否应该有一个读约束,并且想知道是否有一个合适的方法来设置这个约束。

    1 回复  |  直到 7 年前
        1
  •  3
  •   Peter Cordes    7 年前

    没错, "=r" 意思是只写。寄存器在输入时失效。编译器不会在asm之前将任何特定的内容放入所选寄存器,因为它将被覆盖。编译器将进行优化,就像您编写 out1 = asm_result; 外部内联装配。

    "+r" 是输入/输出操作数。如果它 可以 要修改,您需要编译器假定它始终是。

    查看编译器为函数生成的asm,例如在Godbolt编译器资源管理器上。( https://godbolt.org/


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

    "+r"(ptr1) 告诉编译器指针值已被修改,但是 意味着指向的值被修改。这个 "memory" 击打是一种沉重的方式,或者正如杰斯特所说,你应该使用 "=m"(*ptr1) 相反,约束让编译器选择一种寻址模式,并告诉它指向的内存是无条件写入的。


    https://gcc.gnu.org/wiki/DontUseInlineAsm

    STREX没有之前的LDREX有意义吗?我不这么认为,但是如果我错了,那么对于这一条指令,您只需要内联asm,因为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 dmb ).

    叮当声使整件事失去了小枝,用 itte (在拇指模式中)对 dmbne sy


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

    dmb公司 意志命令 后期商店工作。早期的存储,因此这与AArch64(或32位,提供ARMv8指令)上的完全不同,后者编译器生成的代码不使用 dmb公司 .

    我看到的一个遗漏优化是编译器无法将 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;
    }
    

    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;
    }
    

    非常数 秩序

    我们可以看到过度增强对x86的影响,gcc使用 mfence 为了这个,但只是简单的 mov 其他的(在x86 asm中有发布语义)。同样在ARM32 gcc输出中,我们可以看到 dmb公司 之前

    @ 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
    
    推荐文章