Skip to content

Commit 56ce7ac

Browse files
authored
Keep optional symbol tables out of hot symbol records (#64799)
1 parent 14f7762 commit 56ce7ac

5 files changed

Lines changed: 168 additions & 60 deletions

File tree

‎tsc/internal/ast/symbol.go‎

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"strings"
55
"sync/atomic"
66

7+
"github.com/microsoft/TypeScript/tsc/internal/core"
78
"github.com/microsoft/TypeScript/tsc/internal/debug"
89
)
910

@@ -23,31 +24,81 @@ type symbolData struct {
2324
name string
2425
declarations []*Node
2526
valueDeclaration *Node
26-
members SymbolTable
27-
exports SymbolTable
2827
parent *Symbol
29-
exportSymbol *Symbol
28+
extra *symbolExtra
29+
}
30+
31+
type symbolExtra struct {
32+
members SymbolTable
33+
exports SymbolTable
34+
exportSymbol *Symbol
35+
}
36+
37+
// SymbolExtraArena batches optional symbol metadata without adding an allocator
38+
// pointer to every symbol. References from symbolData keep its blocks alive.
39+
type SymbolExtraArena struct {
40+
arena core.Arena[symbolExtra]
3041
}
3142

3243
func (s *Symbol) Flags() SymbolFlags { return s.flags }
3344
func (s *Symbol) CheckFlags() CheckFlags { return s.checkFlags }
3445
func (s *Symbol) Name() string { return s.data.name }
3546
func (s *Symbol) Declarations() []*Node { return s.data.declarations }
3647
func (s *Symbol) ValueDeclaration() *Node { return s.data.valueDeclaration }
37-
func (s *Symbol) Members() SymbolTable { return s.data.members }
38-
func (s *Symbol) Exports() SymbolTable { return s.data.exports }
3948
func (s *Symbol) Parent() *Symbol { return s.data.parent }
40-
func (s *Symbol) ExportSymbol() *Symbol { return s.data.exportSymbol }
49+
50+
func (s *Symbol) Members() SymbolTable {
51+
if s.data.extra == nil {
52+
return nil
53+
}
54+
return s.data.extra.members
55+
}
56+
57+
func (s *Symbol) Exports() SymbolTable {
58+
if s.data.extra == nil {
59+
return nil
60+
}
61+
return s.data.extra.exports
62+
}
63+
64+
func (s *Symbol) ExportSymbol() *Symbol {
65+
if s.data.extra == nil {
66+
return nil
67+
}
68+
return s.data.extra.exportSymbol
69+
}
4170

4271
func (s *Symbol) SetFlags(value SymbolFlags) { s.flags = value }
4372
func (s *Symbol) SetCheckFlags(value CheckFlags) { s.checkFlags = value }
4473
func (s *Symbol) SetName(value string) { s.data.name = value }
4574
func (s *Symbol) SetDeclarations(value []*Node) { s.data.declarations = value }
4675
func (s *Symbol) SetValueDeclaration(value *Node) { s.data.valueDeclaration = value }
47-
func (s *Symbol) SetMembers(value SymbolTable) { s.data.members = value }
48-
func (s *Symbol) SetExports(value SymbolTable) { s.data.exports = value }
4976
func (s *Symbol) SetParent(value *Symbol) { s.data.parent = value }
50-
func (s *Symbol) SetExportSymbol(value *Symbol) { s.data.exportSymbol = value }
77+
78+
func (s *Symbol) ensureExtra(arena *SymbolExtraArena) *symbolExtra {
79+
if s.data.extra == nil {
80+
s.data.extra = arena.arena.New()
81+
}
82+
return s.data.extra
83+
}
84+
85+
func (s *Symbol) SetMembers(value SymbolTable, arena *SymbolExtraArena) {
86+
if value != nil || s.data.extra != nil {
87+
s.ensureExtra(arena).members = value
88+
}
89+
}
90+
91+
func (s *Symbol) SetExports(value SymbolTable, arena *SymbolExtraArena) {
92+
if value != nil || s.data.extra != nil {
93+
s.ensureExtra(arena).exports = value
94+
}
95+
}
96+
97+
func (s *Symbol) SetExportSymbol(value *Symbol, arena *SymbolExtraArena) {
98+
if value != nil || s.data.extra != nil {
99+
s.ensureExtra(arena).exportSymbol = value
100+
}
101+
}
51102

52103
// SymbolWithData is a helper structure that contains both a Symbol and its associated symbolData.
53104
type SymbolWithData struct {

‎tsc/internal/ast/symbol_test.go‎

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package ast_test
22

33
import (
44
"reflect"
5+
"runtime"
56
"testing"
7+
"unsafe"
68

79
"github.com/microsoft/TypeScript/tsc/internal/ast"
810
"gotest.tools/v3/assert"
@@ -17,6 +19,7 @@ func TestSymbolFieldsArePrivate(t *testing.T) {
1719

1820
func TestSymbolAccessors(t *testing.T) {
1921
t.Parallel()
22+
var arena ast.SymbolExtraArena
2023
symbol := ast.NewSymbol()
2124
assert.Equal(t, symbol.Flags(), ast.SymbolFlagsNone)
2225
assert.Equal(t, symbol.CheckFlags(), ast.CheckFlags(0))
@@ -43,10 +46,10 @@ func TestSymbolAccessors(t *testing.T) {
4346
symbol.SetName("C")
4447
symbol.SetDeclarations(declarations)
4548
symbol.SetValueDeclaration(declaration)
46-
symbol.SetMembers(members)
47-
symbol.SetExports(exports)
49+
symbol.SetMembers(members, &arena)
50+
symbol.SetExports(exports, &arena)
4851
symbol.SetParent(parent)
49-
symbol.SetExportSymbol(exportSymbol)
52+
symbol.SetExportSymbol(exportSymbol, &arena)
5053

5154
assert.Equal(t, symbol.Flags(), ast.SymbolFlagsClass|ast.SymbolFlagsTransient)
5255
assert.Equal(t, symbol.CheckFlags(), ast.CheckFlagsReadonly)
@@ -72,10 +75,10 @@ func TestSymbolAccessors(t *testing.T) {
7275
symbol.SetName("")
7376
symbol.SetDeclarations(nil)
7477
symbol.SetValueDeclaration(nil)
75-
symbol.SetMembers(nil)
76-
symbol.SetExports(nil)
78+
symbol.SetMembers(nil, &arena)
79+
symbol.SetExports(nil, &arena)
7780
symbol.SetParent(nil)
78-
symbol.SetExportSymbol(nil)
81+
symbol.SetExportSymbol(nil, &arena)
7982

8083
assert.Equal(t, symbol.Flags(), ast.SymbolFlagsNone)
8184
assert.Equal(t, symbol.CheckFlags(), ast.CheckFlags(0))
@@ -90,24 +93,76 @@ func TestSymbolAccessors(t *testing.T) {
9093

9194
func TestSymbolTableInitialization(t *testing.T) {
9295
t.Parallel()
96+
var arena ast.SymbolExtraArena
9397
symbol := ast.NewSymbol()
9498
member := ast.NewSymbol()
9599
export := ast.NewSymbol()
96100

97-
members := ast.GetMembers(symbol)
101+
members := ast.GetMembers(symbol, &arena)
98102
members["member"] = member
99103
assert.Equal(t, symbol.Members()["member"], member)
100-
assert.Equal(t, ast.GetMembers(symbol)["member"], member)
104+
assert.Equal(t, ast.GetMembers(symbol, &arena)["member"], member)
101105

102-
exports := ast.GetExports(symbol)
106+
exports := ast.GetExports(symbol, &arena)
103107
exports["export"] = export
104108
assert.Equal(t, symbol.Exports()["export"], export)
105-
assert.Equal(t, ast.GetExports(symbol)["export"], export)
109+
assert.Equal(t, ast.GetExports(symbol, &arena)["export"], export)
106110

107-
symbol.SetMembers(nil)
108-
symbol.SetExports(nil)
109-
assert.Assert(t, ast.GetMembers(symbol) != nil)
110-
assert.Assert(t, ast.GetExports(symbol) != nil)
111+
symbol.SetMembers(nil, &arena)
112+
symbol.SetExports(nil, &arena)
113+
assert.Assert(t, ast.GetMembers(symbol, &arena) != nil)
114+
assert.Assert(t, ast.GetExports(symbol, &arena) != nil)
111115
assert.Equal(t, len(symbol.Members()), 0)
112116
assert.Equal(t, len(symbol.Exports()), 0)
113117
}
118+
119+
func TestSymbolHotRecordLayout(t *testing.T) {
120+
t.Parallel()
121+
assert.Equal(t, unsafe.Sizeof(ast.SymbolWithData{}), unsafe.Sizeof(ast.Symbol{})+8*unsafe.Sizeof((*ast.Node)(nil)))
122+
}
123+
124+
func TestSymbolNilExtraFieldsDoNotAllocate(t *testing.T) { //nolint:paralleltest
125+
var arena ast.SymbolExtraArena
126+
symbol := ast.NewSymbol()
127+
allocs := testing.AllocsPerRun(10, func() {
128+
symbol.SetMembers(nil, &arena)
129+
symbol.SetExports(nil, &arena)
130+
symbol.SetExportSymbol(nil, &arena)
131+
})
132+
assert.Equal(t, allocs, float64(0))
133+
assert.Assert(t, reflect.ValueOf(symbol).Elem().FieldByName("data").Elem().FieldByName("extra").IsNil())
134+
}
135+
136+
func TestSymbolSharedExtraData(t *testing.T) {
137+
t.Parallel()
138+
var arena ast.SymbolExtraArena
139+
var otherArena ast.SymbolExtraArena
140+
symbol := ast.NewSymbol()
141+
alias := ast.NewSymbol()
142+
alias.SetSymbolData(symbol)
143+
member := ast.NewSymbol()
144+
export := ast.NewSymbol()
145+
alias.SetMembers(ast.SymbolTable{"member": member}, &arena)
146+
symbol.SetExports(ast.SymbolTable{"export": export}, &otherArena)
147+
alias.SetExportSymbol(export, &otherArena)
148+
runtime.GC()
149+
assert.Equal(t, symbol.Members()["member"], member)
150+
assert.Equal(t, alias.Exports()["export"], export)
151+
assert.Equal(t, symbol.ExportSymbol(), export)
152+
symbol.SetMembers(nil, &otherArena)
153+
alias.SetExports(nil, &arena)
154+
symbol.SetExportSymbol(nil, &arena)
155+
assert.Assert(t, alias.Members() == nil)
156+
assert.Assert(t, symbol.Exports() == nil)
157+
assert.Assert(t, alias.ExportSymbol() == nil)
158+
ast.GetMembers(alias, &otherArena)["new"] = export
159+
ast.GetExports(symbol, &arena)["new"] = member
160+
for range 1024 {
161+
ast.NewSymbol().SetExports(ast.SymbolTable{"export": export}, &arena)
162+
}
163+
arena = ast.SymbolExtraArena{}
164+
otherArena = ast.SymbolExtraArena{}
165+
runtime.GC()
166+
assert.Equal(t, symbol.Members()["new"], export)
167+
assert.Equal(t, alias.Exports()["new"], member)
168+
}

‎tsc/internal/ast/utilities.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ func GetSymbolTable(data *SymbolTable) SymbolTable {
9999
return *data
100100
}
101101

102-
func GetMembers(symbol *Symbol) SymbolTable {
103-
return GetSymbolTable(&symbol.data.members)
102+
func GetMembers(symbol *Symbol, arena *SymbolExtraArena) SymbolTable {
103+
return GetSymbolTable(&symbol.ensureExtra(arena).members)
104104
}
105105

106-
func GetExports(symbol *Symbol) SymbolTable {
107-
return GetSymbolTable(&symbol.data.exports)
106+
func GetExports(symbol *Symbol, arena *SymbolExtraArena) SymbolTable {
107+
return GetSymbolTable(&symbol.ensureExtra(arena).exports)
108108
}
109109

110110
func GetLocals(container *Node) SymbolTable {

0 commit comments

Comments
 (0)