mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-03 10:37:46 +00:00
compiler: add support for atomic operations
This also implements DisableInterrupts/EnableInterrupts for RISC-V, as those operations were needed to implement a few libcalls.
This commit is contained in:
committed by
Ron Evans
parent
734613c20e
commit
fed433c046
@@ -19,3 +19,19 @@ func Asm(asm string)
|
||||
// You can use {} in the asm string (which expands to a register) to set the
|
||||
// return value.
|
||||
func AsmFull(asm string, regs map[string]interface{}) uintptr
|
||||
|
||||
// DisableInterrupts disables all interrupts, and returns the old interrupt
|
||||
// state.
|
||||
func DisableInterrupts() uintptr {
|
||||
// Note: this can be optimized with a CSRRW instruction, which atomically
|
||||
// swaps the value and returns the old value.
|
||||
mask := MIE.Get()
|
||||
MIE.Set(0)
|
||||
return mask
|
||||
}
|
||||
|
||||
// EnableInterrupts enables all interrupts again. The value passed in must be
|
||||
// the mask returned by DisableInterrupts.
|
||||
func EnableInterrupts(mask uintptr) {
|
||||
MIE.Set(mask)
|
||||
}
|
||||
|
||||
@@ -19,3 +19,86 @@ func align(ptr uintptr) uintptr {
|
||||
func getCurrentStackPointer() uintptr {
|
||||
return arm.AsmFull("mov {}, sp", nil)
|
||||
}
|
||||
|
||||
// Documentation:
|
||||
// * https://llvm.org/docs/Atomics.html
|
||||
// * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
|
||||
//
|
||||
// In the case of Cortex-M, some atomic operations are emitted inline while
|
||||
// others are emitted as libcalls. How many are emitted as libcalls depends on
|
||||
// the MCU core variant (M3 and higher support some 32-bit atomic operations
|
||||
// while M0 and M0+ do not).
|
||||
|
||||
//export __sync_fetch_and_add_4
|
||||
func __sync_fetch_and_add_4(ptr *uint32, value uint32) uint32 {
|
||||
mask := arm.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
*ptr = oldValue + value
|
||||
arm.EnableInterrupts(mask)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
//export __sync_fetch_and_add_8
|
||||
func __sync_fetch_and_add_8(ptr *uint64, value uint64) uint64 {
|
||||
mask := arm.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
*ptr = oldValue + value
|
||||
arm.EnableInterrupts(mask)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
//export __sync_lock_test_and_set_4
|
||||
func __sync_lock_test_and_set_4(ptr *uint32, value uint32) uint32 {
|
||||
mask := arm.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
*ptr = value
|
||||
arm.EnableInterrupts(mask)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
//export __sync_lock_test_and_set_8
|
||||
func __sync_lock_test_and_set_8(ptr *uint64, value uint64) uint64 {
|
||||
mask := arm.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
*ptr = value
|
||||
arm.EnableInterrupts(mask)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
//export __sync_val_compare_and_swap_4
|
||||
func __sync_val_compare_and_swap_4(ptr *uint32, expected, desired uint32) uint32 {
|
||||
mask := arm.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
if oldValue == expected {
|
||||
*ptr = desired
|
||||
}
|
||||
arm.EnableInterrupts(mask)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
//export __sync_val_compare_and_swap_8
|
||||
func __sync_val_compare_and_swap_8(ptr *uint64, expected, desired uint64) uint64 {
|
||||
mask := arm.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
if oldValue == expected {
|
||||
*ptr = desired
|
||||
}
|
||||
arm.EnableInterrupts(mask)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
// The safest thing to do here would just be to disable interrupts for
|
||||
// procPin/procUnpin. Note that a global variable is safe in this case, as any
|
||||
// access to procPinnedMask will happen with interrupts disabled.
|
||||
|
||||
var procPinnedMask uintptr
|
||||
|
||||
//go:linkname procPin sync/atomic.runtime_procPin
|
||||
func procPin() {
|
||||
procPinnedMask = arm.DisableInterrupts()
|
||||
}
|
||||
|
||||
//go:linkname procUnpin sync/atomic.runtime_procUnpin
|
||||
func procUnpin() {
|
||||
arm.EnableInterrupts(procPinnedMask)
|
||||
}
|
||||
|
||||
@@ -17,3 +17,76 @@ func align(ptr uintptr) uintptr {
|
||||
func getCurrentStackPointer() uintptr {
|
||||
return riscv.AsmFull("mv {}, sp", nil)
|
||||
}
|
||||
|
||||
// Documentation:
|
||||
// * https://llvm.org/docs/Atomics.html
|
||||
// * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
|
||||
//
|
||||
// In the case of RISC-V, some operations may be implemented with libcalls if
|
||||
// the operation is too big to be handled by assembly. Officially, these calls
|
||||
// should be implemented with a lock-free algorithm but as (as of this time) all
|
||||
// supported RISC-V chips have a single hart, we can simply disable interrupts
|
||||
// to get the same behavior.
|
||||
|
||||
//export __atomic_load_8
|
||||
func __atomic_load_8(ptr *uint64, ordering int32) uint64 {
|
||||
mask := riscv.DisableInterrupts()
|
||||
value := *ptr
|
||||
riscv.EnableInterrupts(mask)
|
||||
return value
|
||||
}
|
||||
|
||||
//export __atomic_store_8
|
||||
func __atomic_store_8(ptr *uint64, value uint64, ordering int32) {
|
||||
mask := riscv.DisableInterrupts()
|
||||
*ptr = value
|
||||
riscv.EnableInterrupts(mask)
|
||||
}
|
||||
|
||||
//export __atomic_exchange_8
|
||||
func __atomic_exchange_8(ptr *uint64, value uint64, ordering int32) uint64 {
|
||||
mask := riscv.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
*ptr = value
|
||||
riscv.EnableInterrupts(mask)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
//export __atomic_compare_exchange_8
|
||||
func __atomic_compare_exchange_8(ptr, expected *uint64, desired uint64, success_ordering, failure_ordering int32) bool {
|
||||
mask := riscv.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
success := oldValue == *expected
|
||||
if success {
|
||||
*ptr = desired
|
||||
} else {
|
||||
*expected = oldValue
|
||||
}
|
||||
riscv.EnableInterrupts(mask)
|
||||
return success
|
||||
}
|
||||
|
||||
//export __atomic_fetch_add_8
|
||||
func __atomic_fetch_add_8(ptr *uint64, value uint64, ordering int32) uint64 {
|
||||
mask := riscv.DisableInterrupts()
|
||||
oldValue := *ptr
|
||||
*ptr = oldValue + value
|
||||
riscv.EnableInterrupts(mask)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
// The safest thing to do here would just be to disable interrupts for
|
||||
// procPin/procUnpin. Note that a global variable is safe in this case, as any
|
||||
// access to procPinnedMask will happen with interrupts disabled.
|
||||
|
||||
var procPinnedMask uintptr
|
||||
|
||||
//go:linkname procPin sync/atomic.runtime_procPin
|
||||
func procPin() {
|
||||
procPinnedMask = riscv.DisableInterrupts()
|
||||
}
|
||||
|
||||
//go:linkname procUnpin sync/atomic.runtime_procUnpin
|
||||
func procUnpin() {
|
||||
riscv.EnableInterrupts(procPinnedMask)
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package runtime
|
||||
|
||||
// This file contains implementations for the sync/atomic package.
|
||||
|
||||
// All implementations assume there are no goroutines, threads or interrupts.
|
||||
|
||||
//go:linkname loadUint64 sync/atomic.LoadUint64
|
||||
func loadUint64(addr *uint64) uint64 {
|
||||
return *addr
|
||||
}
|
||||
|
||||
//go:linkname storeUint32 sync/atomic.StoreUint32
|
||||
func storeUint32(addr *uint32, val uint32) {
|
||||
*addr = val
|
||||
}
|
||||
|
||||
//go:linkname compareAndSwapUint64 sync/atomic.CompareAndSwapUint64
|
||||
func compareAndSwapUint64(addr *uint64, old, new uint64) bool {
|
||||
if *addr == old {
|
||||
*addr = new
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -94,3 +94,14 @@ func extalloc(size uintptr) unsafe.Pointer {
|
||||
|
||||
//export free
|
||||
func extfree(ptr unsafe.Pointer)
|
||||
|
||||
// TinyGo does not yet support any form of parallelism on an OS, so these can be
|
||||
// left empty.
|
||||
|
||||
//go:linkname procPin sync/atomic.runtime_procPin
|
||||
func procPin() {
|
||||
}
|
||||
|
||||
//go:linkname procUnpin sync/atomic.runtime_procUnpin
|
||||
func procUnpin() {
|
||||
}
|
||||
|
||||
@@ -91,3 +91,14 @@ func ticks() timeUnit
|
||||
func abort() {
|
||||
trap()
|
||||
}
|
||||
|
||||
// TinyGo does not yet support any form of parallelism on WebAssembly, so these
|
||||
// can be left empty.
|
||||
|
||||
//go:linkname procPin sync/atomic.runtime_procPin
|
||||
func procPin() {
|
||||
}
|
||||
|
||||
//go:linkname procUnpin sync/atomic.runtime_procUnpin
|
||||
func procUnpin() {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user