src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

forward.go (7323B)


      1 // Copyright 2026 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package dense
      6 
      7 import (
      8 	"container/heap"
      9 	"log"
     10 	"slices"
     11 
     12 	"honnef.co/go/tools/analysis/dfa"
     13 	"honnef.co/go/tools/internal/xtools-internal/graph"
     14 )
     15 
     16 // Forward performs a forward monotone analysis over a control flow graph.
     17 //
     18 // The entry map provides initial state for entry blocks (blocks with zero
     19 // predecessors). For each edge, it calls transfer(fact, edge), where fact is
     20 // the analysis state on entry to edge.Pred. The transfer function must return
     21 // the outgoing analysis state of the edge (which may be fact, if the edge has
     22 // no effect on the  analysis state).
     23 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] {
     24 	cg, nodeMap := graph.Compact(g)
     25 
     26 	nNodes := cg.NumNodes()
     27 	fb := &fwdBuilder[L, Fact, NodeID]{
     28 		cfg:      cg,
     29 		nodeMap:  nodeMap,
     30 		transfer: transfer,
     31 		blocks:   make([]blockInfo[Fact], nNodes),
     32 	}
     33 	fb.queue.init(cg)
     34 
     35 	// Initialize each node.
     36 	totalEdges := 0
     37 	for ni := range nNodes {
     38 		b := &fb.blocks[ni]
     39 
     40 		// Construct back-edges.
     41 		//
     42 		// I experimented with making Graph support iterating over in-edges, but
     43 		// in practice that just meant each Graph implementation had a copy of
     44 		// this logic. So instead we keep Graph as simple as possible and
     45 		// compute the auxiliary data in the algorithm. One drawback of this is
     46 		// that, for the [Transpose] graph, this information is redundant with
     47 		// the underlying graph. We could potentially special-case that.
     48 		outs := 0
     49 		for succID := range cg.Out(ni) {
     50 			succ := &fb.blocks[succID]
     51 			succ.preds = append(succ.preds, blockEdge{ni, outs})
     52 			outs++
     53 			totalEdges++
     54 		}
     55 
     56 		// Initialize in & out states.
     57 		fact, ok := entry[nodeMap.Value(ni)]
     58 		if !ok {
     59 			fact = fb.l.Ident()
     60 		}
     61 		b.in = fact
     62 		b.out = slices.Repeat([]Fact{fb.l.Ident()}, outs)
     63 
     64 		// Enqueue block.
     65 		//
     66 		// It's tempting to enqueue only the entry blocks, but this is wrong.
     67 		// The entry map may be empty if there are no interesting entry states,
     68 		// but the transfer function may still introduce interesting states
     69 		// anywhere.
     70 		b.dirty = true
     71 		fb.queue.enqueue(ni)
     72 	}
     73 
     74 	// Propagate over blocks.
     75 	fb.propagate()
     76 
     77 	// Collect the final analysis results.
     78 	a := Analysis[Fact, NodeID]{
     79 		nodeMap: nodeMap,
     80 		ins:     make([]Fact, nNodes),
     81 		edges:   make([]edgeFact[Fact], 0, totalEdges),
     82 	}
     83 	for pred := range nNodes {
     84 		a.ins[pred] = fb.blocks[pred].in
     85 		i := 0
     86 		for succ := range cg.Out(pred) {
     87 			edge := edge{pred, succ}
     88 			a.edges = append(a.edges, edgeFact[Fact]{edge, fb.blocks[pred].out[i]})
     89 			i++
     90 		}
     91 	}
     92 	slices.SortFunc(a.edges, func(a, b edgeFact[Fact]) int { return a.edge.compare(b.edge) })
     93 	return &a
     94 }
     95 
     96 // fwdBuilder is the state used during [Forward] analysis.
     97 type fwdBuilder[L dfa.Semilattice[Fact], Fact any, NodeID comparable] struct {
     98 	l L // Lattice
     99 
    100 	cfg     graph.Graph[int]     // Control flow graph (compact)
    101 	nodeMap *graph.Index[NodeID] // Map from cfg to original NodeIDs
    102 
    103 	// transfer is the edge transfer function.
    104 	transfer func(from, to NodeID, fact Fact) Fact
    105 
    106 	blocks []blockInfo[Fact]
    107 
    108 	queue nodeHeap
    109 }
    110 
    111 type blockInfo[Fact any] struct {
    112 	dirty bool // The in fact has never been propagated.
    113 
    114 	preds []blockEdge
    115 
    116 	in  Fact
    117 	out []Fact // Corresponds to i'th out edge
    118 }
    119 
    120 type blockEdge struct {
    121 	node int
    122 	i    int // Out edge index
    123 }
    124 
    125 // nodeHeap implements a heap of NodeIDs, ordered topologically.
    126 //
    127 // We use this ordering so forward analysis converges more quickly.
    128 type nodeHeap struct {
    129 	heap        []int   // Remaining nodes in the current sweep
    130 	deferred    []int   // Nodes of next sweep
    131 	inQueue     []int64 // Bitmap over node IDs
    132 	prio        []int   // NodeID -> priority
    133 	currentPrio int     // Priority of last dequeued node, or -1
    134 }
    135 
    136 func (h *nodeHeap) init(g graph.Graph[int]) {
    137 	nNodes := g.NumNodes()
    138 	*h = nodeHeap{
    139 		inQueue:     make([]int64, (nNodes+63)/64),
    140 		prio:        make([]int, nNodes),
    141 		currentPrio: -1,
    142 	}
    143 	for p, nid := range graph.ReversePostorder(g) {
    144 		h.prio[nid] = p
    145 	}
    146 }
    147 
    148 func (h *nodeHeap) enqueue(nid int) {
    149 	if h.inQueue[nid/64]&(1<<(nid%64)) != 0 {
    150 		return
    151 	}
    152 	h.inQueue[nid/64] |= 1 << (nid % 64)
    153 
    154 	if h.currentPrio >= 0 && h.prio[nid] <= h.currentPrio {
    155 		// This is a retreating edge, self-edge, or other update to a node
    156 		// already passed in this sweep. Coalesce it into the next sweep.
    157 		h.deferred = append(h.deferred, nid)
    158 	} else {
    159 		heap.Push(h, nid)
    160 	}
    161 }
    162 
    163 func (h *nodeHeap) dequeue() int {
    164 	if len(h.heap) == 0 {
    165 		// Start the next RPO sweep.
    166 		h.heap, h.deferred = h.deferred, h.heap[:0]
    167 		h.currentPrio = -1
    168 		heap.Init(h)
    169 	}
    170 
    171 	nid := h.heap[0]
    172 	heap.Pop(h)
    173 	h.inQueue[nid/64] &^= 1 << (nid % 64)
    174 	h.currentPrio = h.prio[nid]
    175 	return nid
    176 }
    177 
    178 func (h *nodeHeap) pending() bool     { return len(h.heap) != 0 || len(h.deferred) != 0 }
    179 func (h nodeHeap) Len() int           { return len(h.heap) }
    180 func (h nodeHeap) Less(i, j int) bool { return h.prio[h.heap[i]] < h.prio[h.heap[j]] }
    181 func (h nodeHeap) Swap(i, j int)      { h.heap[i], h.heap[j] = h.heap[j], h.heap[i] }
    182 func (h *nodeHeap) Push(x any)        { h.heap = append(h.heap, x.(int)) }
    183 func (h *nodeHeap) Pop() any {
    184 	n := len(h.heap)
    185 	x := h.heap[n-1]
    186 	h.heap = h.heap[:n-1]
    187 	return x
    188 }
    189 
    190 func (fb *fwdBuilder[L, Fact, NodeID]) merge(a, b Fact) Fact {
    191 	if fb.l.Equals(a, b) {
    192 		return a
    193 	}
    194 	return fb.l.Merge(a, b)
    195 }
    196 
    197 func (fb *fwdBuilder[L, Fact, NodeID]) propagate() {
    198 	for fb.queue.pending() {
    199 		bi := fb.queue.dequeue()
    200 		block := &fb.blocks[bi]
    201 
    202 		// Merge predecessor facts to compute updated "in" fact.
    203 		var in Fact
    204 		first := true
    205 		for _, edge := range block.preds {
    206 			pred := &fb.blocks[edge.node]
    207 			var edgeFact Fact
    208 			if pred.dirty {
    209 				// We haven't visited this predecessor yet, so it doesn't have
    210 				// meaningful out facts.
    211 				edgeFact = fb.l.Ident()
    212 			} else {
    213 				edgeFact = pred.out[edge.i]
    214 			}
    215 			if first {
    216 				if debug {
    217 					log.Printf("propagate to node %d", bi)
    218 				}
    219 				in = edgeFact
    220 				first = false
    221 			} else {
    222 				in = fb.merge(in, edgeFact)
    223 			}
    224 			if debug {
    225 				log.Printf("  from node %d: %v", edge.node, edgeFact)
    226 			}
    227 		}
    228 		if first {
    229 			// No predecessors.
    230 			if debug {
    231 				log.Printf("node %d gets initial state", bi)
    232 			}
    233 			in = block.in
    234 		}
    235 
    236 		if !block.dirty && fb.l.Equals(in, block.in) {
    237 			// No change to block input, which means the transfer function
    238 			// results also won't change from the last time we ran it.
    239 			if debug {
    240 				log.Printf("  initial state unchanged: %v", in)
    241 			}
    242 			continue
    243 		}
    244 		if debug {
    245 			log.Printf("  new initial state: %v", in)
    246 		}
    247 		block.in = in
    248 
    249 		// Apply transfer function.
    250 		predID := fb.nodeMap.Value(bi)
    251 		i := 0
    252 		for succNum := range fb.cfg.Out(bi) {
    253 			edgeFact := fb.transfer(predID, fb.nodeMap.Value(succNum), in)
    254 			if block.dirty || !fb.l.Equals(block.out[i], edgeFact) {
    255 				// Out fact changed, so recompute the target block.
    256 				if debug {
    257 					log.Printf("  to node %d: %v", succNum, edgeFact)
    258 				}
    259 				block.out[i] = edgeFact
    260 				fb.queue.enqueue(succNum)
    261 			} else {
    262 				if debug {
    263 					log.Printf("  to node %d: no change", succNum)
    264 				}
    265 			}
    266 			i++
    267 		}
    268 
    269 		block.dirty = false
    270 	}
    271 }