Skip to content

Commit 5756b35

Browse files
committed
runtime: align 12-byte objects to 8 bytes on 32-bit systems
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>
1 parent cc2a5cf commit 5756b35

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

src/runtime/malloc.go

+8
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,14 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
10161016
// Align tiny pointer for required (conservative) alignment.
10171017
if size&7 == 0 {
10181018
off = alignUp(off, 8)
1019+
} else if sys.PtrSize == 4 && size == 12 {
1020+
// Conservatively align 12-byte objects to 8 bytes on 32-bit
1021+
// systems so that objects whose first field is a 64-bit
1022+
// value is aligned to 8 bytes and does not cause a fault on
1023+
// atomic access. See issue 37262.
1024+
// TODO(mknyszek): Remove this workaround if/when issue 36606
1025+
// is resolved.
1026+
off = alignUp(off, 8)
10191027
} else if size&3 == 0 {
10201028
off = alignUp(off, 4)
10211029
} else if size&1 == 0 {

src/runtime/malloc_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import (
1212
"os"
1313
"os/exec"
1414
"reflect"
15+
"runtime"
1516
. "runtime"
1617
"strings"
18+
"sync/atomic"
1719
"testing"
1820
"time"
1921
"unsafe"
@@ -168,6 +170,61 @@ func TestTinyAlloc(t *testing.T) {
168170
}
169171
}
170172

173+
var (
174+
tinyByteSink *byte
175+
tinyUint32Sink *uint32
176+
tinyObj12Sink *obj12
177+
)
178+
179+
type obj12 struct {
180+
a uint64
181+
b uint32
182+
}
183+
184+
func TestTinyAllocIssue37262(t *testing.T) {
185+
// Try to cause an alignment access fault
186+
// by atomically accessing the first 64-bit
187+
// value of a tiny-allocated object.
188+
// See issue 37262 for details.
189+
190+
// GC twice, once to reach a stable heap state
191+
// and again to make sure we finish the sweep phase.
192+
runtime.GC()
193+
runtime.GC()
194+
195+
// Make 1-byte allocations until we get a fresh tiny slot.
196+
aligned := false
197+
for i := 0; i < 16; i++ {
198+
tinyByteSink = new(byte)
199+
if uintptr(unsafe.Pointer(tinyByteSink))&0xf == 0xf {
200+
aligned = true
201+
break
202+
}
203+
}
204+
if !aligned {
205+
t.Fatal("unable to get a fresh tiny slot")
206+
}
207+
208+
// Create a 4-byte object so that the current
209+
// tiny slot is partially filled.
210+
tinyUint32Sink = new(uint32)
211+
212+
// Create a 12-byte object, which fits into the
213+
// tiny slot. If it actually gets place there,
214+
// then the field "a" will be improperly aligned
215+
// for atomic access on 32-bit architectures.
216+
// This won't be true if issue 36606 gets resolved.
217+
tinyObj12Sink = new(obj12)
218+
219+
// Try to atomically access "x.a".
220+
atomic.StoreUint64(&tinyObj12Sink.a, 10)
221+
222+
// Clear the sinks.
223+
tinyByteSink = nil
224+
tinyUint32Sink = nil
225+
tinyObj12Sink = nil
226+
}
227+
171228
func TestPageCacheLeak(t *testing.T) {
172229
defer GOMAXPROCS(GOMAXPROCS(1))
173230
leaked := PageCachePagesLeaked()

0 commit comments

Comments
 (0)