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が生成されているのが分かる。