State the copy rule on renderScope once, and say the allocation gate prices what keeps a hold set off the heap

This commit is contained in:
2026-09-26 15:04:00 +02:00
committed by Lillem4n
parent 55fb1ec42e
commit 203b235fdd
4 changed files with 6 additions and 7 deletions
+1 -1
View File
@@ -249,7 +249,7 @@ set is declared keeps them on that frame's stack. A `Fake` often reads no refere
at all, and making them anyway cost about a fifth of the cheapest render, so it makes at all, and making them anyway cost about a fifth of the cheapest render, so it makes
them on the first read instead, at two heap allocations for a render that does share a them on the first read instead, at two heap allocations for a render that does share a
draw. The allocation gate over a repeat of a reference path and over a named draw group draw. The allocation gate over a repeat of a reference path and over a named draw group
prices that, and pins the two measures that keep a hold set off the heap. prices that, and pins what keeps a hold set off the heap.
## A category never references itself, and a record's fences run at load ## A category never references itself, and a record's fences run at load
-3
View File
@@ -73,9 +73,6 @@ func TestEveryReadARenderMakesIsGathered(t *testing.T) {
} }
} }
// wantGathered renders a root five times over the seeded stream and fails where a reference the
// render read is not among gathered: same draw group and path, and where the render stood in a
// table's row, gathered from that row or from no row of the table.
func wantGathered(t *testing.T, s *session, tables map[string]*table, label string, gathered []pathRead, renderOnce func()) { func wantGathered(t *testing.T, s *session, tables map[string]*table, label string, gathered []pathRead, renderOnce func()) {
t.Helper() t.Helper()
var reads []pathRead var reads []pathRead
+4 -2
View File
@@ -11,7 +11,9 @@ type holdSet struct {
} }
// renderScope is where a render reads its reference paths: a hold set, in the draw group of the // renderScope is where a render reads its reference paths: a hold set, in the draw group of the
// template rendering, and the row a table's format is rendering, which its columns read. // template rendering, and the row a table's format is rendering, which its columns read. It passes
// by value, and what is read from it reaches a map key, an interface or a func value only as a
// copy; else sc, and with it every render's hold set, moves to the heap.
type renderScope struct { type renderScope struct {
set *holdSet set *holdSet
group string group string
@@ -60,7 +62,7 @@ func (sc renderScope) hold() *hold {
sc.set.named = map[string]*hold{} sc.set.named = map[string]*hold{}
} }
d = &hold{variant: map[string]node{}, value: map[string]draw{}} d = &hold{variant: map[string]node{}, value: map[string]draw{}}
sc.set.named[strings.Clone(sc.group)] = d // a key from sc would leak sc, and with it every render's hold set, to the heap sc.set.named[strings.Clone(sc.group)] = d
} }
return d return d
} }
+1 -1
View File
@@ -55,7 +55,7 @@ func TestNoRenderAllocRegression(t *testing.T) {
} }
} }
// The repeat shape prices both escape measures: dropping either costs an alloc an iteration. // The repeat shape prices what keeps a hold set off the heap: a copy dropped costs an alloc an iteration.
func TestNoReferenceAllocRegression(t *testing.T) { func TestNoReferenceAllocRegression(t *testing.T) {
word := `{"format":"{w}","w":["alpha","beta","gamma","delta"]}` word := `{"format":"{w}","w":["alpha","beta","gamma","delta"]}`
for _, s := range []struct { for _, s := range []struct {