From e161d5a82cfb068472229c707f869e9b9f32f19a Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 2 Feb 2021 22:32:48 +0100 Subject: [PATCH] compiler: work around an ARM backend bug in LLVM Because of a bug in the ARM backend of LLVM, the cmpxchg instruction is lowered using ldrexd/strexd instructions which don't exist on Cortex-M cores. This leads to an "undefined instruction" exception at runtime. Therefore, this patch works around this by lowering directly to a call to the __sync_val_compare_and_swap_8 function, which is what the backend should be doing. For details, see: https://reviews.llvm.org/D95891 To test this patch, you can run the code on a Cortex-M3 or higher microcontroller, for example: tinygo flash -target=pca10040 ./testdata/atomic.go Before this patch, this would trigger an error. With this patch, the behavior is correct. The error (without this patch) could look like this: fatal error: undefined instruction with sp=0x200007cc pc=nil --- compiler/atomic.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/compiler/atomic.go b/compiler/atomic.go index 99e8385a..b9a3e1f4 100644 --- a/compiler/atomic.go +++ b/compiler/atomic.go @@ -1,6 +1,8 @@ package compiler import ( + "strings" + "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) @@ -35,6 +37,31 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) { ptr := b.getValue(call.Args[0]) old := b.getValue(call.Args[1]) newVal := b.getValue(call.Args[2]) + if strings.HasSuffix(name, "64") { + arch := strings.Split(b.Triple, "-")[0] + if strings.HasPrefix(arch, "arm") && strings.HasSuffix(arch, "m") { + // Work around a bug in LLVM, at least LLVM 11: + // https://reviews.llvm.org/D95891 + // Check for armv6m, armv7, armv7em, and perhaps others. + // See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html + compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8") + if compareAndSwap.IsNil() { + // Declare the function if it isn't already declared. + i64Type := b.ctx.Int64Type() + fnType := llvm.FunctionType(i64Type, []llvm.Type{llvm.PointerType(i64Type, 0), i64Type, i64Type}, false) + compareAndSwap = llvm.AddFunction(b.mod, "__sync_val_compare_and_swap_8", fnType) + } + actualOldValue := b.CreateCall(compareAndSwap, []llvm.Value{ptr, old, newVal}, "") + // The __sync_val_compare_and_swap_8 function returns the old + // value. However, we shouldn't return the old value, we should + // return whether the compare/exchange was successful. This is + // easily done by comparing the returned (actual) old value with + // the expected old value passed to + // __sync_val_compare_and_swap_8. + swapped := b.CreateICmp(llvm.IntEQ, old, actualOldValue, "") + return swapped, true + } + } tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true) swapped := b.CreateExtractValue(tuple, 1, "") return swapped, true