Update go version

This commit is contained in:
dwrz
2026-08-21 10:23:37 +00:00
parent 78248a6145
commit c2e2d9ea02
466 changed files with 67766 additions and 2881 deletions

View File

@@ -0,0 +1,60 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package flow implements a monotone flow analysis framework.
package dense
import (
"cmp"
"slices"
"honnef.co/go/tools/internal/xtools-internal/graph"
)
const debug = false
// Analysis is the result of a monotone analysis. Fact is the type of elements
// in the analysis semilattice, and represents the outcome of the analysis at
// every node and edge.
type Analysis[Fact any, NodeID comparable] struct {
nodeMap *graph.Index[NodeID]
ins []Fact // By NodeID
edges []edgeFact[Fact] // Sorted by (from, to)
}
// In returns the analysis fact on entry to nid. This is the merge of the facts
// on all incoming edges.
func (a *Analysis[Fact, NodeID]) In(nid NodeID) Fact {
return a.ins[a.nodeMap.Index(nid)]
}
// Edge returns the analysis fact propagated on edge from ==> to.
func (a *Analysis[Fact, NodeID]) Edge(from, to NodeID) Fact {
i, found := slices.BinarySearchFunc(a.edges, a.edge(from, to), edgeFact[Fact].compare)
if !found {
panic("no such edge")
}
return a.edges[i].fact
}
func (a *Analysis[Fact, NodeID]) edge(from, to NodeID) edge {
fromNum, toNum := a.nodeMap.Index(from), a.nodeMap.Index(to)
return edge{fromNum, toNum}
}
type edge struct {
from, to int
}
func (e edge) compare(f edge) int {
if v := cmp.Compare(e.from, f.from); v != 0 {
return v
}
return cmp.Compare(e.to, f.to)
}
type edgeFact[Fact any] struct {
edge
fact Fact
}

View File

@@ -0,0 +1,271 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dense
import (
"container/heap"
"log"
"slices"
"honnef.co/go/tools/analysis/dfa"
"honnef.co/go/tools/internal/xtools-internal/graph"
)
// Forward performs a forward monotone analysis over a control flow graph.
//
// The entry map provides initial state for entry blocks (blocks with zero
// predecessors). For each edge, it calls transfer(fact, edge), where fact is
// the analysis state on entry to edge.Pred. The transfer function must return
// the outgoing analysis state of the edge (which may be fact, if the edge has
// no effect on the analysis state).
func Forward[L dfa.Semilattice[Fact], Fact any, NodeID comparable](g graph.Graph[NodeID], entry map[NodeID]Fact, transfer func(from, to NodeID, fact Fact) Fact) *Analysis[Fact, NodeID] {
cg, nodeMap := graph.Compact(g)
nNodes := cg.NumNodes()
fb := &fwdBuilder[L, Fact, NodeID]{
cfg: cg,
nodeMap: nodeMap,
transfer: transfer,
blocks: make([]blockInfo[Fact], nNodes),
}
fb.queue.init(cg)
// Initialize each node.
totalEdges := 0
for ni := range nNodes {
b := &fb.blocks[ni]
// Construct back-edges.
//
// I experimented with making Graph support iterating over in-edges, but
// in practice that just meant each Graph implementation had a copy of
// this logic. So instead we keep Graph as simple as possible and
// compute the auxiliary data in the algorithm. One drawback of this is
// that, for the [Transpose] graph, this information is redundant with
// the underlying graph. We could potentially special-case that.
outs := 0
for succID := range cg.Out(ni) {
succ := &fb.blocks[succID]
succ.preds = append(succ.preds, blockEdge{ni, outs})
outs++
totalEdges++
}
// Initialize in & out states.
fact, ok := entry[nodeMap.Value(ni)]
if !ok {
fact = fb.l.Ident()
}
b.in = fact
b.out = slices.Repeat([]Fact{fb.l.Ident()}, outs)
// Enqueue block.
//
// It's tempting to enqueue only the entry blocks, but this is wrong.
// The entry map may be empty if there are no interesting entry states,
// but the transfer function may still introduce interesting states
// anywhere.
b.dirty = true
fb.queue.enqueue(ni)
}
// Propagate over blocks.
fb.propagate()
// Collect the final analysis results.
a := Analysis[Fact, NodeID]{
nodeMap: nodeMap,
ins: make([]Fact, nNodes),
edges: make([]edgeFact[Fact], 0, totalEdges),
}
for pred := range nNodes {
a.ins[pred] = fb.blocks[pred].in
i := 0
for succ := range cg.Out(pred) {
edge := edge{pred, succ}
a.edges = append(a.edges, edgeFact[Fact]{edge, fb.blocks[pred].out[i]})
i++
}
}
slices.SortFunc(a.edges, func(a, b edgeFact[Fact]) int { return a.edge.compare(b.edge) })
return &a
}
// fwdBuilder is the state used during [Forward] analysis.
type fwdBuilder[L dfa.Semilattice[Fact], Fact any, NodeID comparable] struct {
l L // Lattice
cfg graph.Graph[int] // Control flow graph (compact)
nodeMap *graph.Index[NodeID] // Map from cfg to original NodeIDs
// transfer is the edge transfer function.
transfer func(from, to NodeID, fact Fact) Fact
blocks []blockInfo[Fact]
queue nodeHeap
}
type blockInfo[Fact any] struct {
dirty bool // The in fact has never been propagated.
preds []blockEdge
in Fact
out []Fact // Corresponds to i'th out edge
}
type blockEdge struct {
node int
i int // Out edge index
}
// nodeHeap implements a heap of NodeIDs, ordered topologically.
//
// We use this ordering so forward analysis converges more quickly.
type nodeHeap struct {
heap []int // Remaining nodes in the current sweep
deferred []int // Nodes of next sweep
inQueue []int64 // Bitmap over node IDs
prio []int // NodeID -> priority
currentPrio int // Priority of last dequeued node, or -1
}
func (h *nodeHeap) init(g graph.Graph[int]) {
nNodes := g.NumNodes()
*h = nodeHeap{
inQueue: make([]int64, (nNodes+63)/64),
prio: make([]int, nNodes),
currentPrio: -1,
}
for p, nid := range graph.ReversePostorder(g) {
h.prio[nid] = p
}
}
func (h *nodeHeap) enqueue(nid int) {
if h.inQueue[nid/64]&(1<<(nid%64)) != 0 {
return
}
h.inQueue[nid/64] |= 1 << (nid % 64)
if h.currentPrio >= 0 && h.prio[nid] <= h.currentPrio {
// This is a retreating edge, self-edge, or other update to a node
// already passed in this sweep. Coalesce it into the next sweep.
h.deferred = append(h.deferred, nid)
} else {
heap.Push(h, nid)
}
}
func (h *nodeHeap) dequeue() int {
if len(h.heap) == 0 {
// Start the next RPO sweep.
h.heap, h.deferred = h.deferred, h.heap[:0]
h.currentPrio = -1
heap.Init(h)
}
nid := h.heap[0]
heap.Pop(h)
h.inQueue[nid/64] &^= 1 << (nid % 64)
h.currentPrio = h.prio[nid]
return nid
}
func (h *nodeHeap) pending() bool { return len(h.heap) != 0 || len(h.deferred) != 0 }
func (h nodeHeap) Len() int { return len(h.heap) }
func (h nodeHeap) Less(i, j int) bool { return h.prio[h.heap[i]] < h.prio[h.heap[j]] }
func (h nodeHeap) Swap(i, j int) { h.heap[i], h.heap[j] = h.heap[j], h.heap[i] }
func (h *nodeHeap) Push(x any) { h.heap = append(h.heap, x.(int)) }
func (h *nodeHeap) Pop() any {
n := len(h.heap)
x := h.heap[n-1]
h.heap = h.heap[:n-1]
return x
}
func (fb *fwdBuilder[L, Fact, NodeID]) merge(a, b Fact) Fact {
if fb.l.Equals(a, b) {
return a
}
return fb.l.Merge(a, b)
}
func (fb *fwdBuilder[L, Fact, NodeID]) propagate() {
for fb.queue.pending() {
bi := fb.queue.dequeue()
block := &fb.blocks[bi]
// Merge predecessor facts to compute updated "in" fact.
var in Fact
first := true
for _, edge := range block.preds {
pred := &fb.blocks[edge.node]
var edgeFact Fact
if pred.dirty {
// We haven't visited this predecessor yet, so it doesn't have
// meaningful out facts.
edgeFact = fb.l.Ident()
} else {
edgeFact = pred.out[edge.i]
}
if first {
if debug {
log.Printf("propagate to node %d", bi)
}
in = edgeFact
first = false
} else {
in = fb.merge(in, edgeFact)
}
if debug {
log.Printf(" from node %d: %v", edge.node, edgeFact)
}
}
if first {
// No predecessors.
if debug {
log.Printf("node %d gets initial state", bi)
}
in = block.in
}
if !block.dirty && fb.l.Equals(in, block.in) {
// No change to block input, which means the transfer function
// results also won't change from the last time we ran it.
if debug {
log.Printf(" initial state unchanged: %v", in)
}
continue
}
if debug {
log.Printf(" new initial state: %v", in)
}
block.in = in
// Apply transfer function.
predID := fb.nodeMap.Value(bi)
i := 0
for succNum := range fb.cfg.Out(bi) {
edgeFact := fb.transfer(predID, fb.nodeMap.Value(succNum), in)
if block.dirty || !fb.l.Equals(block.out[i], edgeFact) {
// Out fact changed, so recompute the target block.
if debug {
log.Printf(" to node %d: %v", succNum, edgeFact)
}
block.out[i] = edgeFact
fb.queue.enqueue(succNum)
} else {
if debug {
log.Printf(" to node %d: no change", succNum)
}
}
i++
}
block.dirty = false
}
}

View File

@@ -0,0 +1,48 @@
package dfa
import (
"fmt"
"strings"
)
// Dot returns a directed graph in [Graphviz] format that represents the finite
// join-semilattice ⟨S, ≤⟩. Vertices represent elements in S and edges
// represent the ≤ relation between elements. We map from ⟨S, ∨⟩ to ⟨S, ≤⟩ by
// computing x y for all elements in [S]², where x ≤ y iff x y == y.
//
// The resulting graph can be filtered through [tred] to compute the transitive
// reduction of the graph, the visualisation of which corresponds to the Hasse
// diagram of the semilattice.
//
// [Graphviz]: https://graphviz.org/
// [tred]: https://graphviz.org/docs/cli/tred/
func Dot[L Semilattice[Elem], Elem any](states []Elem) string {
var sb strings.Builder
sb.WriteString("digraph{\n")
sb.WriteString("rankdir=\"BT\"\n")
for i, v := range states {
if vs, ok := any(v).(fmt.Stringer); ok {
fmt.Fprintf(&sb, "n%d [label=%q]\n", i, vs)
} else {
fmt.Fprintf(&sb, "n%d [label=%q]\n", i, fmt.Sprintf("%v", v))
}
}
var l L
for dx, x := range states {
for dy, y := range states {
if dx == dy {
continue
}
if l.Equals(l.Merge(x, y), y) {
fmt.Fprintf(&sb, "n%d -> n%d\n", dx, dy)
}
}
}
sb.WriteString("}")
return sb.String()
}

View File

@@ -0,0 +1,147 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dfa
import (
"fmt"
"maps"
"slices"
)
// A Semilattice describes a bounded semilattice over Elem.
// That is, a partial order over values of type Elem, with a binary
// Merge operator and an identity element.
//
// This is typically implemented by a stateless type, and acts as a factory for
// lattice elements.
type Semilattice[Elem any] interface {
// Ident returns the identity element of this lattice, that is the unit of
// the Merge operation.
Ident() Elem
// Equals returns whether a and b are the same element.
Equals(a, b Elem) bool
// Merge combines two lattice values, such as the two possible values of a
// variable at the end of an if/else statement.
//
// Merge must satisfy the following identities, where we use ∧ for Merge, =
// for Equals, and 𝟏 for Ident:
//
// - Associativity: x ∧ (y ∧ z) = (x ∧ y) ∧ z
// - Commutativity: x ∧ y = y ∧ x
// - Idempotency: x ∧ x = x
// - Identity: x ∧ 𝟏 = x
Merge(a, b Elem) Elem
}
// A MapLattice implements [Semilattice][map[Key]Elem]. The values in the map
// are themselves defined by [Semilattice] L.
//
// Any elements missing from the map are implicitly L's identity element, and
// L's identity element never appears as a value in the map.
//
// For densely numbered keys, consider using [DenseMapLattice] instead.
type MapLattice[Key comparable, Elem any, L Semilattice[Elem]] struct {
l L
}
func (m MapLattice[Key, Elem, L]) Ident() map[Key]Elem {
return nil
}
func (m MapLattice[Key, Elem, L]) Equals(a, b map[Key]Elem) bool {
return maps.EqualFunc(a, b, m.l.Equals)
}
func (m MapLattice[Key, Elem, L]) Merge(a, b map[Key]Elem) map[Key]Elem {
if len(a) == 0 {
return b
} else if len(b) == 0 {
return a
}
// We need to consider the union of keys in a and b.
out := make(map[Key]Elem)
id := m.l.Ident()
for k, av := range a {
bv, ok := b[k]
if !ok {
// Because Merge(x, Ident()) == x, we can skip calling L.Merge.
out[k] = av
continue
}
w := m.l.Merge(av, bv)
if m.l.Equals(w, id) {
// In a semilattice, Merge(x, y) = Ident is only possible when x ==
// Ident and y == Ident.
panic(fmt.Sprintf(
"%T is not a semilattice: Merge(%v, %v) returned Ident for non-Ident arguments",
m.l, av, bv))
}
out[k] = w
}
// We considered keys that are only in a, and in both a and b. Now we just
// need to handle keys that are only in b.
for k, v2 := range b {
if _, ok := a[k]; !ok {
out[k] = v2
}
}
return out
}
// A DenseMapLattice implements [Semilattice][[]Elem]. It is like a [MapLattice]
// that is indexed by integers. The values in the map are themselves defined by
// [Semilattice] L.
//
// Unlike [MapLattice], L's identity element may appear as a value in the map,
// to allow for gaps in the numbering of keys when the identity element is
// Elem's zero value.
type DenseMapLattice[Elem any, L Semilattice[Elem]] struct {
l L
}
func (s DenseMapLattice[Elem, L]) Ident() []Elem {
return nil
}
func (s DenseMapLattice[Elem, L]) Equals(a, b []Elem) bool {
nmin := min(len(a), len(b))
ident := s.l.Ident()
// Check that up to nmin, all elements in a and b match. If one of a or b
// is longer, then its tail nmin:nmax must only contain identity elements.
return slices.EqualFunc(a[:nmin], b[:nmin], s.l.Equals) &&
!slices.ContainsFunc(a[nmin:], func(e Elem) bool {
return !s.l.Equals(e, ident)
}) &&
!slices.ContainsFunc(b[nmin:], func(e Elem) bool {
return !s.l.Equals(e, ident)
})
}
func (s DenseMapLattice[Elem, L]) Merge(a, b []Elem) []Elem {
if len(a) == 0 {
return b
} else if len(b) == 0 {
return a
}
out := make([]Elem, max(len(a), len(b)))
for k := range max(len(a), len(b)) {
av := s.l.Ident()
bv := s.l.Ident()
if k < len(a) {
av = a[k]
}
if k < len(b) {
bv = b[k]
}
out[k] = s.l.Merge(av, bv)
}
return out
}