From 4809bc51c5ad4f07cad427faff4972dd7f083a54 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 23 Apr 2019 20:21:58 +0200 Subject: [PATCH] compiler: implement atomic operations in package sync/atomic --- builtins.go | 16 +++++- compiler/atomic.go | 114 +++++++++++++++++++++++++++++++++++++++++++ compiler/compiler.go | 15 +++--- main_test.go | 3 ++ testdata/atomic.go | 80 ++++++++++++++++++++++++++++++ testdata/atomic.txt | 35 +++++++++++++ 6 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 compiler/atomic.go create mode 100644 testdata/atomic.go create mode 100644 testdata/atomic.txt diff --git a/builtins.go b/builtins.go index a34001a77c..a2761e4dc1 100644 --- a/builtins.go +++ b/builtins.go @@ -140,7 +140,7 @@ var genericBuiltins = []string{ "umodti3.c", } -var aeabiBuiltins = []string{ +var armBuiltins = []string{ "arm/aeabi_cdcmp.S", "arm/aeabi_cdcmpeq_check_nan.c", "arm/aeabi_cfcmp.S", @@ -158,12 +158,24 @@ var aeabiBuiltins = []string{ "arm/aeabi_memset.S", "arm/aeabi_uidivmod.S", "arm/aeabi_uldivmod.S", + "arm/sync_fetch_and_add_4.S", + "arm/sync_fetch_and_add_8.S", + "arm/sync_fetch_and_and_4.S", + "arm/sync_fetch_and_and_8.S", + "arm/sync_fetch_and_nand_4.S", + "arm/sync_fetch_and_nand_8.S", + "arm/sync_fetch_and_or_4.S", + "arm/sync_fetch_and_or_8.S", + "arm/sync_fetch_and_sub_4.S", + "arm/sync_fetch_and_sub_8.S", + "arm/sync_fetch_and_xor_4.S", + "arm/sync_fetch_and_xor_8.S", } func builtinFiles(target string) []string { builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins if strings.HasPrefix(target, "arm") { - builtins = append(builtins, aeabiBuiltins...) + builtins = append(builtins, armBuiltins...) } return builtins } diff --git a/compiler/atomic.go b/compiler/atomic.go new file mode 100644 index 0000000000..ab75793c2d --- /dev/null +++ b/compiler/atomic.go @@ -0,0 +1,114 @@ +package compiler + +import ( + "golang.org/x/tools/go/ssa" + "tinygo.org/x/go-llvm" +) + +func (c *Compiler) emitAtomic(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) { + name := call.Value.(*ssa.Function).Name() + switch name { + case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": + ptr, err := c.parseExpr(frame, call.Args[0]) + if err != nil { + return llvm.Value{}, err + } + val, err := c.parseExpr(frame, call.Args[1]) + if err != nil { + return llvm.Value{}, err + } + if c.GOARCH == "wasm" { + ptrVal := c.builder.CreateLoad(ptr, "") + ptrVal = c.builder.CreateAdd(ptrVal, val, "") + c.builder.CreateStore(ptrVal, ptr) + return ptrVal, nil + } + oldVal := c.builder.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) + // Return the new value, not the original value returned by atomicrmw. + return c.builder.CreateAdd(oldVal, val, ""), nil + case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer": + ptr, err := c.parseExpr(frame, call.Args[0]) + if err != nil { + return llvm.Value{}, err + } + val, err := c.parseExpr(frame, call.Args[1]) + if err != nil { + return llvm.Value{}, err + } + if c.GOARCH == "wasm" { + old := c.builder.CreateLoad(ptr, "") + c.builder.CreateStore(val, ptr) + return old, nil + } + isPointer := val.Type().TypeKind() == llvm.PointerTypeKind + if isPointer { + // atomicrmw only supports integers, so cast to an integer. + val = c.builder.CreatePtrToInt(val, c.uintptrType, "") + ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "") + } + oldVal := c.builder.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) + if isPointer { + oldVal = c.builder.CreateIntToPtr(oldVal, c.i8ptrType, "") + } + return oldVal, nil + case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer": + ptr, err := c.parseExpr(frame, call.Args[0]) + if err != nil { + return llvm.Value{}, err + } + old, err := c.parseExpr(frame, call.Args[1]) + if err != nil { + return llvm.Value{}, err + } + newVal, err := c.parseExpr(frame, call.Args[2]) + if err != nil { + return llvm.Value{}, err + } + if c.GOARCH == "wasm" { + swapBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "cas.swap") + nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "cas.next") + frame.blockExits[frame.currentBlock] = nextBlock + val := c.builder.CreateLoad(ptr, "") + swapped := c.builder.CreateICmp(llvm.IntEQ, val, old, "") + c.builder.CreateCondBr(swapped, swapBlock, nextBlock) + c.builder.SetInsertPointAtEnd(swapBlock) + c.builder.CreateStore(newVal, ptr) + c.builder.CreateBr(nextBlock) + c.builder.SetInsertPointAtEnd(nextBlock) + return swapped, nil + } + tuple := c.builder.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true) + swapped := c.builder.CreateExtractValue(tuple, 1, "") + return swapped, nil + case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer": + ptr, err := c.parseExpr(frame, call.Args[0]) + if err != nil { + return llvm.Value{}, err + } + val := c.builder.CreateLoad(ptr, "") + if c.GOARCH == "wasm" { + return val, nil + } + val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) + val.SetAlignment(c.targetData.PrefTypeAlignment(val.Type())) // required + return val, nil + case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer": + ptr, err := c.parseExpr(frame, call.Args[0]) + if err != nil { + return llvm.Value{}, err + } + val, err := c.parseExpr(frame, call.Args[1]) + if err != nil { + return llvm.Value{}, err + } + store := c.builder.CreateStore(val, ptr) + if c.GOARCH == "wasm" { + return store, nil + } + store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) + store.SetAlignment(c.targetData.PrefTypeAlignment(val.Type())) // required + return store, nil + default: + return llvm.Value{}, c.makeError(call.Pos(), "unknown atomic call: "+name) + } +} diff --git a/compiler/compiler.go b/compiler/compiler.go index 4c686556ea..5a04db12ab 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1291,17 +1291,20 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e // Try to call the function directly for trivially static calls. if fn := instr.StaticCallee(); fn != nil { - switch fn.RelString(nil) { - case "device/arm.ReadRegister": + name := fn.RelString(nil) + switch { + case name == "device/arm.ReadRegister": return c.emitReadRegister(instr.Args) - case "device/arm.Asm", "device/avr.Asm": + case name == "device/arm.Asm" || name == "device/avr.Asm": return c.emitAsm(instr.Args) - case "device/arm.AsmFull", "device/avr.AsmFull": + case name == "device/arm.AsmFull" || name == "device/avr.AsmFull": return c.emitAsmFull(frame, instr) - case "device/arm.SVCall0", "device/arm.SVCall1", "device/arm.SVCall2", "device/arm.SVCall3", "device/arm.SVCall4": + case strings.HasPrefix(name, "device/arm.SVCall"): return c.emitSVCall(frame, instr.Args) - case "syscall.Syscall", "syscall.Syscall6", "syscall.Syscall9": + case strings.HasPrefix(name, "syscall.Syscall"): return c.emitSyscall(frame, instr) + case strings.HasPrefix(name, "sync/atomic."): + return c.emitAtomic(frame, instr) } targetFunc := c.ir.GetFunction(fn) diff --git a/main_test.go b/main_test.go index 80832288ee..b339969f93 100644 --- a/main_test.go +++ b/main_test.go @@ -56,6 +56,9 @@ func TestCompiler(t *testing.T) { t.Log("running tests for emulated cortex-m3...") for _, path := range matches { + if path == "testdata/atomic.go" { + continue // TODO: add builtins to compiler-rt + } t.Run(path, func(t *testing.T) { runTest(path, tmpdir, "qemu", t) }) diff --git a/testdata/atomic.go b/testdata/atomic.go new file mode 100644 index 0000000000..8c5fa557c0 --- /dev/null +++ b/testdata/atomic.go @@ -0,0 +1,80 @@ +package main + +import ( + "sync/atomic" + "unsafe" +) + +func main() { + i32 := int32(-5) + println("AddInt32:", atomic.AddInt32(&i32, 8), i32) + + i64 := int64(-5) + println("AddInt64:", atomic.AddInt64(&i64, 8), i64) + + u32 := uint32(5) + println("AddUint32:", atomic.AddUint32(&u32, 8), u32) + + u64 := uint64(5) + println("AddUint64:", atomic.AddUint64(&u64, 8), u64) + + uptr := uintptr(5) + println("AddUintptr:", uint64(atomic.AddUintptr(&uptr, 8)), uint64(uptr)) + + println("SwapInt32:", atomic.SwapInt32(&i32, 33), i32) + println("SwapInt64:", atomic.SwapInt64(&i64, 33), i64) + println("SwapUint32:", atomic.SwapUint32(&u32, 33), u32) + println("SwapUint64:", atomic.SwapUint64(&u64, 33), u64) + println("SwapUintptr:", uint64(atomic.SwapUintptr(&uptr, 33)), uint64(uptr)) + ptr := unsafe.Pointer(&i32) + println("SwapPointer:", atomic.SwapPointer(&ptr, unsafe.Pointer(&u32)) == unsafe.Pointer(&i32), ptr == unsafe.Pointer(&u32)) + + i32 = int32(-5) + println("CompareAndSwapInt32:", atomic.CompareAndSwapInt32(&i32, 5, 3), i32) + println("CompareAndSwapInt32:", atomic.CompareAndSwapInt32(&i32, -5, 3), i32) + + i64 = int64(-5) + println("CompareAndSwapInt64:", atomic.CompareAndSwapInt64(&i64, 5, 3), i64) + println("CompareAndSwapInt64:", atomic.CompareAndSwapInt64(&i64, -5, 3), i64) + + u32 = uint32(5) + println("CompareAndSwapUint32:", atomic.CompareAndSwapUint32(&u32, 4, 3), u32) + println("CompareAndSwapUint32:", atomic.CompareAndSwapUint32(&u32, 5, 3), u32) + + u64 = uint64(5) + println("CompareAndSwapUint64:", atomic.CompareAndSwapUint64(&u64, 4, 3), u64) + println("CompareAndSwapUint64:", atomic.CompareAndSwapUint64(&u64, 5, 3), u64) + + uptr = uintptr(5) + println("CompareAndSwapUintptr:", atomic.CompareAndSwapUintptr(&uptr, 4, 3), uint64(uptr)) + println("CompareAndSwapUintptr:", atomic.CompareAndSwapUintptr(&uptr, 5, 3), uint64(uptr)) + + ptr = unsafe.Pointer(&i32) + println("CompareAndSwapPointer:", atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(&u32), unsafe.Pointer(&i64)), ptr == unsafe.Pointer(&i32)) + println("CompareAndSwapPointer:", atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(&i32), unsafe.Pointer(&i64)), ptr == unsafe.Pointer(&i64)) + + println("LoadInt32:", atomic.LoadInt32(&i32)) + println("LoadInt64:", atomic.LoadInt64(&i64)) + println("LoadUint32:", atomic.LoadUint32(&u32)) + println("LoadUint64:", atomic.LoadUint64(&u64)) + println("LoadUintptr:", uint64(atomic.LoadUintptr(&uptr))) + println("LoadPointer:", atomic.LoadPointer(&ptr) == unsafe.Pointer(&i64)) + + atomic.StoreInt32(&i32, -20) + println("StoreInt32:", i32) + + atomic.StoreInt64(&i64, -20) + println("StoreInt64:", i64) + + atomic.StoreUint32(&u32, 20) + println("StoreUint32:", u32) + + atomic.StoreUint64(&u64, 20) + println("StoreUint64:", u64) + + atomic.StoreUintptr(&uptr, 20) + println("StoreUintptr:", uint64(uptr)) + + atomic.StorePointer(&ptr, unsafe.Pointer(&uptr)) + println("StorePointer:", ptr == unsafe.Pointer(&uptr)) +} diff --git a/testdata/atomic.txt b/testdata/atomic.txt new file mode 100644 index 0000000000..d1f2ab2937 --- /dev/null +++ b/testdata/atomic.txt @@ -0,0 +1,35 @@ +AddInt32: 3 3 +AddInt64: 3 3 +AddUint32: 13 13 +AddUint64: 13 13 +AddUintptr: 13 13 +SwapInt32: 3 33 +SwapInt64: 3 33 +SwapUint32: 13 33 +SwapUint64: 13 33 +SwapUintptr: 13 33 +SwapPointer: true true +CompareAndSwapInt32: false -5 +CompareAndSwapInt32: true 3 +CompareAndSwapInt64: false -5 +CompareAndSwapInt64: true 3 +CompareAndSwapUint32: false 5 +CompareAndSwapUint32: true 3 +CompareAndSwapUint64: false 5 +CompareAndSwapUint64: true 3 +CompareAndSwapUintptr: false 5 +CompareAndSwapUintptr: true 3 +CompareAndSwapPointer: false true +CompareAndSwapPointer: true true +LoadInt32: 3 +LoadInt64: 3 +LoadUint32: 3 +LoadUint64: 3 +LoadUintptr: 3 +LoadPointer: true +StoreInt32: -20 +StoreInt64: -20 +StoreUint32: 20 +StoreUint64: 20 +StoreUintptr: 20 +StorePointer: true