C++ではスレッド機能がサポートされており、ClangでもLLVM IRがサポートされている。例えば、以下のようなコードでIRを出力してみる。
cpp_atomic.cpp
#include <stdint.h> #include <atomic> int test_32() { std::atomic<int> x(3); int before = x.fetch_add(2); return x.load(); }
今回は簡単化のため、-O3
でコンパイルする。余計な命令が出てこないので、こちらの方が見やすいからだ。
./bin/clang cpp_atomic.cpp -O3 -emit-llvm ./bin/llvm-dis cpp_atomic.bc -o -
; Function Attrs: nounwind uwtable define dso_local i32 @_Z7test_32v() local_unnamed_addr #0 personality i32 (...)* @__gxx_personality_v0 { entry: %x = alloca %"struct.std::atomic", align 4 %0 = bitcast %"struct.std::atomic"* %x to i8* call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %0) #2 %1 = getelementptr inbounds %"struct.std::atomic", %"struct.std::atomic"* %x, i64 0, i32 0, i32 0 store i32 3, i32* %1, align 4 %2 = atomicrmw add i32* %1, i32 2 seq_cst %3 = load atomic i32, i32* %1 seq_cst, align 4 call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %0) #2 ret i32 %3 }
atomicrmw add
が生成されている。以下の表のように、IRを生成することができる。
IR | DAG | Opcode |
---|---|---|
load atomic | AtomicLoad | ATOMIC_CMP_SWAP_XXX |
store atomic | AtomicStore | ATOMIC_SWAP_XXX |
atomicrmw add | AtomicLoadAdd | ATOMIC_LOAD_ADD_XXX |
atomicrmw sub | AtomicLoadSub | ATOMIC_LOAD_SUB_XXX |
atomicrmw xor | AtomicLoadXor | ATOMIC_LOAD_XOR_XXX |
atomicrmw and | AtomicLoadAnd | ATOMIC_LOAD_AND_XXX |
atomicrmw nand | AtomicLoadNand | ATOMIC_LOAD_NAND_XXX |
atomicrmw or | AtomicLoadOr | ATOMIC_LOAD_OR_XXX |
cmpxchg | AtomicCmpSwapWithSuccess | ATOMIC_CMP_SWAP_XXX |
atomicrmw xchg | AtomicLoadSwap | ATOMIC_SWAP_XXX |
MYRISCVXInstrInfo.td
に、可能な限りの生成パタンを追加してみる。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
... // Atomic instructions with 2 source operands (ATOMIC_SWAP & ATOMIC_LOAD_*). class Atomic2Ops<bits<7> opcode, bits<3> funct3, bits<7>funct7, string instr_asm, PatFrag Op, RegisterClass RC> : MYRISCVX_R<opcode, funct3, funct7, (outs RC:$rd), (ins PtrRC:$rs1, RC:$rs2), !strconcat(instr_asm, "\t$rd, $rs1, (${rs2})"), [(set RC:$rd, (Op iPTR:$rs1, RC:$rs2))], IILoad>; ... def ATOMIC_LOAD_ADD_I32 : Atomic2Ops<0b0101111, 0b010, 0b0000000, "amoadd.w" , atomic_load_add_32 , GPR>; def ATOMIC_LOAD_AND_I32 : Atomic2Ops<0b0101111, 0b010, 0b0110000, "amoand.w" , atomic_load_and_32 , GPR>; def ATOMIC_LOAD_OR_I32 : Atomic2Ops<0b0101111, 0b010, 0b0100000, "amoor.w" , atomic_load_or_32 , GPR>; def ATOMIC_LOAD_XOR_I32 : Atomic2Ops<0b0101111, 0b010, 0b0010000, "amoxor.w" , atomic_load_xor_32 , GPR>; def ATOMIC_SWAP_I32 : Atomic2Ops<0b0101111, 0b010, 0b0000100, "amoswap.w", atomic_swap_32 , GPR>;
RISC-Vのamoadd.w
, amoand.w
, amoor
, amoxor.w
, amoswap.w
を定義してみた。
amo{Op}.w rd1, rs1, (rs2)
:rs2
に格納されているアドレスからデータをロードし、その値をrd
に示されるレジスタに格納する。ロードした値に対してrs1
の値をOp
で示される演算を適用し、その結果をrs2
で格納されれているアドレスにストアする。上記操作をアトミックに行う。
とりあえずこの状態でテストコードをコンパイルしてみる。
./bin/llc -filetype=asm cpp_atomic.bc -mcpu=simple32 -march=myriscvx32 -o -
生成したアセンブリを見てみる。
addi x2, x2, -8 .cfi_def_cfa_offset 8 sw x2, 4(x2) # 4-byte Folded Spill .cfi_offset 2, -4 addi x10, zero, 3 sw x10, 0(x2) addi x11, zero, 2 addi x10, x2, 0 amoadd.w x11, x10, (x11) addi x11, zero, 0 addi x12, x11, 0 j __sync_val_compare_and_swap_4 lw x2, 4(x2) # 4-byte Folded Reload addi x2, x2, 8 ret
amoswap.w
が生成されているのが分かる。