1

How amd64 function guarantees atomicity?

https://github.com/golang/go/blob/master/src/runtime/internal/atomic/atomic_amd64.go

package atomic

import "unsafe"

// Export some functions via linkname to assembly in sync/atomic.
//go:linkname Load
//go:linkname Loadp
//go:linkname Load64

//go:nosplit
//go:noinline
func Load(ptr *uint32) uint32 {
    return *ptr
}

//go:nosplit
//go:noinline
func Loadp(ptr unsafe.Pointer) unsafe.Pointer {
    return *(*unsafe.Pointer)(ptr)
}

//go:nosplit
//go:noinline
func Load64(ptr *uint64) uint64 {
    return *ptr
}

Unlike arm64 can find assembly implementation, use the LDAR command.

https://github.com/golang/go/blob/master/src/runtime/internal/atomic/atomic_arm64.s

// uint32 ·Load(uint32 volatile* addr)
TEXT ·Load(SB),NOSPLIT,$0-12
    MOVD    ptr+0(FP), R0
    LDARW   (R0), R0
    MOVW    R0, ret+8(FP)
    RET

// uint8 ·Load8(uint8 volatile* addr)
TEXT ·Load8(SB),NOSPLIT,$0-9
    MOVD    ptr+0(FP), R0
    LDARB   (R0), R0
    MOVB    R0, ret+8(FP)
    RET

// uint64 ·Load64(uint64 volatile* addr)
TEXT ·Load64(SB),NOSPLIT,$0-16
    MOVD    ptr+0(FP), R0
    LDAR    (R0), R0
    MOVD    R0, ret+8(FP)
    RET

// void *·Loadp(void *volatile *addr)
TEXT ·Loadp(SB),NOSPLIT,$0-16
    MOVD    ptr+0(FP), R0
    LDAR    (R0), R0
    MOVD    R0, ret+8(FP)
    RET
F566
  • 475
  • 4
  • 8
  • Please [do not post images of text](https://meta.stackoverflow.com/a/285557/720999). – kostix Nov 09 '22 at 12:06
  • 1
    @kostix Ok. I will fix it. – F566 Nov 09 '22 at 12:08
  • 5
    That's because 1) Go guarantees that all variables of primitive types are naturally aligned for their sizes—see "Size and alignment guarantees" in [the spec](https://golang.org/ref/spec), and 2) On amd64, loads of naturally-aligned 64-bit integers are already atomic. – kostix Nov 09 '22 at 12:08
  • Note that alignment guarantees only work for "normal" way of using variables. Say, if you have a `[]byte` and use `unsafe` to interpret a 8-byte chunk of data in this slice, which begins at an address not aligned to 8-byte, as a 64-bit integer, a 64-bit wide load from that location would work on amd64 (i.e. it won't result in a CPU fault) but it won't be atomic anymore. – kostix Nov 09 '22 at 12:11
  • 1
    I would recommend to read [this classic piece](https://preshing.com/20130618/atomic-vs-non-atomic-operations/) to get more perspective. – kostix Nov 09 '22 at 12:13
  • 1
    Notice that they prevent it from inlining, so it can't optimize into surrounding operations and avoid actually doing the load there and then, without compile-time reordering since the compiler has to assume that it might have side effects. [Why is integer assignment on a naturally aligned variable atomic on x86?](//stackoverflow.com/a/36685056) explains why atomicity is guaranteed, along with @kostix's point about Go aligning 64-bit vars. [C++ How is release-and-acquire achieved on x86 only using MOV?](//stackoverflow.com/q/60314179) explains why x86 doesn't need special insn for acq/rel. – Peter Cordes Nov 09 '22 at 12:32
  • 1
    Also [how are barriers/fences and acquire, release semantics implemented microarchitecturally?](https://stackoverflow.com/q/58070428). [Does the MOV x86 instruction implement a C++11 memory\_order\_release atomic store?](https://stackoverflow.com/q/29922747) . So for AMD64, they could get the Go compiler to emit the machine code they wanted with simple source code. Using noinline made it equivalent to having hand-written it in asm, but without having to actually do that, or even use anything like `volatile`. The `noinline` part is critically important, I'm pretty sure. – Peter Cordes Nov 09 '22 at 12:37
  • 1
    Related: [How does a mutex lock and unlock functions prevents CPU reordering?](https://stackoverflow.com/q/50951011) re: why being a non-inline function is sufficient to get compile-time ordering of surrounding code for lock/unlock, or acquire-load / release-store. – Peter Cordes Nov 09 '22 at 12:41
  • 2
    @PeterCordes, FWIW, the `sync/atomic` package the OP refers to is treated specially by the Go compiler, and it considers the functions in it to be intrinsics, so they are in fact inlined, if used directly in the code. TBH I cannot directly answer why they are marked with `//go:noinline` in the source code, and how that interacts with the fact above. I'm now curious to learn. Well, OK, looks like your other comments in this thread answer that one. – kostix Nov 09 '22 at 12:48

0 Answers0