// Copyright 2024 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 ssa import ( "sync/atomic" ) // Each task has two states: it is initially "active", // and transitions to "done". // // tasks form a directed graph. An edge from x to y (with y in x.edges) // indicates that the task x waits on the task y to be done. // Cycles are permitted. // // Calling x.wait() blocks the calling goroutine until task x, // and all the tasks transitively reachable from x are done. // // The nil *task is always considered done. type task struct { done chan unit // close when the task is done. edges map[*task]unit // set of predecessors of this task. transitive atomic.Bool // true once it is known all predecessors are done. } func (x *task) isTransitivelyDone() bool { return x == nil || x.transitive.Load() } // addEdge creates an edge from x to y, indicating that // x.wait() will not return before y is done. // All calls to x.addEdge(...) should happen before x.markDone(). func (x *task) addEdge(y *task) { if x == y || y.isTransitivelyDone() { return // no work remaining } // heuristic done check select { case <-x.done: panic("cannot add an edge to a done task") default: } if x.edges == nil { x.edges = make(map[*task]unit) } x.edges[y] = unit{} } // markDone changes the task's state to markDone. func (x *task) markDone() { if x != nil { close(x.done) } } // wait blocks until x and all the tasks it can reach through edges are done. func (x *task) wait() { if x.isTransitivelyDone() { return // already known to be done. Skip allocations. } // Use BFS to wait on u.done to be closed, for all u transitively // reachable from x via edges. // // This work can be repeated by multiple workers doing wait(). // // Note: Tarjan's SCC algorithm is able to mark SCCs as transitively done // as soon as the SCC has been visited. This is theoretically faster, but is // a more complex algorithm. Until we have evidence, we need the more complex // algorithm, the simpler algorithm BFS is implemented. // // In Go 1.23, ssa/TestStdlib reaches <=3 *tasks per wait() in most schedules // On some schedules, there is a cycle building net/http and internal/trace/testtrace // due to slices functions. work := []*task{x} enqueued := map[*task]unit{x: {}} for i := 0; i < len(work); i++ { u := work[i] if u.isTransitivelyDone() { // already transitively done work[i] = nil continue } <-u.done // wait for u to be marked done. for v := range u.edges { if _, ok := enqueued[v]; !ok { enqueued[v] = unit{} work = append(work, v) } } } // work is transitively closed over dependencies. // u in work is done (or transitively done and skipped). // u is transitively done. for _, u := range work { if u != nil { x.transitive.Store(true) } } }