]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: implement xadduintptr and update system mstats using it
authorSrdjan Petrovic <spetrovic@google.com>
Thu, 16 Apr 2015 21:32:18 +0000 (14:32 -0700)
committerDavid Crawshaw <crawshaw@golang.org>
Fri, 24 Apr 2015 16:53:26 +0000 (16:53 +0000)
The motivation is that sysAlloc/Free() currently aren't safe to be
called without a valid G, because arm's xadd64() uses locks that require
a valid G.

The solution here was proposed by Dmitry Vyukov: use xadduintptr()
instead of xadd64(), until arm can support xadd64 on all of its
architectures (not a trivial task for arm).

Change-Id: I250252079357ea2e4360e1235958b1c22051498f
Reviewed-on: https://go-review.googlesource.com/9002
Reviewed-by: Dmitry Vyukov <dvyukov@google.com>
18 files changed:
src/runtime/asm_amd64.s
src/runtime/asm_amd64p32.s
src/runtime/atomic_386.go
src/runtime/atomic_amd64x.go
src/runtime/atomic_arm.go
src/runtime/atomic_arm64.go
src/runtime/atomic_ppc64x.go
src/runtime/atomic_test.go [new file with mode: 0644]
src/runtime/export_test.go
src/runtime/malloc.go
src/runtime/mem_bsd.go
src/runtime/mem_darwin.go
src/runtime/mem_linux.go
src/runtime/mem_plan9.go
src/runtime/mem_windows.go
src/runtime/mstats.go
src/runtime/os1_darwin.go
src/runtime/os1_linux.go

index 468763f09545f022380feb9450e17cdfd370fd4a..02e25f740281d0c472bf00748398cb7b3e1daa78 100644 (file)
@@ -530,6 +530,9 @@ TEXT runtime·xadd64(SB), NOSPLIT, $0-24
        MOVQ    AX, ret+16(FP)
        RET
 
+TEXT runtime·xadduintptr(SB), NOSPLIT, $0-24
+       JMP     runtime·xadd64(SB)
+
 TEXT runtime·xchg(SB), NOSPLIT, $0-20
        MOVQ    ptr+0(FP), BX
        MOVL    new+8(FP), AX
index 23e2cb9662e2d8731178be0f5cf7b553be69c66d..5e9210fca9c2b1e845382778729d160726cf8652 100644 (file)
@@ -483,6 +483,9 @@ TEXT runtime·xadd64(SB), NOSPLIT, $0-24
        MOVQ    AX, ret+16(FP)
        RET
 
+TEXT runtime·xadduintptr(SB), NOSPLIT, $0-12
+       JMP     runtime·xadd(SB)
+
 TEXT runtime·xchg(SB), NOSPLIT, $0-12
        MOVL    ptr+0(FP), BX
        MOVL    new+4(FP), AX
index 7828c66c98693dcc94c2247abc5e5da46994c7a7..f8d589e33ba686190b3a1d5b45205e896dbdffae 100644 (file)
@@ -32,6 +32,10 @@ func xadd64(ptr *uint64, delta int64) uint64 {
        }
 }
 
+//go:noescape
+//go:linkname xadduintptr runtime.xadd
+func xadduintptr(ptr *uintptr, delta uintptr) uintptr
+
 //go:nosplit
 func xchg64(ptr *uint64, new uint64) uint64 {
        for {
index e539387bc725b8aa18c505aeeccda08fa95798c6..edcc6d665efe0fb7e14f891c64ea8ba9075d8cbc 100644 (file)
@@ -36,6 +36,9 @@ func xadd(ptr *uint32, delta int32) uint32
 //go:noescape
 func xadd64(ptr *uint64, delta int64) uint64
 
+//go:noescape
+func xadduintptr(ptr *uintptr, delta uintptr) uintptr
+
 //go:noescape
 func xchg(ptr *uint32, new uint32) uint32
 
index 75206ab94afd4ae9b68de016743cb78236b45a87..02a1f35ffafe8460e8f43ffcfeb3ab6020502d28 100644 (file)
@@ -27,6 +27,10 @@ func xadd(val *uint32, delta int32) uint32 {
        }
 }
 
+//go:noescape
+//go:linkname xadduintptr runtime.xadd
+func xadduintptr(ptr *uintptr, delta uintptr) uintptr
+
 //go:nosplit
 func xchg(addr *uint32, v uint32) uint32 {
        for {
index 6a78a8dc6eeaab13cda4475fff32cb33736781fd..a377e3e4b3d0a589ddbcd0a171b23fa39519b828 100644 (file)
@@ -12,6 +12,10 @@ func xadd(ptr *uint32, delta int32) uint32
 //go:noescape
 func xadd64(ptr *uint64, delta int64) uint64
 
+//go:noescape
+//go:linkname xadduintptr runtime.xadd64
+func xadduintptr(ptr *uintptr, delta uintptr) uintptr
+
 //go:noescape
 func xchg(ptr *uint32, new uint32) uint32
 
index 17c642d815c50b30c125712ed2b2b93be79f4ba3..b58ee5ae33bd84aa73830d088fdf44aeec18552e 100644 (file)
@@ -14,6 +14,10 @@ func xadd(ptr *uint32, delta int32) uint32
 //go:noescape
 func xadd64(ptr *uint64, delta int64) uint64
 
+//go:noescape
+//go:linkname xadduintptr runtime.xadd64
+func xadduintptr(ptr *uintptr, delta uintptr) uintptr
+
 //go:noescape
 func xchg(ptr *uint32, new uint32) uint32
 
diff --git a/src/runtime/atomic_test.go b/src/runtime/atomic_test.go
new file mode 100644 (file)
index 0000000..2699103
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime_test
+
+import (
+       "runtime"
+       "testing"
+       "unsafe"
+)
+
+func runParallel(N, iter int, f func()) {
+       defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(int(N)))
+       done := make(chan bool)
+       for i := 0; i < N; i++ {
+               go func() {
+                       for j := 0; j < iter; j++ {
+                               f()
+                       }
+                       done <- true
+               }()
+       }
+       for i := 0; i < N; i++ {
+               <-done
+       }
+}
+
+func TestXadduintptr(t *testing.T) {
+       const N = 20
+       const iter = 100000
+       inc := uintptr(100)
+       total := uintptr(0)
+       runParallel(N, iter, func() {
+               runtime.Xadduintptr(&total, inc)
+       })
+       if want := uintptr(N * iter * inc); want != total {
+               t.Fatalf("xadduintpr error, want %d, got %d", want, total)
+       }
+       total = 0
+       runParallel(N, iter, func() {
+               runtime.Xadduintptr(&total, inc)
+               runtime.Xadduintptr(&total, uintptr(-int64(inc)))
+       })
+       if total != 0 {
+               t.Fatalf("xadduintpr total error, want %d, got %d", 0, total)
+       }
+}
+
+// Tests that xadduintptr correctly updates 64-bit values.  The place where
+// we actually do so is mstats.go, functions mSysStat{Inc,Dec}.
+func TestXadduintptrOnUint64(t *testing.T) {
+       if runtime.BigEndian != 0 {
+               // On big endian architectures, we never use xadduintptr to update
+               // 64-bit values and hence we skip the test.  (Note that functions
+               // mSysStat{Inc,Dec} in mstats.go have explicit checks for
+               // big-endianness.)
+               return
+       }
+       const inc = 100
+       val := uint64(0)
+       runtime.Xadduintptr((*uintptr)(unsafe.Pointer(&val)), inc)
+       if inc != val {
+               t.Fatalf("xadduintptr should increase lower-order bits, want %d, got %d", inc, val)
+       }
+}
index a2b098d51ee030178ceaadf77082f86f311553cc..1b5f267b8b25d3ed722897597b1ca0e69a2ae7b1 100644 (file)
@@ -21,6 +21,7 @@ var F64toint = f64toint
 var Entersyscall = entersyscall
 var Exitsyscall = exitsyscall
 var LockedOSThread = lockedOSThread
+var Xadduintptr = xadduintptr
 
 var FuncPC = funcPC
 
@@ -129,3 +130,5 @@ var Write = write
 
 func Envs() []string     { return envs }
 func SetEnvs(e []string) { envs = e }
+
+var BigEndian = _BigEndian
index 4a2d3e3cac7ef0b08388fd9e24fa5a84f2d9d628..5896e74e919482e94c81af8bfce5c79e5db20c88 100644 (file)
@@ -790,7 +790,7 @@ var globalAlloc struct {
 // There is no associated free operation.
 // Intended for things like function/type/debug-related persistent data.
 // If align is 0, uses default align (currently 8).
-func persistentalloc(size, align uintptr, stat *uint64) unsafe.Pointer {
+func persistentalloc(size, align uintptr, sysStat *uint64) unsafe.Pointer {
        const (
                chunk    = 256 << 10
                maxBlock = 64 << 10 // VM reservation granularity is 64K on windows
@@ -811,7 +811,7 @@ func persistentalloc(size, align uintptr, stat *uint64) unsafe.Pointer {
        }
 
        if size >= maxBlock {
-               return sysAlloc(size, stat)
+               return sysAlloc(size, sysStat)
        }
 
        mp := acquirem()
@@ -840,9 +840,9 @@ func persistentalloc(size, align uintptr, stat *uint64) unsafe.Pointer {
                unlock(&globalAlloc.mutex)
        }
 
-       if stat != &memstats.other_sys {
-               xadd64(stat, int64(size))
-               xadd64(&memstats.other_sys, -int64(size))
+       if sysStat != &memstats.other_sys {
+               mSysStatInc(sysStat, size)
+               mSysStatDec(&memstats.other_sys, size)
        }
        return p
 }
index c868977f863fca19a2d341b31ff5dca5f1b3d135..ecab584d04958ad69a3b7a2989c04f12fc1ca867 100644 (file)
@@ -8,13 +8,15 @@ package runtime
 
 import "unsafe"
 
+// Don't split the stack as this function may be invoked without a valid G,
+// which prevents us from allocating more stack.
 //go:nosplit
-func sysAlloc(n uintptr, stat *uint64) unsafe.Pointer {
+func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
        v := unsafe.Pointer(mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0))
        if uintptr(v) < 4096 {
                return nil
        }
-       xadd64(stat, int64(n))
+       mSysStatInc(sysStat, n)
        return v
 }
 
@@ -25,8 +27,11 @@ func sysUnused(v unsafe.Pointer, n uintptr) {
 func sysUsed(v unsafe.Pointer, n uintptr) {
 }
 
-func sysFree(v unsafe.Pointer, n uintptr, stat *uint64) {
-       xadd64(stat, -int64(n))
+// Don't split the stack as this function may be invoked without a valid G,
+// which prevents us from allocating more stack.
+//go:nosplit
+func sysFree(v unsafe.Pointer, n uintptr, sysStat *uint64) {
+       mSysStatDec(sysStat, n)
        munmap(v, n)
 }
 
@@ -51,10 +56,10 @@ func sysReserve(v unsafe.Pointer, n uintptr, reserved *bool) unsafe.Pointer {
        return p
 }
 
-func sysMap(v unsafe.Pointer, n uintptr, reserved bool, stat *uint64) {
+func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
        const _ENOMEM = 12
 
-       xadd64(stat, int64(n))
+       mSysStatInc(sysStat, n)
 
        // On 64-bit, we don't actually have v reserved, so tread carefully.
        if !reserved {
index e3b8845549bed605b62cf8ab755a853280f4ee90..3bebd97c572c991357fb571a471303df177400c4 100644 (file)
@@ -6,13 +6,15 @@ package runtime
 
 import "unsafe"
 
+// Don't split the stack as this function may be invoked without a valid G,
+// which prevents us from allocating more stack.
 //go:nosplit
-func sysAlloc(n uintptr, stat *uint64) unsafe.Pointer {
+func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
        v := (unsafe.Pointer)(mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0))
        if uintptr(v) < 4096 {
                return nil
        }
-       xadd64(stat, int64(n))
+       mSysStatInc(sysStat, n)
        return v
 }
 
@@ -24,8 +26,11 @@ func sysUnused(v unsafe.Pointer, n uintptr) {
 func sysUsed(v unsafe.Pointer, n uintptr) {
 }
 
-func sysFree(v unsafe.Pointer, n uintptr, stat *uint64) {
-       xadd64(stat, -int64(n))
+// Don't split the stack as this function may be invoked without a valid G,
+// which prevents us from allocating more stack.
+//go:nosplit
+func sysFree(v unsafe.Pointer, n uintptr, sysStat *uint64) {
+       mSysStatDec(sysStat, n)
        munmap(v, n)
 }
 
@@ -46,8 +51,8 @@ const (
        _ENOMEM = 12
 )
 
-func sysMap(v unsafe.Pointer, n uintptr, reserved bool, stat *uint64) {
-       xadd64(stat, int64(n))
+func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
+       mSysStatInc(sysStat, n)
        p := (unsafe.Pointer)(mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0))
        if uintptr(p) == _ENOMEM {
                throw("runtime: out of memory")
index a78a03ee5c1ad08ce9c623a89d022e5c6d3ccc4b..f988e75a171c4cdf7884234c29f410525e4709d4 100644 (file)
@@ -48,8 +48,10 @@ func mmap_fixed(v unsafe.Pointer, n uintptr, prot, flags, fd int32, offset uint3
        return p
 }
 
+// Don't split the stack as this method may be invoked without a valid G, which
+// prevents us from allocating more stack.
 //go:nosplit
-func sysAlloc(n uintptr, stat *uint64) unsafe.Pointer {
+func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
        p := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
        if uintptr(p) < 4096 {
                if uintptr(p) == _EACCES {
@@ -62,7 +64,7 @@ func sysAlloc(n uintptr, stat *uint64) unsafe.Pointer {
                }
                return nil
        }
-       xadd64(stat, int64(n))
+       mSysStatInc(sysStat, n)
        return p
 }
 
@@ -93,8 +95,11 @@ func sysUsed(v unsafe.Pointer, n uintptr) {
        }
 }
 
-func sysFree(v unsafe.Pointer, n uintptr, stat *uint64) {
-       xadd64(stat, -int64(n))
+// Don't split the stack as this function may be invoked without a valid G,
+// which prevents us from allocating more stack.
+//go:nosplit
+func sysFree(v unsafe.Pointer, n uintptr, sysStat *uint64) {
+       mSysStatDec(sysStat, n)
        munmap(v, n)
 }
 
@@ -128,8 +133,8 @@ func sysReserve(v unsafe.Pointer, n uintptr, reserved *bool) unsafe.Pointer {
        return p
 }
 
-func sysMap(v unsafe.Pointer, n uintptr, reserved bool, stat *uint64) {
-       xadd64(stat, int64(n))
+func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
+       mSysStatInc(sysStat, n)
 
        // On 64-bit, we don't actually have v reserved, so tread carefully.
        if !reserved {
index 4dc8a6119ad129f373a3a87b3fbdae650cd79e12..755887f50eda118e829bcf979fdabe7a4e5e1135 100644 (file)
@@ -130,19 +130,19 @@ func sbrk(n uintptr) unsafe.Pointer {
        return unsafe.Pointer(bl)
 }
 
-func sysAlloc(n uintptr, stat *uint64) unsafe.Pointer {
+func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
        lock(&memlock)
        p := memAlloc(n)
        memCheck()
        unlock(&memlock)
        if p != nil {
-               xadd64(stat, int64(n))
+               mSysStatInc(sysStat, n)
        }
        return p
 }
 
-func sysFree(v unsafe.Pointer, n uintptr, stat *uint64) {
-       xadd64(stat, -int64(n))
+func sysFree(v unsafe.Pointer, n uintptr, sysStat *uint64) {
+       mSysStatDec(sysStat, n)
        lock(&memlock)
        memFree(v, n)
        memCheck()
@@ -155,10 +155,10 @@ func sysUnused(v unsafe.Pointer, n uintptr) {
 func sysUsed(v unsafe.Pointer, n uintptr) {
 }
 
-func sysMap(v unsafe.Pointer, n uintptr, reserved bool, stat *uint64) {
+func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
        // sysReserve has already allocated all heap memory,
        // but has not adjusted stats.
-       xadd64(stat, int64(n))
+       mSysStatInc(sysStat, n)
 }
 
 func sysFault(v unsafe.Pointer, n uintptr) {
index a800ccae1db8b4feb39181ccd800083b153b2c40..42aa7fb4ebd390a68c5bd9a24b506876932d852d 100644 (file)
@@ -18,9 +18,11 @@ const (
        _PAGE_NOACCESS  = 0x0001
 )
 
+// Don't split the stack as this function may be invoked without a valid G,
+// which prevents us from allocating more stack.
 //go:nosplit
-func sysAlloc(n uintptr, stat *uint64) unsafe.Pointer {
-       xadd64(stat, int64(n))
+func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
+       mSysStatInc(sysStat, n)
        return unsafe.Pointer(stdcall4(_VirtualAlloc, 0, n, _MEM_COMMIT|_MEM_RESERVE, _PAGE_READWRITE))
 }
 
@@ -74,8 +76,11 @@ func sysUsed(v unsafe.Pointer, n uintptr) {
        }
 }
 
-func sysFree(v unsafe.Pointer, n uintptr, stat *uint64) {
-       xadd64(stat, -int64(n))
+// Don't split the stack as this function may be invoked without a valid G,
+// which prevents us from allocating more stack.
+//go:nosplit
+func sysFree(v unsafe.Pointer, n uintptr, sysStat *uint64) {
+       mSysStatDec(sysStat, n)
        r := stdcall3(_VirtualFree, uintptr(v), 0, _MEM_RELEASE)
        if r == 0 {
                throw("runtime: failed to release pages")
@@ -100,8 +105,8 @@ func sysReserve(v unsafe.Pointer, n uintptr, reserved *bool) unsafe.Pointer {
        return unsafe.Pointer(stdcall4(_VirtualAlloc, 0, n, _MEM_RESERVE, _PAGE_READWRITE))
 }
 
-func sysMap(v unsafe.Pointer, n uintptr, reserved bool, stat *uint64) {
-       xadd64(stat, int64(n))
+func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
+       mSysStatInc(sysStat, n)
        p := stdcall4(_VirtualAlloc, uintptr(v), n, _MEM_COMMIT, _PAGE_READWRITE)
        if p != uintptr(v) {
                throw("runtime: cannot map pages in arena address space")
index 3711c397cced5f8cab77fe7ab6eaa314a289a30f..270449d0fd455e066bdfec3b700b6db9f3e86fb8 100644 (file)
@@ -353,3 +353,41 @@ func purgecachedstats(c *mcache) {
                c.local_nsmallfree[i] = 0
        }
 }
+
+// Atomically increases a given *system* memory stat.  We are counting on this
+// stat never overflowing a uintptr, so this function must only be used for
+// system memory stats.
+//
+// The current implementation for little endian architectures is based on
+// xadduintptr(), which is less than ideal: xadd64() should really be used.
+// Using xadduintptr() is a stop-gap solution until arm supports xadd64() that
+// doesn't use locks.  (Locks are a problem as they require a valid G, which
+// restricts their useability.)
+//
+// A side-effect of using xadduintptr() is that we need to check for
+// overflow errors.
+//go:nosplit
+func mSysStatInc(sysStat *uint64, n uintptr) {
+       if _BigEndian != 0 {
+               xadd64(sysStat, int64(n))
+               return
+       }
+       if val := xadduintptr((*uintptr)(unsafe.Pointer(sysStat)), n); val < n {
+               print("runtime: stat overflow: val ", val, ", n ", n, "\n")
+               exit(2)
+       }
+}
+
+// Atomically decreases a given *system* memory stat.  Same comments as
+// mSysStatInc apply.
+//go:nosplit
+func mSysStatDec(sysStat *uint64, n uintptr) {
+       if _BigEndian != 0 {
+               xadd64(sysStat, -int64(n))
+               return
+       }
+       if val := xadduintptr((*uintptr)(unsafe.Pointer(sysStat)), uintptr(-int64(n))); val+n < n {
+               print("runtime: stat underflow: val ", val, ", n ", n, "\n")
+               exit(2)
+       }
+}
index a4c987470017bf37977e2cbaa46270eb9a0dc7ca..10cf460f7fe69b3af19881a14030287572fd5ed2 100644 (file)
@@ -98,8 +98,7 @@ func newosproc(mp *m, stk unsafe.Pointer) {
 //
 //go:nosplit
 func newosproc0(stacksize uintptr, fn unsafe.Pointer, fnarg uintptr) {
-       var dummy uint64
-       stack := sysAlloc(stacksize, &dummy)
+       stack := sysAlloc(stacksize, &memstats.stacks_sys)
        if stack == nil {
                write(2, unsafe.Pointer(&failallocatestack[0]), int32(len(failallocatestack)))
                exit(1)
index a286dcd960625fea1025a41d9da5060b2f27bde5..e4b18c79b314b291f75328046729d9e97fc60ab7 100644 (file)
@@ -146,8 +146,7 @@ func newosproc(mp *m, stk unsafe.Pointer) {
 // Version of newosproc that doesn't require a valid G.
 //go:nosplit
 func newosproc0(stacksize uintptr, fn unsafe.Pointer) {
-       var dummy uint64
-       stack := sysAlloc(stacksize, &dummy)
+       stack := sysAlloc(stacksize, &memstats.stacks_sys)
        if stack == nil {
                write(2, unsafe.Pointer(&failallocatestack[0]), int32(len(failallocatestack)))
                exit(1)