@@ -100,7 +100,7 @@ func (b *builder) createLandingPad() {
100100
101101 // Continue at the 'recover' block, which returns to the parent in an
102102 // appropriate way.
103- b .CreateBr (b .blockEntries [b .fn .Recover ] )
103+ b .CreateBr (b .blockInfo [b .fn .Recover . Index ]. entry )
104104}
105105
106106// Create a checkpoint (similar to setjmp). This emits inline assembly that
@@ -234,41 +234,108 @@ func (b *builder) createInvokeCheckpoint() {
234234 continueBB := b .insertBasicBlock ("" )
235235 b .CreateCondBr (isZero , continueBB , b .landingpad )
236236 b .SetInsertPointAtEnd (continueBB )
237- b .blockExits [ b . currentBlock ] = continueBB
237+ b .currentBlockInfo . exit = continueBB
238238}
239239
240- // isInLoop checks if there is a path from a basic block to itself.
241- func isInLoop (start * ssa.BasicBlock ) bool {
242- // Use a breadth-first search to scan backwards through the block graph.
243- queue := []* ssa.BasicBlock {start }
244- checked := map [* ssa.BasicBlock ]struct {}{}
245-
246- for len (queue ) > 0 {
247- // pop a block off of the queue
248- block := queue [len (queue )- 1 ]
249- queue = queue [:len (queue )- 1 ]
250-
251- // Search through predecessors.
252- // Searching backwards means that this is pretty fast when the block is close to the start of the function.
253- // Defers are often placed near the start of the function.
254- for _ , pred := range block .Preds {
255- if pred == start {
256- // cycle found
257- return true
258- }
240+ // isInLoop checks if there is a path from the current block to itself.
241+ // Use Tarjan's strongly connected components algorithm to search for cycles.
242+ // A one-node SCC is a cycle iff there is an edge from the node to itself.
243+ // A multi-node SCC is always a cycle.
244+ // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
245+ func (b * builder ) isInLoop () bool {
246+ if b .currentBlockInfo .tarjan .lowLink == 0 {
247+ b .strongConnect (b .currentBlock )
248+ }
249+ return b .currentBlockInfo .tarjan .cyclic
250+ }
259251
260- if _ , ok := checked [pred ]; ok {
261- // block already checked
262- continue
263- }
252+ func (b * builder ) strongConnect (block * ssa.BasicBlock ) {
253+ // Assign a new index.
254+ // Indices start from 1 so that 0 can be used as a sentinel.
255+ assignedIndex := b .tarjanIndex + 1
256+ b .tarjanIndex = assignedIndex
257+
258+ // Apply the new index.
259+ blockIndex := block .Index
260+ node := & b .blockInfo [blockIndex ].tarjan
261+ node .lowLink = assignedIndex
262+
263+ // Push the node onto the stack.
264+ node .onStack = true
265+ b .tarjanStack = append (b .tarjanStack , uint (blockIndex ))
266+
267+ // Process the successors.
268+ for _ , successor := range block .Succs {
269+ // Look up the successor's state.
270+ successorIndex := successor .Index
271+ if successorIndex == blockIndex {
272+ // Handle a self-cycle specially.
273+ node .cyclic = true
274+ continue
275+ }
276+ successorNode := & b .blockInfo [successorIndex ].tarjan
264277
265- // add to queue and checked map
266- queue = append (queue , pred )
267- checked [pred ] = struct {}{}
278+ switch {
279+ case successorNode .lowLink == 0 :
280+ // This node has not yet been visisted.
281+ b .strongConnect (successor )
282+
283+ case ! successorNode .onStack :
284+ // This node has been visited, but is in a different SCC.
285+ // Ignore it, and do not update lowLink.
286+ continue
287+ }
288+
289+ // Update the lowLink index.
290+ // This always uses the min-of-lowlink instead of using index in the on-stack case.
291+ // This is done for two reasons:
292+ // 1. The lowLink update can be shared between the new-node and on-stack cases.
293+ // 2. The assigned index does not need to be saved - it is only needed for root node detection.
294+ if successorNode .lowLink < node .lowLink {
295+ node .lowLink = successorNode .lowLink
268296 }
269297 }
270298
271- return false
299+ if node .lowLink == assignedIndex {
300+ // This is a root node.
301+ // Pop the SCC off the stack.
302+ stack := b .tarjanStack
303+ top := stack [len (stack )- 1 ]
304+ stack = stack [:len (stack )- 1 ]
305+ blocks := b .blockInfo
306+ topNode := & blocks [top ].tarjan
307+ topNode .onStack = false
308+
309+ if top != uint (blockIndex ) {
310+ // The root node is not the only node in the SCC.
311+ // Mark all nodes in this SCC as cyclic.
312+ topNode .cyclic = true
313+ for top != uint (blockIndex ) {
314+ top = stack [len (stack )- 1 ]
315+ stack = stack [:len (stack )- 1 ]
316+ topNode = & blocks [top ].tarjan
317+ topNode .onStack = false
318+ topNode .cyclic = true
319+ }
320+ }
321+
322+ b .tarjanStack = stack
323+ }
324+ }
325+
326+ // tarjanNode holds per-block state for isInLoop and strongConnect.
327+ type tarjanNode struct {
328+ // lowLink is the index of the first visited node that is reachable from this block.
329+ // The lowLink indices are assigned by the SCC search, and do not correspond to b.Index.
330+ // A lowLink of 0 is used as a sentinel to mark a node which has not yet been visited.
331+ lowLink uint
332+
333+ // onStack tracks whether this node is currently on the SCC search stack.
334+ onStack bool
335+
336+ // cyclic indicates whether this block is in a loop.
337+ // If lowLink is 0, strongConnect must be called before reading this field.
338+ cyclic bool
272339}
273340
274341// createDefer emits a single defer instruction, to be run when this function
@@ -410,7 +477,10 @@ func (b *builder) createDefer(instr *ssa.Defer) {
410477
411478 // Put this struct in an allocation.
412479 var alloca llvm.Value
413- if ! isInLoop (instr .Block ()) {
480+ if instr .Block () != b .currentBlock {
481+ panic ("block mismatch" )
482+ }
483+ if ! b .isInLoop () {
414484 // This can safely use a stack allocation.
415485 alloca = llvmutil .CreateEntryBlockAlloca (b .Builder , deferredCallType , "defer.alloca" )
416486 } else {
0 commit comments