llvm ir中atomicrmw和cmpxchg指令必须指定合法内存序:atomicrmw add i32 %ptr, i32 1 seq_cst;cmpxchg i32 %ptr, i32 %old, i32 %new seq_cst,后者返回{ i32, i1 }结构体需extractvalue拆包,且二者均不支持monotonic。

LLVM IR 中 atomicrmw 和 cmpxchg 指令怎么写
LLVM IR 不用 std::atomic 这类 C++ 语法,而是用原生命令直接表达原子读-改-写和比较交换。核心是两个指令:atomicrmw(如 atomicrmw add)和 cmpxchg。它们都强制要求指定 ordering 参数,且必须是合法的内存序枚举值。
例如,对一个 i32* 地址做原子加 1 并返回旧值:
%old = atomicrmw add i32* %ptr, i32 1 seq_cst
注意三点:
-
seq_cst是唯一允许省略syncscope的 ordering;其他如acquire必须显式写syncscope("singlethread")或syncscope("system") -
atomicrmw不支持monotonic—— LLVM 要求更严格的语义映射,实际对应的是unordered(已弃用)或relaxed -
cmpxchg返回的是一个结构体{ i32, i1 },需用extractvalue拆包,不能直接当整数用
memory_order_relaxed 在 LLVM IR 里对应哪个 ordering
对应 monotonic,不是 relaxed。LLVM IR 的内存序枚举中没有 relaxed,monotonic 是它的直接等价物:仅保证单地址上的原子性与线程内顺序,不建立跨线程同步关系。
常见误用是写成 unordered —— 这个值早在 LLVM 14 就被标记为 deprecated,生成时会报 warning,运行时行为未定义。正确写法是:
store i32 42, i32* %ptr, monotonic, align 4
但要注意:monotonic 仅对 store/load 有效;atomicrmw 和 cmpxchg 不接受它,必须用 acquire、release 或更强的序。
syncscope("system") 和 syncscope("singlethread") 有什么实际区别
区别在于同步范围是否跨线程:
-
syncscope("singlethread"):只约束当前线程内原子操作的重排,相当于“伪原子”——编译器不会把它当跨线程同步点,优化器可能大胆合并或删减 -
syncscope("system"):表示该操作参与全系统范围的同步,是默认值(可省略),对应 C++ 的memory_order_seq_cst或acquire/release在多核间的实际语义 - GPU 后端还支持
syncscope("agent")(如 CUDA 的 warp 内同步)或syncscope("workgroup"),这些必须显式声明,否则后端无法生成正确 barrier 指令
漏写 syncscope 不一定报错,但 x86 上可能降级为 singlethread,ARM/AArch64 则可能插入多余 barrier,性能掉得明显。
fence 指令为什么不能替代 atomic 操作
fence 只是内存屏障,不带读写动作,也不作用于特定地址。它只能约束“本线程内 fence 前后的内存访问顺序”,但无法保证对同一地址的多个线程操作互斥或可见。
典型错误是想用 fence acquire + 普通 load 替代 load atomic:
fence acquire %val = load i32, i32* %ptr
这不行——fence 不让 %ptr 的访问变成原子,也不防止其他线程同时写 %ptr 导致撕裂读。真正需要原子性的地方,必须用 load atomic 或 atomicrmw,fence 只能辅助它们建 synchronizes-with 边。
另一个坑:LLVM 的 fence 不接受 monotonic,只支持 acquire、release、acq_rel、seq_cst —— 因为 barrier 本身就是要建同步关系,弱序没意义。











