Compress paged link-store references

Dense checker link stores still keep a pointer for every occupied slot.
Those tables cost memory and require GC scanning even though values
already live in arenas.

Use four-byte indexes in the page tables. Retain bounded arena blocks
so references stay stable without geometrically over-allocating large
stores. Keep map-backed stores unchanged: compressing those did not
improve allocation usage.
This commit is contained in:
Jake Bailey committed 2026-10-14 09:14:33 -07:00
1 parent 6c976dad5d
commit 455ef7b3ae
3 files changed
+196 -11

No files matched your search

+39
View File
@@ -1,6 +1,7 @@
package core
import (
"math/bits"
"slices"
"unsafe"
)
@@ -69,3 +70,41 @@ func nextArenaSize[T any](size int) int {
size = min(size*2, 256, maxSize)
return size
}
// IndexedArena keeps values at stable addresses and references them with uint32
// indexes instead of Go pointers. Index zero is nil. Unlike Arena, it retains all
// blocks so that an index alone keeps its value reachable through the arena.
type IndexedArena[T any] struct {
blocks [][]T
size uint32
}
const indexedArenaPageShift = 8
func (a *IndexedArena[T]) New() (uint32, *T) {
if a.size == ^uint32(0) {
panic("IndexedArena exhausted")
}
if len(a.blocks) == 0 || len(a.blocks[len(a.blocks)-1]) == cap(a.blocks[len(a.blocks)-1]) {
size := 1 << min(len(a.blocks), indexedArenaPageShift)
a.blocks = append(a.blocks, make([]T, 0, size))
}
block := &a.blocks[len(a.blocks)-1]
*block = (*block)[:len(*block)+1]
a.size++
return a.size, &(*block)[len(*block)-1]
}
func (a *IndexedArena[T]) Get(index uint32) *T {
if index == 0 {
return nil
}
// The initial blocks have sizes 1, 2, 4, ..., 128. Subsequent
// blocks have 256 entries, avoiding geometric over-allocation.
if index < 1<<indexedArenaPageShift {
block := bits.Len32(index) - 1
return &a.blocks[block][index-(1<<block)]
}
index -= 1 << indexedArenaPageShift
return &a.blocks[indexedArenaPageShift+int(index>>indexedArenaPageShift)][index&((1<<indexedArenaPageShift)-1)]
}
+12 -11
View File
@@ -40,12 +40,12 @@ const (
)
// PagedLinkStore implements a sparse-array-like structure for storing elements keyed by dense uint64 keys.
// Elements are allocated in an area, element references are stored in fixed-size pages of 256 entries, and
// an index of pages is maintained in a growable list.
// Elements are allocated in an arena, and compressed indexes are stored in fixed-size pages of 256 entries.
// The pages contain no Go pointers, halving their size on 64-bit hosts and avoiding GC scans of each entry.
type PagedLinkStore[V any] struct {
pages []*[LinkPageSize]*V
arena Arena[V]
pages []*[LinkPageSize]uint32
arena IndexedArena[V]
}
func (s *PagedLinkStore[V]) Get(key uint64) *V {
@@ -56,15 +56,16 @@ func (s *PagedLinkStore[V]) Get(key uint64) *V {
}
page := s.pages[pageIndex]
if page == nil {
page = new([LinkPageSize]*V)
page = new([LinkPageSize]uint32)
s.pages[pageIndex] = page
}
link := page[key&LinkPageMask]
if link == nil {
link = s.arena.New()
page[key&LinkPageMask] = link
index := page[key&LinkPageMask]
if index == 0 {
id, value := s.arena.New()
page[key&LinkPageMask] = id
return value
}
return link
return s.arena.Get(index)
}
func (s *PagedLinkStore[V]) Has(key uint64) bool {
@@ -75,7 +76,7 @@ func (s *PagedLinkStore[V]) TryGet(key uint64) *V {
pageIndex := key >> LinkPageShift
if int(pageIndex) < len(s.pages) {
if page := s.pages[pageIndex]; page != nil {
return page[key&LinkPageMask]
return s.arena.Get(page[key&LinkPageMask])
}
}
return nil
+145
View File
@@ -0,0 +1,145 @@
package core_test
import (
"fmt"
"runtime"
"testing"
"github.com/microsoft/TypeScript/tsc/internal/core"
"gotest.tools/v3/assert"
)
func TestLinkStore(t *testing.T) {
t.Parallel()
type value struct {
number int
text *string
}
var store core.LinkStore[int, value]
assert.Assert(t, !store.Has(0))
assert.Assert(t, store.TryGet(0) == nil)
values := make([]*value, 4096)
for i := range values {
v := store.Get(i)
assert.Equal(t, *v, value{})
v.number = i
v.text = new("retained")
values[i] = v
}
runtime.GC()
for i, v := range values {
assert.Assert(t, store.Has(i))
assert.Equal(t, store.Get(i), v)
assert.Equal(t, store.TryGet(i), v)
assert.Equal(t, v.number, i)
assert.Equal(t, *v.text, "retained")
}
assert.Assert(t, !store.Has(len(values)))
assert.Assert(t, store.TryGet(len(values)) == nil)
}
func TestPagedLinkStore(t *testing.T) {
t.Parallel()
var store core.PagedLinkStore[int]
keys := []uint64{0, 255, 256, 65535, 16777215}
values := make([]*int, len(keys))
for _, key := range keys {
assert.Assert(t, !store.Has(key))
}
for i, key := range keys {
v := store.Get(key)
assert.Equal(t, *v, 0)
*v = i + 1
values[i] = v
}
runtime.GC()
for i, key := range keys {
assert.Assert(t, store.Has(key))
assert.Equal(t, store.Get(key), values[i])
assert.Equal(t, *store.TryGet(key), i+1)
}
assert.Assert(t, !store.Has(1))
assert.Assert(t, store.TryGet(1) == nil)
}
func BenchmarkLinkStore(b *testing.B) {
type value struct {
typ *int
flags uint32
}
b.ReportAllocs()
for b.Loop() {
var store core.LinkStore[int, value]
for i := range 10000 {
store.Get(i).flags = uint32(i)
}
for i := range 10000 {
if store.Get(i).flags != uint32(i) {
b.Fatal("link changed during arena growth")
}
}
}
}
func TestIndexedArena(t *testing.T) {
t.Parallel()
type value struct {
number int
text *string
}
var arena core.IndexedArena[value]
assert.Assert(t, arena.Get(0) == nil)
values := make([]*value, 4096)
for i := range values {
index, v := arena.New()
assert.Equal(t, index, uint32(i+1))
assert.Equal(t, *v, value{})
v.number = i
v.text = new("retained")
values[i] = v
}
runtime.GC()
for i, v := range values {
assert.Equal(t, arena.Get(uint32(i+1)), v)
assert.Equal(t, v.number, i)
assert.Equal(t, *v.text, "retained")
}
}
func TestIndexedArenaReachability(t *testing.T) {
t.Parallel()
var arena core.IndexedArena[*string]
for i := range 4096 {
_, value := arena.New()
*value = new(fmt.Sprint(i))
}
runtime.GC()
for i := range 4096 {
assert.Equal(t, **arena.Get(uint32(i + 1)), fmt.Sprint(i))
}
}
func BenchmarkPagedLinkStore(b *testing.B) {
for _, stride := range []uint64{1, 16, 256} {
b.Run(fmt.Sprint(stride), func(b *testing.B) {
type value struct {
typ *int
flags uint32
}
b.ReportAllocs()
for b.Loop() {
var store core.PagedLinkStore[value]
for i := range 10000 {
store.Get(uint64(i) * stride).flags = uint32(i)
}
for i := range 10000 {
if store.Get(uint64(i)*stride).flags != uint32(i) {
b.Fatal("link changed during arena growth")
}
}
}
})
}
}