]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: align 12-byte objects to 8 bytes on 32-bit systems
authorMichael Anthony Knyszek <mknyszek@google.com>
Thu, 10 Sep 2020 21:20:46 +0000 (21:20 +0000)
committerMichael Knyszek <mknyszek@google.com>
Thu, 1 Oct 2020 19:13:03 +0000 (19:13 +0000)
Currently on 32-bit systems 8-byte fields in a struct have an alignment
of 4 bytes, which means that atomic instructions may fault. This issue
is tracked in #36606.

Our current workaround is to allocate memory and put any such atomically
accessed fields at the beginning of the object. This workaround fails
because the tiny allocator might not align the object right. This case
specifically only happens with 12-byte objects because a type's size is
rounded up to its alignment. So if e.g. we have a type like:

type obj struct {
    a uint64
    b byte
}

then its size will be 12 bytes, because "a" will require a 4 byte
alignment. This argument may be extended to all objects of size 9-15
bytes.

So, make this workaround work by specifically aligning such objects to 8
bytes on 32-bit systems. This change leaves a TODO to remove the code
once #36606 gets resolved. It also adds a test which will presumably no
longer be necessary (the compiler should enforce the right alignment)
when it gets resolved as well.

Fixes #37262.

Change-Id: I3a34e5b014b3c37ed2e5e75e62d71d8640aa42bc
Reviewed-on: https://go-review.googlesource.com/c/go/+/254057
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Austin Clements <austin@google.com>
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Michael Knyszek <mknyszek@google.com>

src/runtime/malloc.go
src/runtime/malloc_test.go

index 4fa14996c2e47aaedbe6f338b18d18dda5c3482a..c71f856f09883befcce02967d8e4f3c8b0f5ef64 100644 (file)
@@ -1016,6 +1016,14 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
                        // Align tiny pointer for required (conservative) alignment.
                        if size&7 == 0 {
                                off = alignUp(off, 8)
+                       } else if sys.PtrSize == 4 && size == 12 {
+                               // Conservatively align 12-byte objects to 8 bytes on 32-bit
+                               // systems so that objects whose first field is a 64-bit
+                               // value is aligned to 8 bytes and does not cause a fault on
+                               // atomic access. See issue 37262.
+                               // TODO(mknyszek): Remove this workaround if/when issue 36606
+                               // is resolved.
+                               off = alignUp(off, 8)
                        } else if size&3 == 0 {
                                off = alignUp(off, 4)
                        } else if size&1 == 0 {
index 5c97f548fda312e13396eeb7a9913c710fc9aba0..4ba94d0494e7d214e553cc6e992c5b814bac293b 100644 (file)
@@ -12,8 +12,10 @@ import (
        "os"
        "os/exec"
        "reflect"
+       "runtime"
        . "runtime"
        "strings"
+       "sync/atomic"
        "testing"
        "time"
        "unsafe"
@@ -168,6 +170,61 @@ func TestTinyAlloc(t *testing.T) {
        }
 }
 
+var (
+       tinyByteSink   *byte
+       tinyUint32Sink *uint32
+       tinyObj12Sink  *obj12
+)
+
+type obj12 struct {
+       a uint64
+       b uint32
+}
+
+func TestTinyAllocIssue37262(t *testing.T) {
+       // Try to cause an alignment access fault
+       // by atomically accessing the first 64-bit
+       // value of a tiny-allocated object.
+       // See issue 37262 for details.
+
+       // GC twice, once to reach a stable heap state
+       // and again to make sure we finish the sweep phase.
+       runtime.GC()
+       runtime.GC()
+
+       // Make 1-byte allocations until we get a fresh tiny slot.
+       aligned := false
+       for i := 0; i < 16; i++ {
+               tinyByteSink = new(byte)
+               if uintptr(unsafe.Pointer(tinyByteSink))&0xf == 0xf {
+                       aligned = true
+                       break
+               }
+       }
+       if !aligned {
+               t.Fatal("unable to get a fresh tiny slot")
+       }
+
+       // Create a 4-byte object so that the current
+       // tiny slot is partially filled.
+       tinyUint32Sink = new(uint32)
+
+       // Create a 12-byte object, which fits into the
+       // tiny slot. If it actually gets place there,
+       // then the field "a" will be improperly aligned
+       // for atomic access on 32-bit architectures.
+       // This won't be true if issue 36606 gets resolved.
+       tinyObj12Sink = new(obj12)
+
+       // Try to atomically access "x.a".
+       atomic.StoreUint64(&tinyObj12Sink.a, 10)
+
+       // Clear the sinks.
+       tinyByteSink = nil
+       tinyUint32Sink = nil
+       tinyObj12Sink = nil
+}
+
 func TestPageCacheLeak(t *testing.T) {
        defer GOMAXPROCS(GOMAXPROCS(1))
        leaked := PageCachePagesLeaked()