104 lines
2.9 KiB
Go
104 lines
2.9 KiB
Go
// 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)
|
|
}
|
|
}
|
|
}
|