mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
analysis for - is this block only reached through feature checks? - does the function signature imply AVX-something? - is there an instruction in this block which implies AVX-something? and keep track of which features those are. Features = AVX, AVX2, AVX512, etc. Has a test. Change-Id: I0b6f2e87d01ec587818db11cf71fac1e4d500650 Reviewed-on: https://go-review.googlesource.com/c/go/+/706337 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Junyang Shao <shaojunyang@google.com>
504 lines
13 KiB
Go
504 lines
13 KiB
Go
// Copyright 2015 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 (
|
|
"cmd/internal/src"
|
|
"fmt"
|
|
)
|
|
|
|
// Block represents a basic block in the control flow graph of a function.
|
|
type Block struct {
|
|
// A unique identifier for the block. The system will attempt to allocate
|
|
// these IDs densely, but no guarantees.
|
|
ID ID
|
|
|
|
// Source position for block's control operation
|
|
Pos src.XPos
|
|
|
|
// What cpu features (AVXnnn, SVEyyy) are implied to reach/execute this block?
|
|
CPUfeatures CPUfeatures
|
|
|
|
// The kind of block this is.
|
|
Kind BlockKind
|
|
|
|
// Likely direction for branches.
|
|
// If BranchLikely, Succs[0] is the most likely branch taken.
|
|
// If BranchUnlikely, Succs[1] is the most likely branch taken.
|
|
// Ignored if len(Succs) < 2.
|
|
// Fatal if not BranchUnknown and len(Succs) > 2.
|
|
Likely BranchPrediction
|
|
|
|
// After flagalloc, records whether flags are live at the end of the block.
|
|
FlagsLiveAtEnd bool
|
|
|
|
// A block that would be good to align (according to the optimizer's guesses)
|
|
Hotness Hotness
|
|
|
|
// Subsequent blocks, if any. The number and order depend on the block kind.
|
|
Succs []Edge
|
|
|
|
// Inverse of successors.
|
|
// The order is significant to Phi nodes in the block.
|
|
// TODO: predecessors is a pain to maintain. Can we somehow order phi
|
|
// arguments by block id and have this field computed explicitly when needed?
|
|
Preds []Edge
|
|
|
|
// A list of values that determine how the block is exited. The number
|
|
// and type of control values depends on the Kind of the block. For
|
|
// instance, a BlockIf has a single boolean control value and BlockExit
|
|
// has a single memory control value.
|
|
//
|
|
// The ControlValues() method may be used to get a slice with the non-nil
|
|
// control values that can be ranged over.
|
|
//
|
|
// Controls[1] must be nil if Controls[0] is nil.
|
|
Controls [2]*Value
|
|
|
|
// Auxiliary info for the block. Its value depends on the Kind.
|
|
Aux Aux
|
|
AuxInt int64
|
|
|
|
// The unordered set of Values that define the operation of this block.
|
|
// After the scheduling pass, this list is ordered.
|
|
Values []*Value
|
|
|
|
// The containing function
|
|
Func *Func
|
|
|
|
// Storage for Succs, Preds and Values.
|
|
succstorage [2]Edge
|
|
predstorage [4]Edge
|
|
valstorage [9]*Value
|
|
}
|
|
|
|
// Edge represents a CFG edge.
|
|
// Example edges for b branching to either c or d.
|
|
// (c and d have other predecessors.)
|
|
//
|
|
// b.Succs = [{c,3}, {d,1}]
|
|
// c.Preds = [?, ?, ?, {b,0}]
|
|
// d.Preds = [?, {b,1}, ?]
|
|
//
|
|
// These indexes allow us to edit the CFG in constant time.
|
|
// In addition, it informs phi ops in degenerate cases like:
|
|
//
|
|
// b:
|
|
// if k then c else c
|
|
// c:
|
|
// v = Phi(x, y)
|
|
//
|
|
// Then the indexes tell you whether x is chosen from
|
|
// the if or else branch from b.
|
|
//
|
|
// b.Succs = [{c,0},{c,1}]
|
|
// c.Preds = [{b,0},{b,1}]
|
|
//
|
|
// means x is chosen if k is true.
|
|
type Edge struct {
|
|
// block edge goes to (in a Succs list) or from (in a Preds list)
|
|
b *Block
|
|
// index of reverse edge. Invariant:
|
|
// e := x.Succs[idx]
|
|
// e.b.Preds[e.i] = Edge{x,idx}
|
|
// and similarly for predecessors.
|
|
i int
|
|
}
|
|
|
|
func (e Edge) Block() *Block {
|
|
return e.b
|
|
}
|
|
func (e Edge) Index() int {
|
|
return e.i
|
|
}
|
|
func (e Edge) String() string {
|
|
return fmt.Sprintf("{%v,%d}", e.b, e.i)
|
|
}
|
|
|
|
// BlockKind is the kind of SSA block.
|
|
type BlockKind uint8
|
|
|
|
// short form print
|
|
func (b *Block) String() string {
|
|
return fmt.Sprintf("b%d", b.ID)
|
|
}
|
|
|
|
// long form print
|
|
func (b *Block) LongString() string {
|
|
s := b.Kind.String()
|
|
if b.Aux != nil {
|
|
s += fmt.Sprintf(" {%s}", b.Aux)
|
|
}
|
|
if t := b.AuxIntString(); t != "" {
|
|
s += fmt.Sprintf(" [%s]", t)
|
|
}
|
|
for _, c := range b.ControlValues() {
|
|
s += fmt.Sprintf(" %s", c)
|
|
}
|
|
if len(b.Succs) > 0 {
|
|
s += " ->"
|
|
for _, c := range b.Succs {
|
|
s += " " + c.b.String()
|
|
}
|
|
}
|
|
switch b.Likely {
|
|
case BranchUnlikely:
|
|
s += " (unlikely)"
|
|
case BranchLikely:
|
|
s += " (likely)"
|
|
}
|
|
return s
|
|
}
|
|
|
|
// NumControls returns the number of non-nil control values the
|
|
// block has.
|
|
func (b *Block) NumControls() int {
|
|
if b.Controls[0] == nil {
|
|
return 0
|
|
}
|
|
if b.Controls[1] == nil {
|
|
return 1
|
|
}
|
|
return 2
|
|
}
|
|
|
|
// ControlValues returns a slice containing the non-nil control
|
|
// values of the block. The index of each control value will be
|
|
// the same as it is in the Controls property and can be used
|
|
// in ReplaceControl calls.
|
|
func (b *Block) ControlValues() []*Value {
|
|
if b.Controls[0] == nil {
|
|
return b.Controls[:0]
|
|
}
|
|
if b.Controls[1] == nil {
|
|
return b.Controls[:1]
|
|
}
|
|
return b.Controls[:2]
|
|
}
|
|
|
|
// SetControl removes all existing control values and then adds
|
|
// the control value provided. The number of control values after
|
|
// a call to SetControl will always be 1.
|
|
func (b *Block) SetControl(v *Value) {
|
|
b.ResetControls()
|
|
b.Controls[0] = v
|
|
v.Uses++
|
|
}
|
|
|
|
// ResetControls sets the number of controls for the block to 0.
|
|
func (b *Block) ResetControls() {
|
|
if b.Controls[0] != nil {
|
|
b.Controls[0].Uses--
|
|
}
|
|
if b.Controls[1] != nil {
|
|
b.Controls[1].Uses--
|
|
}
|
|
b.Controls = [2]*Value{} // reset both controls to nil
|
|
}
|
|
|
|
// AddControl appends a control value to the existing list of control values.
|
|
func (b *Block) AddControl(v *Value) {
|
|
i := b.NumControls()
|
|
b.Controls[i] = v // panics if array is full
|
|
v.Uses++
|
|
}
|
|
|
|
// ReplaceControl exchanges the existing control value at the index provided
|
|
// for the new value. The index must refer to a valid control value.
|
|
func (b *Block) ReplaceControl(i int, v *Value) {
|
|
b.Controls[i].Uses--
|
|
b.Controls[i] = v
|
|
v.Uses++
|
|
}
|
|
|
|
// CopyControls replaces the controls for this block with those from the
|
|
// provided block. The provided block is not modified.
|
|
func (b *Block) CopyControls(from *Block) {
|
|
if b == from {
|
|
return
|
|
}
|
|
b.ResetControls()
|
|
for _, c := range from.ControlValues() {
|
|
b.AddControl(c)
|
|
}
|
|
}
|
|
|
|
// Reset sets the block to the provided kind and clears all the blocks control
|
|
// and auxiliary values. Other properties of the block, such as its successors,
|
|
// predecessors and values are left unmodified.
|
|
func (b *Block) Reset(kind BlockKind) {
|
|
b.Kind = kind
|
|
b.ResetControls()
|
|
b.Aux = nil
|
|
b.AuxInt = 0
|
|
}
|
|
|
|
// resetWithControl resets b and adds control v.
|
|
// It is equivalent to b.Reset(kind); b.AddControl(v),
|
|
// except that it is one call instead of two and avoids a bounds check.
|
|
// It is intended for use by rewrite rules, where this matters.
|
|
func (b *Block) resetWithControl(kind BlockKind, v *Value) {
|
|
b.Kind = kind
|
|
b.ResetControls()
|
|
b.Aux = nil
|
|
b.AuxInt = 0
|
|
b.Controls[0] = v
|
|
v.Uses++
|
|
}
|
|
|
|
// resetWithControl2 resets b and adds controls v and w.
|
|
// It is equivalent to b.Reset(kind); b.AddControl(v); b.AddControl(w),
|
|
// except that it is one call instead of three and avoids two bounds checks.
|
|
// It is intended for use by rewrite rules, where this matters.
|
|
func (b *Block) resetWithControl2(kind BlockKind, v, w *Value) {
|
|
b.Kind = kind
|
|
b.ResetControls()
|
|
b.Aux = nil
|
|
b.AuxInt = 0
|
|
b.Controls[0] = v
|
|
b.Controls[1] = w
|
|
v.Uses++
|
|
w.Uses++
|
|
}
|
|
|
|
// truncateValues truncates b.Values at the ith element, zeroing subsequent elements.
|
|
// The values in b.Values after i must already have had their args reset,
|
|
// to maintain correct value uses counts.
|
|
func (b *Block) truncateValues(i int) {
|
|
clear(b.Values[i:])
|
|
b.Values = b.Values[:i]
|
|
}
|
|
|
|
// AddEdgeTo adds an edge from block b to block c.
|
|
func (b *Block) AddEdgeTo(c *Block) {
|
|
i := len(b.Succs)
|
|
j := len(c.Preds)
|
|
b.Succs = append(b.Succs, Edge{c, j})
|
|
c.Preds = append(c.Preds, Edge{b, i})
|
|
b.Func.invalidateCFG()
|
|
}
|
|
|
|
// removePred removes the ith input edge from b.
|
|
// It is the responsibility of the caller to remove
|
|
// the corresponding successor edge, and adjust any
|
|
// phi values by calling b.removePhiArg(v, i).
|
|
func (b *Block) removePred(i int) {
|
|
n := len(b.Preds) - 1
|
|
if i != n {
|
|
e := b.Preds[n]
|
|
b.Preds[i] = e
|
|
// Update the other end of the edge we moved.
|
|
e.b.Succs[e.i].i = i
|
|
}
|
|
b.Preds[n] = Edge{}
|
|
b.Preds = b.Preds[:n]
|
|
b.Func.invalidateCFG()
|
|
}
|
|
|
|
// removeSucc removes the ith output edge from b.
|
|
// It is the responsibility of the caller to remove
|
|
// the corresponding predecessor edge.
|
|
// Note that this potentially reorders successors of b, so it
|
|
// must be used very carefully.
|
|
func (b *Block) removeSucc(i int) {
|
|
n := len(b.Succs) - 1
|
|
if i != n {
|
|
e := b.Succs[n]
|
|
b.Succs[i] = e
|
|
// Update the other end of the edge we moved.
|
|
e.b.Preds[e.i].i = i
|
|
}
|
|
b.Succs[n] = Edge{}
|
|
b.Succs = b.Succs[:n]
|
|
b.Func.invalidateCFG()
|
|
}
|
|
|
|
func (b *Block) swapSuccessors() {
|
|
if len(b.Succs) != 2 {
|
|
b.Fatalf("swapSuccessors with len(Succs)=%d", len(b.Succs))
|
|
}
|
|
e0 := b.Succs[0]
|
|
e1 := b.Succs[1]
|
|
b.Succs[0] = e1
|
|
b.Succs[1] = e0
|
|
e0.b.Preds[e0.i].i = 1
|
|
e1.b.Preds[e1.i].i = 0
|
|
b.Likely *= -1
|
|
}
|
|
|
|
// Swaps b.Succs[x] and b.Succs[y].
|
|
func (b *Block) swapSuccessorsByIdx(x, y int) {
|
|
if x == y {
|
|
return
|
|
}
|
|
ex := b.Succs[x]
|
|
ey := b.Succs[y]
|
|
b.Succs[x] = ey
|
|
b.Succs[y] = ex
|
|
ex.b.Preds[ex.i].i = y
|
|
ey.b.Preds[ey.i].i = x
|
|
}
|
|
|
|
// removePhiArg removes the ith arg from phi.
|
|
// It must be called after calling b.removePred(i) to
|
|
// adjust the corresponding phi value of the block:
|
|
//
|
|
// b.removePred(i)
|
|
// for _, v := range b.Values {
|
|
//
|
|
// if v.Op != OpPhi {
|
|
// continue
|
|
// }
|
|
// b.removePhiArg(v, i)
|
|
//
|
|
// }
|
|
func (b *Block) removePhiArg(phi *Value, i int) {
|
|
n := len(b.Preds)
|
|
if numPhiArgs := len(phi.Args); numPhiArgs-1 != n {
|
|
b.Fatalf("inconsistent state for %v, num predecessors: %d, num phi args: %d", phi, n, numPhiArgs)
|
|
}
|
|
phi.Args[i].Uses--
|
|
phi.Args[i] = phi.Args[n]
|
|
phi.Args[n] = nil
|
|
phi.Args = phi.Args[:n]
|
|
phielimValue(phi)
|
|
}
|
|
|
|
// uniquePred returns the predecessor of b, if there is exactly one.
|
|
// Returns nil otherwise.
|
|
func (b *Block) uniquePred() *Block {
|
|
if len(b.Preds) != 1 {
|
|
return nil
|
|
}
|
|
return b.Preds[0].b
|
|
}
|
|
|
|
// LackingPos indicates whether b is a block whose position should be inherited
|
|
// from its successors. This is true if all the values within it have unreliable positions
|
|
// and if it is "plain", meaning that there is no control flow that is also very likely
|
|
// to correspond to a well-understood source position.
|
|
func (b *Block) LackingPos() bool {
|
|
// Non-plain predecessors are If or Defer, which both (1) have two successors,
|
|
// which might have different line numbers and (2) correspond to statements
|
|
// in the source code that have positions, so this case ought not occur anyway.
|
|
if b.Kind != BlockPlain {
|
|
return false
|
|
}
|
|
if b.Pos != src.NoXPos {
|
|
return false
|
|
}
|
|
for _, v := range b.Values {
|
|
if v.LackingPos() {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (b *Block) AuxIntString() string {
|
|
switch b.Kind.AuxIntType() {
|
|
case "int8":
|
|
return fmt.Sprintf("%v", int8(b.AuxInt))
|
|
case "uint8":
|
|
return fmt.Sprintf("%v", uint8(b.AuxInt))
|
|
case "": // no aux int type
|
|
return ""
|
|
default: // type specified but not implemented - print as int64
|
|
return fmt.Sprintf("%v", b.AuxInt)
|
|
}
|
|
}
|
|
|
|
// likelyBranch reports whether block b is the likely branch of all of its predecessors.
|
|
func (b *Block) likelyBranch() bool {
|
|
if len(b.Preds) == 0 {
|
|
return false
|
|
}
|
|
for _, e := range b.Preds {
|
|
p := e.b
|
|
if len(p.Succs) == 1 || len(p.Succs) == 2 && (p.Likely == BranchLikely && p.Succs[0].b == b ||
|
|
p.Likely == BranchUnlikely && p.Succs[1].b == b) {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (b *Block) Logf(msg string, args ...interface{}) { b.Func.Logf(msg, args...) }
|
|
func (b *Block) Log() bool { return b.Func.Log() }
|
|
func (b *Block) Fatalf(msg string, args ...interface{}) { b.Func.Fatalf(msg, args...) }
|
|
|
|
type BranchPrediction int8
|
|
|
|
const (
|
|
BranchUnlikely = BranchPrediction(-1)
|
|
BranchUnknown = BranchPrediction(0)
|
|
BranchLikely = BranchPrediction(+1)
|
|
)
|
|
|
|
type Hotness int8 // Could use negative numbers for specifically non-hot blocks, but don't, yet.
|
|
const (
|
|
// These values are arranged in what seems to be order of increasing alignment importance.
|
|
// Currently only a few are relevant. Implicitly, they are all in a loop.
|
|
HotNotFlowIn Hotness = 1 << iota // This block is only reached by branches
|
|
HotInitial // In the block order, the first one for a given loop. Not necessarily topological header.
|
|
HotPgo // By PGO-based heuristics, this block occurs in a hot loop
|
|
|
|
HotNot = 0
|
|
HotInitialNotFlowIn = HotInitial | HotNotFlowIn // typically first block of a rotated loop, loop is entered with a branch (not to this block). No PGO
|
|
HotPgoInitial = HotPgo | HotInitial // special case; single block loop, initial block is header block has a flow-in entry, but PGO says it is hot
|
|
HotPgoInitialNotFLowIn = HotPgo | HotInitial | HotNotFlowIn // PGO says it is hot, and the loop is rotated so flow enters loop with a branch
|
|
)
|
|
|
|
type CPUfeatures uint32
|
|
|
|
const (
|
|
CPUNone CPUfeatures = 0
|
|
CPUAll CPUfeatures = ^CPUfeatures(0)
|
|
CPUavx CPUfeatures = 1 << iota
|
|
CPUavx2
|
|
CPUavxvnni
|
|
CPUavx512
|
|
CPUbitalg
|
|
CPUgfni
|
|
CPUvbmi
|
|
CPUvbmi2
|
|
CPUvpopcntdq
|
|
CPUavx512vnni
|
|
|
|
CPUneon
|
|
CPUsve2
|
|
)
|
|
|
|
func (f CPUfeatures) String() string {
|
|
if f == CPUNone {
|
|
return "none"
|
|
}
|
|
if f == CPUAll {
|
|
return "all"
|
|
}
|
|
s := ""
|
|
foo := func(what string, feat CPUfeatures) {
|
|
if feat&f != 0 {
|
|
if s != "" {
|
|
s += "+"
|
|
}
|
|
s += what
|
|
}
|
|
}
|
|
foo("avx", CPUavx)
|
|
foo("avx2", CPUavx2)
|
|
foo("avx512", CPUavx512)
|
|
foo("avxvnni", CPUavxvnni)
|
|
foo("bitalg", CPUbitalg)
|
|
foo("gfni", CPUgfni)
|
|
foo("vbmi", CPUvbmi)
|
|
foo("vbmi2", CPUvbmi2)
|
|
foo("popcntdq", CPUvpopcntdq)
|
|
foo("avx512vnni", CPUavx512vnni)
|
|
|
|
return s
|
|
}
|