Name the path walks after their entry points, keep the probe allocation-free, and record why the walks are four loops

This commit is contained in:
2026-09-28 00:26:33 +02:00
committed by Lillem4n
parent 917c93564f
commit 68033bd42f
8 changed files with 35 additions and 29 deletions
+1
View File
@@ -110,3 +110,4 @@ In [docs/decisions.md](docs/decisions.md):
- The US given names come from a mirror of the SSA file - The US given names come from a mirror of the SSA file
- `List` advertises direct descents only - `List` advertises direct descents only
- Each entry point to `drawCheck.hasRead` says what its caller gets - Each entry point to `drawCheck.hasRead` says what its caller gets
- The four path walks are four loops
+9
View File
@@ -683,3 +683,12 @@ the direct chain is the one a reader can predict from the tables' parents.
2026-09-25, larv-review; approved 2026-09-25 by lilleman. Goal 2: a reader lands on 2026-09-25, larv-review; approved 2026-09-25 by lilleman. Goal 2: a reader lands on
`readsPath` or `splitsDraws` from its caller, so each doc states where that walk stops, `readsPath` or `splitsDraws` from its caller, so each doc states where that walk stops,
though `hasRead` states the same stop rule. Valid while the two differ in where they stop. though `hasRead` states the same stop rule. Valid while the two differ in where they stop.
## The four path walks are four loops
2026-09-28, larv-review on systems-architect's finding; awaiting approval. Goals 2 and
11: `pathCheck`, `pathCover`, `pathProbe` and `pathDraw` each loop over `stepInto` and
`table.route` themselves. One walk calling them through an interface or a type parameter
leaks the walker and the pins it holds, an allocation per reference read. Valid while Go's
escape analysis treats a call through an interface or a type parameter as leaking its
receiver.
+1 -1
View File
@@ -32,7 +32,7 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
if !isTable { if !isTable {
return nil return nil
} }
probe := &probeWalk{} probe := &pathProbe{drawn: map[*table]bool{}}
_, _ = probe.walk(t, a.tail) _, _ = probe.walk(t, a.tail)
tr := &tableRead{headTable: t, pins: probe.pins, drawn: probe.drawn} tr := &tableRead{headTable: t, pins: probe.pins, drawn: probe.drawn}
written := a.spelling[:len(a.spelling)-len(joinSegments(a.tail))] written := a.spelling[:len(a.spelling)-len(joinSegments(a.tail))]
+1 -1
View File
@@ -164,7 +164,7 @@ func renderEdges(n node) []renderEdge {
// contributes every variant, since any of them may be the one drawn. checkPath has // contributes every variant, since any of them may be the one drawn. checkPath has
// already proved the tail resolves in every variant. // already proved the tail resolves in every variant.
func pathLeaves(n node, tail []string) []node { func pathLeaves(n node, tail []string) []node {
w := &proveWalk{tail: tail} w := &pathCheck{tail: tail}
_, _ = w.walk(n, tail) _, _ = w.walk(n, tail)
return w.leaves return w.leaves
} }
+1 -1
View File
@@ -44,7 +44,7 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
if len(a.tail) > 0 { if len(a.tail) > 0 {
sc.pins = &d.pins sc.pins = &d.pins
} }
r := renderLeaf(s, drawPath(t.head(a.key), a.tail, a.key, &renderWalk{s: s, pins: &d.pins, held: d, a: &a}), sc) r := renderLeaf(s, drawPath(t.head(a.key), a.tail, a.key, &pathDraw{s: s, pins: &d.pins, held: d, a: &a}), sc)
if d.value == nil { if d.value == nil {
d.value = map[string]draw{} d.value = map[string]draw{}
} }
+1 -1
View File
@@ -100,7 +100,7 @@ func operandReader(t *template, head string) string {
// coverPath collects what holding one path pins: every choice level the path // coverPath collects what holding one path pins: every choice level the path
// passes through, whole, and the leaf it renders. // passes through, whole, and the leaf it renders.
func coverPath(n node, tail []string, into map[node]bool) { func coverPath(n node, tail []string, into map[node]bool) {
_, _ = (&coverWalk{into: into}).walk(n, tail) _, _ = (&pathCover{into: into}).walk(n, tail)
} }
// cover collects a level and everything contained in it. A fixed string outside a // cover collects a level and everything contained in it. A fixed string outside a
+19 -23
View File
@@ -185,18 +185,18 @@ func (t *table) route(tail []string, descended bool) (tableRoute, error) {
return r, nil return r, nil
} }
// proveWalk proves a path resolves whichever way the draws go: every variant of a // pathCheck proves a path resolves whichever way the draws go: every variant of a
// choice carries the rest of it, and is walked, a selector names a row inside the // choice carries the rest of it, and is walked, a selector names a row inside the
// rows selected before it, and no level read carries a repeat or a drawGroup. It // rows selected before it, and no level read carries a repeat or a drawGroup. It
// collects every leaf the path may render; level and tail name a refused level. // collects every leaf the path may render; level and tail name a refused level.
type proveWalk struct { type pathCheck struct {
pins pinSet pins pinSet
level string level string
tail []string tail []string
leaves []node leaves []node
} }
func (w *proveWalk) walk(n node, tail []string) (node, error) { func (w *pathCheck) walk(n node, tail []string) (node, error) {
for descended := false; len(tail) > 0 || descended; { for descended := false; len(tail) > 0 || descended; {
var err error var err error
switch x := n.(type) { switch x := n.(type) {
@@ -228,7 +228,7 @@ func (w *proveWalk) walk(n node, tail []string) (node, error) {
return n, nil return n, nil
} }
func (w *proveWalk) walkEvery(c *choice, tail []string) (node, error) { func (w *pathCheck) walkEvery(c *choice, tail []string) (node, error) {
if err := carriedByAll(c, tail); err != nil { if err := carriedByAll(c, tail); err != nil {
return nil, err return nil, err
} }
@@ -242,7 +242,7 @@ func (w *proveWalk) walkEvery(c *choice, tail []string) (node, error) {
return last, nil return last, nil
} }
func (w *proveWalk) enter(t *template, rest []string) error { func (w *pathCheck) enter(t *template, rest []string) error {
name := join(w.level, strings.Join(w.tail[:len(w.tail)-len(rest)], ".")) name := join(w.level, strings.Join(w.tail[:len(w.tail)-len(rest)], "."))
switch { switch {
case t.repeat > 1: case t.repeat > 1:
@@ -253,11 +253,10 @@ func (w *proveWalk) enter(t *template, rest []string) error {
return nil return nil
} }
// coverWalk collects into what holding a path pins: every choice level it passes // pathCover is coverPath's walk: it stops at a choice and reads no row.
// through, whole, and the leaf it renders. It stops at a choice and reads no row. type pathCover struct{ into map[node]bool }
type coverWalk struct{ into map[node]bool }
func (w *coverWalk) walk(n node, tail []string) (node, error) { func (w *pathCover) walk(n node, tail []string) (node, error) {
for descended := false; len(tail) > 0 || descended; { for descended := false; len(tail) > 0 || descended; {
var err error var err error
switch x := n.(type) { switch x := n.(type) {
@@ -281,15 +280,15 @@ func (w *coverWalk) walk(n node, tail []string) (node, error) {
return n, nil return n, nil
} }
// probeWalk proves a path as proveWalk does, walking one variant of a choice, so a // pathProbe proves a path resolves, walking one variant of a choice, so a
// path that resolves resolves whichever variants and rows are drawn. It draws // path that resolves resolves whichever variants and rows are drawn. It draws
// nothing, marking in drawn the tables a draw would pin. // nothing, marking in drawn the tables a draw would pin.
type probeWalk struct { type pathProbe struct {
pins pinSet pins pinSet
drawn map[*table]bool drawn map[*table]bool
} }
func (w *probeWalk) walk(n node, tail []string) (node, error) { func (w *pathProbe) walk(n node, tail []string) (node, error) {
for descended := false; len(tail) > 0 || descended; { for descended := false; len(tail) > 0 || descended; {
var err error var err error
switch x := n.(type) { switch x := n.(type) {
@@ -318,26 +317,23 @@ func (w *probeWalk) walk(n node, tail []string) (node, error) {
return n, nil return n, nil
} }
func (w *probeWalk) readRow(t *table, r tableRoute) error { func (w *pathProbe) readRow(t *table, r tableRoute) error {
if r.sel != "" { if r.sel != "" {
return w.pins.selectRow(t, r.sel) return w.pins.selectRow(t, r.sel)
} }
if !r.draw { if !r.draw {
return nil return nil
} }
if w.drawn == nil {
w.drawn = map[*table]bool{}
}
for stop := w.pins.nearestPinned(t); t != stop; t = t.parentT { for stop := w.pins.nearestPinned(t); t != stop; t = t.parentT {
w.drawn[t] = true w.drawn[t] = true
} }
return nil return nil
} }
// renderWalk draws the rows and variants a proved path reads, pinning them in pins. // pathDraw draws the rows and variants a proved path reads, pinning them in pins.
// For a held read, held keeps the variant drawn at each level of a, so paths // For a held read, held keeps the variant drawn at each level of a, so paths
// sharing a prefix share it. // sharing a prefix share it.
type renderWalk struct { type pathDraw struct {
s *session s *session
pins *pinSet pins *pinSet
held *hold held *hold
@@ -346,7 +342,7 @@ type renderWalk struct {
// drawPath walks w over a path proved first, by a probe or by New's fences, so the // drawPath walks w over a path proved first, by a probe or by New's fences, so the
// walk cannot fail. head is what n is reached by, for the panic. // walk cannot fail. head is what n is reached by, for the panic.
func drawPath(n node, tail []string, head string, w *renderWalk) node { func drawPath(n node, tail []string, head string, w *pathDraw) node {
leaf, err := w.walk(n, tail) leaf, err := w.walk(n, tail)
if err != nil { if err != nil {
panic(fmt.Sprintf("fejkdata: %s: %v; the path should have been proved before it was drawn", join(head, joinSegments(tail)), err)) panic(fmt.Sprintf("fejkdata: %s: %v; the path should have been proved before it was drawn", join(head, joinSegments(tail)), err))
@@ -354,7 +350,7 @@ func drawPath(n node, tail []string, head string, w *renderWalk) node {
return leaf return leaf
} }
func (w *renderWalk) walk(n node, tail []string) (node, error) { func (w *pathDraw) walk(n node, tail []string) (node, error) {
for descended := false; len(tail) > 0 || descended; { for descended := false; len(tail) > 0 || descended; {
var err error var err error
switch x := n.(type) { switch x := n.(type) {
@@ -387,7 +383,7 @@ func (w *renderWalk) walk(n node, tail []string) (node, error) {
// variant is the variant of c the walk continues into: the one held for this level // variant is the variant of c the walk continues into: the one held for this level
// of the read, drawn once, where the walk holds its draws; else one drawn afresh. // of the read, drawn once, where the walk holds its draws; else one drawn afresh.
func (w *renderWalk) variant(c *choice, rest []string) node { func (w *pathDraw) variant(c *choice, rest []string) node {
if w.held == nil { if w.held == nil {
return pick(w.s, c) return pick(w.s, c)
} }
@@ -453,9 +449,9 @@ func unreachableInChoice(c *choice, want string) error {
return fmt.Errorf("not every variant of this %d-way choice carries %q; all carry %v", len(c.items), want, offered) return fmt.Errorf("not every variant of this %d-way choice carries %q; all carry %v", len(c.items), want, offered)
} }
// checkPath proves a dotted tail as proveWalk does, so a path that validates here // checkPath proves a dotted tail as pathCheck does, so a path that validates here
// resolves on every render, and a typo is a New-time error. // resolves on every render, and a typo is a New-time error.
func checkPath(n node, tail []string, level string) error { func checkPath(n node, tail []string, level string) error {
_, err := (&proveWalk{level: level, tail: tail}).walk(n, tail) _, err := (&pathCheck{level: level, tail: tail}).walk(n, tail)
return err return err
} }
+2 -2
View File
@@ -44,10 +44,10 @@ func (f *Generator) Fake(path string) (string, error) {
// every call. // every call.
func descend(s *session, root node, segments []string, sc renderScope) (node, error) { func descend(s *session, root node, segments []string, sc renderScope) (node, error) {
// Walked once without drawing first, so a path that fails moves no seeded stream. // Walked once without drawing first, so a path that fails moves no seeded stream.
if _, err := (&probeWalk{}).walk(root, segments); err != nil { if _, err := (&pathProbe{drawn: map[*table]bool{}}).walk(root, segments); err != nil {
return nil, err return nil, err
} }
return drawPath(root, segments, "", &renderWalk{s: s, pins: &sc.hold().pins}), nil return drawPath(root, segments, "", &pathDraw{s: s, pins: &sc.hold().pins}), nil
} }
// render evaluates a compiled node to a string. compile validates every node up // render evaluates a compiled node to a string. compile validates every node up