assembly GNU C inline asm中的输入操作数“m”(var)“和输出操作数"=m”(var)“?使用时没有指令作为屏障?

s1ag04yj  于 2023-01-17  发布在  其他
关注(0)|答案(2)|浏览(146)

我想知道asm中的输入操作数"m"(var)和输出操作数"=m"(var)是做什么的:

asm volatile("" : "=m"(blk) : :);
asm volatile("" : : "m"(write_idx), "m"(blk) :);

我在SPMC队列中遇到了here上面的2行。
副作用是什么呢?上面的行没有ASM指令,所以我相信作者试图利用一些定义良好的副作用(Eidogg. flush由寄存器保存的write_idxblk的值,如果它们在第2行?)

aamkag61

aamkag611#

asm volatile("" : "=m"(blk) : :);

在这条语句之后,编译器认为一些随机汇编代码已经写入blk。由于给出了输出操作数,编译器假定blk的先前值无关紧要,并且可以丢弃在此语句之前没有人读取的、为blk赋值的代码。它还必须假设blk现在保持某个其他值,并且从存储器而不是例如从寄存器中的副本读回该值。
简单来说,这看起来像是试图强迫编译器将blk视为从编译器未知的源代码中赋值的,通常通过将blk限定为volatile可以更好地实现这一点。
请注意,由于asm语句是限定的volatile,因此该语句还暗示了对编译器的排序要求,以防止某些代码重新排列。
类似的代码(虽然没有volatile)也可以用来告诉编译器你希望一个对象假设一个未指定的值作为优化,但是我建议不要使用这种技巧。

asm volatile("" : : "m"(write_idx), "m"(blk) :);

编译器假定此语句读取变量write_idxblk。因此,在执行此语句之前,它必须将这些变量的当前内容具体化到内存中。这看起来像是内存屏障的粗略近似,但实际上并不像预期的那样影响内存屏障。
除此之外,您所显示的代码肯定有缺陷:除非另有说明,否则C++对象不能在不同步的情况下同时修改。但是,此代码不能进行同步,因为它不包含任何具有类型(如std::atomic)或同步设施(如互斥锁)的头文件。
在没有详细阅读代码的情况下,我猜想作者只是使用普通的对象来进行同步,相信并发变异是定义良好的。观察到事实并非如此,作者可能认为编译器应该受到责备,并添加了这些asm语句作为杂牌,使其生成似乎有效的代码。然而,这是不正确的,尽管它可能看起来在具有足够严格的存储器模型(例如x86)和足够愚蠢的编译器的体系结构上工作,或者纯粹是运气。
不要像这样编程。使用原子或高级同步设施代替。

yfwxisqw

yfwxisqw2#

第一个对我来说听起来像是个bug。asm输出操作数必须总是被写入。编译器会假设在优化之前对blk的任何存储都可以被删除,因为asm块会设置一个新值。例如:

extern int foo;
int bar()
{
    auto& blk = foo;
    asm volatile("" : "=m"(blk) : :);
    return blk;
}

int baz()
{
    foo = 42;
    return bar();
}

使用gcc 12.2以-O2进行编译将生成:

baz():
        movl    foo(%rip), %eax
        ret

正如您所看到的,foo = 42;已经过优化,这只是return blk;。注解asm,以便进行比较:

baz():
        movl    $42, foo(%rip)
        movl    $42, %eax
        ret

(The对bar的调用在两种情况下都是内联的。)

相关问题