Skip to content

Commit

Permalink
runtime: align 12-byte objects to 8 bytes on 32-bit systems
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Austin Clements <[email protected]>
Run-TryBot: Cherry Zhang <[email protected]>
TryBot-Result: Go Bot <[email protected]>
Trust: Michael Knyszek <[email protected]>
  • Loading branch information
mknyszek committed Oct 1, 2020
1 parent cc2a5cf commit 5756b35
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/runtime/malloc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
57 changes: 57 additions & 0 deletions src/runtime/malloc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
"os"
"os/exec"
"reflect"
"runtime"
. "runtime"
"strings"
"sync/atomic"
"testing"
"time"
"unsafe"
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 5756b35

Please sign in to comment.