Skip to content

Commit 9d0320d

Browse files
randall77gopherbot
authored andcommitted
runtime: align taggable pointers more so we can use low bits for tag
Currently we assume alignment to 8 bytes, so we can steal the low 3 bits. This CL assumes alignment to 512 bytes, so we can steal the low 9 bits. That's 6 extra bits! Aligning to 512 bytes wastes a bit of space but it is not egregious. Most of the objects that we make tagged pointers to are pretty big. Update #49405 Change-Id: I66fc7784ac1be5f12f285de1d7851d5a6871fb75 Reviewed-on: https://go-review.googlesource.com/c/go/+/665815 Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Auto-Submit: Keith Randall <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 702f164 commit 9d0320d

File tree

9 files changed

+65
-34
lines changed

9 files changed

+65
-34
lines changed

src/runtime/export_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,10 +1777,12 @@ func FrameStartLine(f *Frame) int {
17771777

17781778
// PersistentAlloc allocates some memory that lives outside the Go heap.
17791779
// This memory will never be freed; use sparingly.
1780-
func PersistentAlloc(n uintptr) unsafe.Pointer {
1781-
return persistentalloc(n, 0, &memstats.other_sys)
1780+
func PersistentAlloc(n, align uintptr) unsafe.Pointer {
1781+
return persistentalloc(n, align, &memstats.other_sys)
17821782
}
17831783

1784+
const TagAlign = tagAlign
1785+
17841786
// FPCallers works like Callers and uses frame pointer unwinding to populate
17851787
// pcBuf with the return addresses of the physical frames on the stack.
17861788
func FPCallers(pcBuf []uintptr) int {

src/runtime/lfstack.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ type lfstack uint64
2424
func (head *lfstack) push(node *lfnode) {
2525
node.pushcnt++
2626
new := lfstackPack(node, node.pushcnt)
27-
if node1 := lfstackUnpack(new); node1 != node {
28-
print("runtime: lfstack.push invalid packing: node=", node, " cnt=", hex(node.pushcnt), " packed=", hex(new), " -> node=", node1, "\n")
29-
throw("lfstack.push")
30-
}
3127
for {
3228
old := atomic.Load64((*uint64)(head))
3329
node.next = old
@@ -61,15 +57,11 @@ func lfnodeValidate(node *lfnode) {
6157
if base, _, _ := findObject(uintptr(unsafe.Pointer(node)), 0, 0); base != 0 {
6258
throw("lfstack node allocated from the heap")
6359
}
64-
if lfstackUnpack(lfstackPack(node, ^uintptr(0))) != node {
65-
printlock()
66-
println("runtime: bad lfnode address", hex(uintptr(unsafe.Pointer(node))))
67-
throw("bad lfnode address")
68-
}
60+
lfstackPack(node, ^uintptr(0))
6961
}
7062

7163
func lfstackPack(node *lfnode, cnt uintptr) uint64 {
72-
return uint64(taggedPointerPack(unsafe.Pointer(node), cnt))
64+
return uint64(taggedPointerPack(unsafe.Pointer(node), cnt&(1<<tagBits-1)))
7365
}
7466

7567
func lfstackUnpack(val uint64) *lfnode {

src/runtime/lfstack_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type MyNode struct {
2121
// We require lfstack objects to live outside the heap so that
2222
// checkptr passes on the unsafe shenanigans used.
2323
func allocMyNode(data int) *MyNode {
24-
n := (*MyNode)(PersistentAlloc(unsafe.Sizeof(MyNode{})))
24+
n := (*MyNode)(PersistentAlloc(unsafe.Sizeof(MyNode{}), TagAlign))
2525
LFNodeValidate(&n.LFNode)
2626
n.data = data
2727
return n

src/runtime/mgc.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1392,6 +1392,12 @@ type gcBgMarkWorkerNode struct {
13921392
// gcBgMarkWorker().
13931393
m muintptr
13941394
}
1395+
type gcBgMarkWorkerNodePadded struct {
1396+
gcBgMarkWorkerNode
1397+
pad [tagAlign - unsafe.Sizeof(gcBgMarkWorkerNode{}) - gcBgMarkWorkerNodeRedZoneSize]byte
1398+
}
1399+
1400+
const gcBgMarkWorkerNodeRedZoneSize = (16 << 2) * asanenabledBit // redZoneSize(512)
13951401

13961402
func gcBgMarkWorker(ready chan struct{}) {
13971403
gp := getg()
@@ -1400,7 +1406,7 @@ func gcBgMarkWorker(ready chan struct{}) {
14001406
// the stack (see gopark). Prevent deadlock from recursively
14011407
// starting GC by disabling preemption.
14021408
gp.m.preemptoff = "GC worker init"
1403-
node := new(gcBgMarkWorkerNode)
1409+
node := &new(gcBgMarkWorkerNodePadded).gcBgMarkWorkerNode // TODO: technically not allowed in the heap. See comment in tagptr.go.
14041410
gp.m.preemptoff = ""
14051411

14061412
node.gp.set(gp)

src/runtime/mspanset.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,23 @@ const (
5656
spanSetInitSpineCap = 256 // Enough for 1GB heap on 64-bit
5757
)
5858

59-
type spanSetBlock struct {
59+
type spanSetBlockHeader struct {
6060
// Free spanSetBlocks are managed via a lock-free stack.
6161
lfnode
6262

6363
// popped is the number of pop operations that have occurred on
6464
// this block. This number is used to help determine when a block
6565
// may be safely recycled.
6666
popped atomic.Uint32
67+
}
68+
69+
type spanSetBlockHeader2 struct {
70+
spanSetBlockHeader
71+
pad [tagAlign - unsafe.Sizeof(spanSetBlockHeader{})]byte
72+
}
73+
74+
type spanSetBlock struct {
75+
spanSetBlockHeader2
6776

6877
// spans is the set of spans in this block.
6978
spans [spanSetBlockEntries]atomicMSpanPointer
@@ -313,7 +322,7 @@ func (p *spanSetBlockAlloc) alloc() *spanSetBlock {
313322
if s := (*spanSetBlock)(p.stack.pop()); s != nil {
314323
return s
315324
}
316-
return (*spanSetBlock)(persistentalloc(unsafe.Sizeof(spanSetBlock{}), cpu.CacheLineSize, &memstats.gcMiscSys))
325+
return (*spanSetBlock)(persistentalloc(unsafe.Sizeof(spanSetBlock{}), max(cpu.CacheLineSize, tagAlign), &memstats.gcMiscSys))
317326
}
318327

319328
// free returns a spanSetBlock back to the pool.

src/runtime/netpoll.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -688,14 +688,18 @@ func netpollAdjustWaiters(delta int32) {
688688
func (c *pollCache) alloc() *pollDesc {
689689
lock(&c.lock)
690690
if c.first == nil {
691-
const pdSize = unsafe.Sizeof(pollDesc{})
691+
type pollDescPadded struct {
692+
pollDesc
693+
pad [tagAlign - unsafe.Sizeof(pollDesc{})]byte
694+
}
695+
const pdSize = unsafe.Sizeof(pollDescPadded{})
692696
n := pollBlockSize / pdSize
693697
if n == 0 {
694698
n = 1
695699
}
696700
// Must be in non-GC memory because can be referenced
697701
// only from epoll/kqueue internals.
698-
mem := persistentalloc(n*pdSize, 0, &memstats.other_sys)
702+
mem := persistentalloc(n*pdSize, tagAlign, &memstats.other_sys)
699703
for i := uintptr(0); i < n; i++ {
700704
pd := (*pollDesc)(add(mem, i*pdSize))
701705
lockInit(&pd.lock, lockRankPollDesc)

src/runtime/tagptr.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@ package runtime
66

77
// taggedPointer is a pointer with a numeric tag.
88
// The size of the numeric tag is GOARCH-dependent,
9-
// currently at least 10 bits.
9+
// currently at least 16 bits.
1010
// This should only be used with pointers allocated outside the Go heap.
1111
type taggedPointer uint64
1212

1313
// minTagBits is the minimum number of tag bits that we expect.
14-
const minTagBits = 10
14+
const minTagBits = 16
15+
16+
// # of bits we can steal from the bottom. We enforce that all pointers
17+
// that we tag are aligned to at least this many bits.
18+
// Currently the long pole in this tent is pollDesc at 280 bytes. Setting
19+
// 9 here rounds those structs up to 512 bytes.
20+
// gcBgMarkWorkerNode is also small, but we don't make many of those
21+
// so it is ok to waste space on them.
22+
const tagAlignBits = 9
23+
const tagAlign = 1 << tagAlignBits

src/runtime/tagptr_32bit.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import "unsafe"
1111
// The number of bits stored in the numeric tag of a taggedPointer
1212
const taggedPointerBits = 32
1313

14+
// The number of bits allowed in a tag.
15+
const tagBits = 32
16+
1417
// On 32-bit systems, taggedPointer has a 32-bit pointer and 32-bit count.
1518

1619
// taggedPointerPack created a taggedPointer from a pointer and a tag.

src/runtime/tagptr_64bit.go

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,25 @@ const (
2828
// get to really high addresses and panic if it does.
2929
addrBits = 48
3030

31-
// In addition to the 16 bits taken from the top, we can take 3 from the
32-
// bottom, because node must be pointer-aligned, giving a total of 19 bits
33-
// of count.
34-
tagBits = 64 - addrBits + 3
31+
// In addition to the 16 bits taken from the top, we can take 9 from the
32+
// bottom, because we require pointers to be well-aligned (see tagptr.go:tagAlignBits).
33+
// That gives us a total of 25 bits for the tag.
34+
tagBits = 64 - addrBits + tagAlignBits
3535

3636
// On AIX, 64-bit addresses are split into 36-bit segment number and 28-bit
3737
// offset in segment. Segment numbers in the range 0x0A0000000-0x0AFFFFFFF(LSA)
3838
// are available for mmap.
3939
// We assume all tagged addresses are from memory allocated with mmap.
4040
// We use one bit to distinguish between the two ranges.
4141
aixAddrBits = 57
42-
aixTagBits = 64 - aixAddrBits + 3
42+
aixTagBits = 64 - aixAddrBits + tagAlignBits
4343

4444
// riscv64 SV57 mode gives 56 bits of userspace VA.
4545
// tagged pointer code supports it,
4646
// but broader support for SV57 mode is incomplete,
4747
// and there may be other issues (see #54104).
4848
riscv64AddrBits = 56
49-
riscv64TagBits = 64 - riscv64AddrBits + 3
49+
riscv64TagBits = 64 - riscv64AddrBits + tagAlignBits
5050
)
5151

5252
// The number of bits stored in the numeric tag of a taggedPointer
@@ -55,32 +55,38 @@ const taggedPointerBits = (goos.IsAix * aixTagBits) + (goarch.IsRiscv64 * riscv6
5555
// taggedPointerPack created a taggedPointer from a pointer and a tag.
5656
// Tag bits that don't fit in the result are discarded.
5757
func taggedPointerPack(ptr unsafe.Pointer, tag uintptr) taggedPointer {
58+
var t taggedPointer
5859
if GOOS == "aix" {
5960
if GOARCH != "ppc64" {
6061
throw("check this code for aix on non-ppc64")
6162
}
62-
return taggedPointer(uint64(uintptr(ptr))<<(64-aixAddrBits) | uint64(tag&(1<<aixTagBits-1)))
63+
t = taggedPointer(uint64(uintptr(ptr))<<(64-aixAddrBits) | uint64(tag&(1<<aixTagBits-1)))
64+
} else if GOARCH == "riscv64" {
65+
t = taggedPointer(uint64(uintptr(ptr))<<(64-riscv64AddrBits) | uint64(tag&(1<<riscv64TagBits-1)))
66+
} else {
67+
t = taggedPointer(uint64(uintptr(ptr))<<(64-addrBits) | uint64(tag&(1<<tagBits-1)))
6368
}
64-
if GOARCH == "riscv64" {
65-
return taggedPointer(uint64(uintptr(ptr))<<(64-riscv64AddrBits) | uint64(tag&(1<<riscv64TagBits-1)))
69+
if t.pointer() != ptr || t.tag() != tag {
70+
print("runtime: taggedPointerPack invalid packing: ptr=", ptr, " tag=", hex(tag), " packed=", hex(t), " -> ptr=", t.pointer(), " tag=", hex(t.tag()), "\n")
71+
throw("taggedPointerPack")
6672
}
67-
return taggedPointer(uint64(uintptr(ptr))<<(64-addrBits) | uint64(tag&(1<<tagBits-1)))
73+
return t
6874
}
6975

7076
// Pointer returns the pointer from a taggedPointer.
7177
func (tp taggedPointer) pointer() unsafe.Pointer {
7278
if GOARCH == "amd64" {
7379
// amd64 systems can place the stack above the VA hole, so we need to sign extend
7480
// val before unpacking.
75-
return unsafe.Pointer(uintptr(int64(tp) >> tagBits << 3))
81+
return unsafe.Pointer(uintptr(int64(tp) >> tagBits << tagAlignBits))
7682
}
7783
if GOOS == "aix" {
78-
return unsafe.Pointer(uintptr((tp >> aixTagBits << 3) | 0xa<<56))
84+
return unsafe.Pointer(uintptr((tp >> aixTagBits << tagAlignBits) | 0xa<<56))
7985
}
8086
if GOARCH == "riscv64" {
81-
return unsafe.Pointer(uintptr(tp >> riscv64TagBits << 3))
87+
return unsafe.Pointer(uintptr(tp >> riscv64TagBits << tagAlignBits))
8288
}
83-
return unsafe.Pointer(uintptr(tp >> tagBits << 3))
89+
return unsafe.Pointer(uintptr(tp >> tagBits << tagAlignBits))
8490
}
8591

8692
// Tag returns the tag from a taggedPointer.

0 commit comments

Comments
 (0)