| // Copyright 2016 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 ssagen |
| |
| import ( |
| "container/heap" |
| "fmt" |
| |
| "cmd/compile/internal/ir" |
| "cmd/compile/internal/ssa" |
| "cmd/compile/internal/types" |
| "cmd/internal/src" |
| ) |
| |
| // This file contains the algorithm to place phi nodes in a function. |
| // For small functions, we use Braun, Buchwald, Hack, Leißa, Mallon, and Zwinkau. |
| // https://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf |
| // For large functions, we use Sreedhar & Gao: A Linear Time Algorithm for Placing Φ-Nodes. |
| // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.8.1979&rep=rep1&type=pdf |
| |
| const smallBlocks = 500 |
| |
| const debugPhi = false |
| |
| // fwdRefAux wraps an arbitrary ir.Node as an ssa.Aux for use with OpFwdref. |
| type fwdRefAux struct { |
| _ [0]func() // ensure ir.Node isn't compared for equality |
| N ir.Node |
| } |
| |
| func (fwdRefAux) CanBeAnSSAAux() {} |
| |
| // insertPhis finds all the places in the function where a phi is |
| // necessary and inserts them. |
| // Uses FwdRef ops to find all uses of variables, and s.defvars to find |
| // all definitions. |
| // Phi values are inserted, and all FwdRefs are changed to a Copy |
| // of the appropriate phi or definition. |
| // TODO: make this part of cmd/compile/internal/ssa somehow? |
| func (s *state) insertPhis() { |
| if len(s.f.Blocks) <= smallBlocks { |
| sps := simplePhiState{s: s, f: s.f, defvars: s.defvars} |
| sps.insertPhis() |
| return |
| } |
| ps := phiState{s: s, f: s.f, defvars: s.defvars} |
| ps.insertPhis() |
| } |
| |
| type phiState struct { |
| s *state // SSA state |
| f *ssa.Func // function to work on |
| defvars []map[ir.Node]*ssa.Value // defined variables at end of each block |
| |
| varnum map[ir.Node]int32 // variable numbering |
| |
| // properties of the dominator tree |
| idom []*ssa.Block // dominator parents |
| tree []domBlock // dominator child+sibling |
| level []int32 // level in dominator tree (0 = root or unreachable, 1 = children of root, ...) |
| |
| // scratch locations |
| priq blockHeap // priority queue of blocks, higher level (toward leaves) = higher priority |
| q []*ssa.Block // inner loop queue |
| queued *sparseSet // has been put in q |
| hasPhi *sparseSet // has a phi |
| hasDef *sparseSet // has a write of the variable we're processing |
| |
| // miscellaneous |
| placeholder *ssa.Value // value to use as a "not set yet" placeholder. |
| } |
| |
| func (s *phiState) insertPhis() { |
| if debugPhi { |
| fmt.Println(s.f.String()) |
| } |
| |
| // Find all the variables for which we need to match up reads & writes. |
| // This step prunes any basic-block-only variables from consideration. |
| // Generate a numbering for these variables. |
| s.varnum = map[ir.Node]int32{} |
| var vars []ir.Node |
| var vartypes []*types.Type |
| for _, b := range s.f.Blocks { |
| for _, v := range b.Values { |
| if v.Op != ssa.OpFwdRef { |
| continue |
| } |
| var_ := v.Aux.(fwdRefAux).N |
| |
| // Optimization: look back 1 block for the definition. |
| if len(b.Preds) == 1 { |
| c := b.Preds[0].Block() |
| if w := s.defvars[c.ID][var_]; w != nil { |
| v.Op = ssa.OpCopy |
| v.Aux = nil |
| v.AddArg(w) |
| continue |
| } |
| } |
| |
| if _, ok := s.varnum[var_]; ok { |
| continue |
| } |
| s.varnum[var_] = int32(len(vartypes)) |
| if debugPhi { |
| fmt.Printf("var%d = %v\n", len(vartypes), var_) |
| } |
| vars = append(vars, var_) |
| vartypes = append(vartypes, v.Type) |
| } |
| } |
| |
| if len(vartypes) == 0 { |
| return |
| } |
| |
| // Find all definitions of the variables we need to process. |
| // defs[n] contains all the blocks in which variable number n is assigned. |
| defs := make([][]*ssa.Block, len(vartypes)) |
| for _, b := range s.f.Blocks { |
| for var_ := range s.defvars[b.ID] { // TODO: encode defvars some other way (explicit ops)? make defvars[n] a slice instead of a map. |
| if n, ok := s.varnum[var_]; ok { |
| defs[n] = append(defs[n], b) |
| } |
| } |
| } |
| |
| // Make dominator tree. |
| s.idom = s.f.Idom() |
| s.tree = make([]domBlock, s.f.NumBlocks()) |
| for _, b := range s.f.Blocks { |
| p := s.idom[b.ID] |
| if p != nil { |
| s.tree[b.ID].sibling = s.tree[p.ID].firstChild |
| s.tree[p.ID].firstChild = b |
| } |
| } |
| // Compute levels in dominator tree. |
| // With parent pointers we can do a depth-first walk without |
| // any auxiliary storage. |
| s.level = make([]int32, s.f.NumBlocks()) |
| b := s.f.Entry |
| levels: |
| for { |
| if p := s.idom[b.ID]; p != nil { |
| s.level[b.ID] = s.level[p.ID] + 1 |
| if debugPhi { |
| fmt.Printf("level %s = %d\n", b, s.level[b.ID]) |
| } |
| } |
| if c := s.tree[b.ID].firstChild; c != nil { |
| b = c |
| continue |
| } |
| for { |
| if c := s.tree[b.ID].sibling; c != nil { |
| b = c |
| continue levels |
| } |
| b = s.idom[b.ID] |
| if b == nil { |
| break levels |
| } |
| } |
| } |
| |
| // Allocate scratch locations. |
| s.priq.level = s.level |
| s.q = make([]*ssa.Block, 0, s.f.NumBlocks()) |
| s.queued = newSparseSet(s.f.NumBlocks()) |
| s.hasPhi = newSparseSet(s.f.NumBlocks()) |
| s.hasDef = newSparseSet(s.f.NumBlocks()) |
| s.placeholder = s.s.entryNewValue0(ssa.OpUnknown, types.TypeInvalid) |
| |
| // Generate phi ops for each variable. |
| for n := range vartypes { |
| s.insertVarPhis(n, vars[n], defs[n], vartypes[n]) |
| } |
| |
| // Resolve FwdRefs to the correct write or phi. |
| s.resolveFwdRefs() |
| |
| // Erase variable numbers stored in AuxInt fields of phi ops. They are no longer needed. |
| for _, b := range s.f.Blocks { |
| for _, v := range b.Values { |
| if v.Op == ssa.OpPhi { |
| v.AuxInt = 0 |
| } |
| // Any remaining FwdRefs are dead code. |
| if v.Op == ssa.OpFwdRef { |
| v.Op = ssa.OpUnknown |
| v.Aux = nil |
| } |
| } |
| } |
| } |
| |
| func (s *phiState) insertVarPhis(n int, var_ ir.Node, defs []*ssa.Block, typ *types.Type) { |
| priq := &s.priq |
| q := s.q |
| queued := s.queued |
| queued.clear() |
| hasPhi := s.hasPhi |
| hasPhi.clear() |
| hasDef := s.hasDef |
| hasDef.clear() |
| |
| // Add defining blocks to priority queue. |
| for _, b := range defs { |
| priq.a = append(priq.a, b) |
| hasDef.add(b.ID) |
| if debugPhi { |
| fmt.Printf("def of var%d in %s\n", n, b) |
| } |
| } |
| heap.Init(priq) |
| |
| // Visit blocks defining variable n, from deepest to shallowest. |
| for len(priq.a) > 0 { |
| currentRoot := heap.Pop(priq).(*ssa.Block) |
| if debugPhi { |
| fmt.Printf("currentRoot %s\n", currentRoot) |
| } |
| // Walk subtree below definition. |
| // Skip subtrees we've done in previous iterations. |
| // Find edges exiting tree dominated by definition (the dominance frontier). |
| // Insert phis at target blocks. |
| if queued.contains(currentRoot.ID) { |
| s.s.Fatalf("root already in queue") |
| } |
| q = append(q, currentRoot) |
| queued.add(currentRoot.ID) |
| for len(q) > 0 { |
| b := q[len(q)-1] |
| q = q[:len(q)-1] |
| if debugPhi { |
| fmt.Printf(" processing %s\n", b) |
| } |
| |
| currentRootLevel := s.level[currentRoot.ID] |
| for _, e := range b.Succs { |
| c := e.Block() |
| // TODO: if the variable is dead at c, skip it. |
| if s.level[c.ID] > currentRootLevel { |
| // a D-edge, or an edge whose target is in currentRoot's subtree. |
| continue |
| } |
| if hasPhi.contains(c.ID) { |
| continue |
| } |
| // Add a phi to block c for variable n. |
| hasPhi.add(c.ID) |
| v := c.NewValue0I(currentRoot.Pos, ssa.OpPhi, typ, int64(n)) // TODO: line number right? |
| // Note: we store the variable number in the phi's AuxInt field. Used temporarily by phi building. |
| if var_.Op() == ir.ONAME { |
| s.s.addNamedValue(var_.(*ir.Name), v) |
| } |
| for range c.Preds { |
| v.AddArg(s.placeholder) // Actual args will be filled in by resolveFwdRefs. |
| } |
| if debugPhi { |
| fmt.Printf("new phi for var%d in %s: %s\n", n, c, v) |
| } |
| if !hasDef.contains(c.ID) { |
| // There's now a new definition of this variable in block c. |
| // Add it to the priority queue to explore. |
| heap.Push(priq, c) |
| hasDef.add(c.ID) |
| } |
| } |
| |
| // Visit children if they have not been visited yet. |
| for c := s.tree[b.ID].firstChild; c != nil; c = s.tree[c.ID].sibling { |
| if !queued.contains(c.ID) { |
| q = append(q, c) |
| queued.add(c.ID) |
| } |
| } |
| } |
| } |
| } |
| |
| // resolveFwdRefs links all FwdRef uses up to their nearest dominating definition. |
| func (s *phiState) resolveFwdRefs() { |
| // Do a depth-first walk of the dominator tree, keeping track |
| // of the most-recently-seen value for each variable. |
| |
| // Map from variable ID to SSA value at the current point of the walk. |
| values := make([]*ssa.Value, len(s.varnum)) |
| for i := range values { |
| values[i] = s.placeholder |
| } |
| |
| // Stack of work to do. |
| type stackEntry struct { |
| b *ssa.Block // block to explore |
| |
| // variable/value pair to reinstate on exit |
| n int32 // variable ID |
| v *ssa.Value |
| |
| // Note: only one of b or n,v will be set. |
| } |
| var stk []stackEntry |
| |
| stk = append(stk, stackEntry{b: s.f.Entry}) |
| for len(stk) > 0 { |
| work := stk[len(stk)-1] |
| stk = stk[:len(stk)-1] |
| |
| b := work.b |
| if b == nil { |
| // On exit from a block, this case will undo any assignments done below. |
| values[work.n] = work.v |
| continue |
| } |
| |
| // Process phis as new defs. They come before FwdRefs in this block. |
| for _, v := range b.Values { |
| if v.Op != ssa.OpPhi { |
| continue |
| } |
| n := int32(v.AuxInt) |
| // Remember the old assignment so we can undo it when we exit b. |
| stk = append(stk, stackEntry{n: n, v: values[n]}) |
| // Record the new assignment. |
| values[n] = v |
| } |
| |
| // Replace a FwdRef op with the current incoming value for its variable. |
| for _, v := range b.Values { |
| if v.Op != ssa.OpFwdRef { |
| continue |
| } |
| n := s.varnum[v.Aux.(fwdRefAux).N] |
| v.Op = ssa.OpCopy |
| v.Aux = nil |
| v.AddArg(values[n]) |
| } |
| |
| // Establish values for variables defined in b. |
| for var_, v := range s.defvars[b.ID] { |
| n, ok := s.varnum[var_] |
| if !ok { |
| // some variable not live across a basic block boundary. |
| continue |
| } |
| // Remember the old assignment so we can undo it when we exit b. |
| stk = append(stk, stackEntry{n: n, v: values[n]}) |
| // Record the new assignment. |
| values[n] = v |
| } |
| |
| // Replace phi args in successors with the current incoming value. |
| for _, e := range b.Succs { |
| c, i := e.Block(), e.Index() |
| for j := len(c.Values) - 1; j >= 0; j-- { |
| v := c.Values[j] |
| if v.Op != ssa.OpPhi { |
| break // All phis will be at the end of the block during phi building. |
| } |
| // Only set arguments that have been resolved. |
| // For very wide CFGs, this significantly speeds up phi resolution. |
| // See golang.org/issue/8225. |
| if w := values[v.AuxInt]; w.Op != ssa.OpUnknown { |
| v.SetArg(i, w) |
| } |
| } |
| } |
| |
| // Walk children in dominator tree. |
| for c := s.tree[b.ID].firstChild; c != nil; c = s.tree[c.ID].sibling { |
| stk = append(stk, stackEntry{b: c}) |
| } |
| } |
| } |
| |
| // domBlock contains extra per-block information to record the dominator tree. |
| type domBlock struct { |
| firstChild *ssa.Block // first child of block in dominator tree |
| sibling *ssa.Block // next child of parent in dominator tree |
| } |
| |
| // A block heap is used as a priority queue to implement the PiggyBank |
| // from Sreedhar and Gao. That paper uses an array which is better |
| // asymptotically but worse in the common case when the PiggyBank |
| // holds a sparse set of blocks. |
| type blockHeap struct { |
| a []*ssa.Block // block IDs in heap |
| level []int32 // depth in dominator tree (static, used for determining priority) |
| } |
| |
| func (h *blockHeap) Len() int { return len(h.a) } |
| func (h *blockHeap) Swap(i, j int) { a := h.a; a[i], a[j] = a[j], a[i] } |
| |
| func (h *blockHeap) Push(x interface{}) { |
| v := x.(*ssa.Block) |
| h.a = append(h.a, v) |
| } |
| func (h *blockHeap) Pop() interface{} { |
| old := h.a |
| n := len(old) |
| x := old[n-1] |
| h.a = old[:n-1] |
| return x |
| } |
| func (h *blockHeap) Less(i, j int) bool { |
| return h.level[h.a[i].ID] > h.level[h.a[j].ID] |
| } |
| |
| // TODO: stop walking the iterated domininance frontier when |
| // the variable is dead. Maybe detect that by checking if the |
| // node we're on is reverse dominated by all the reads? |
| // Reverse dominated by the highest common successor of all the reads? |
| |
| // copy of ../ssa/sparseset.go |
| // TODO: move this file to ../ssa, then use sparseSet there. |
| type sparseSet struct { |
| dense []ssa.ID |
| sparse []int32 |
| } |
| |
| // newSparseSet returns a sparseSet that can represent |
| // integers between 0 and n-1 |
| func newSparseSet(n int) *sparseSet { |
| return &sparseSet{dense: nil, sparse: make([]int32, n)} |
| } |
| |
| func (s *sparseSet) contains(x ssa.ID) bool { |
| i := s.sparse[x] |
| return i < int32(len(s.dense)) && s.dense[i] == x |
| } |
| |
| func (s *sparseSet) add(x ssa.ID) { |
| i := s.sparse[x] |
| if i < int32(len(s.dense)) && s.dense[i] == x { |
| return |
| } |
| s.dense = append(s.dense, x) |
| s.sparse[x] = int32(len(s.dense)) - 1 |
| } |
| |
| func (s *sparseSet) clear() { |
| s.dense = s.dense[:0] |
| } |
| |
| // Variant to use for small functions. |
| type simplePhiState struct { |
| s *state // SSA state |
| f *ssa.Func // function to work on |
| fwdrefs []*ssa.Value // list of FwdRefs to be processed |
| defvars []map[ir.Node]*ssa.Value // defined variables at end of each block |
| reachable []bool // which blocks are reachable |
| } |
| |
| func (s *simplePhiState) insertPhis() { |
| s.reachable = ssa.ReachableBlocks(s.f) |
| |
| // Find FwdRef ops. |
| for _, b := range s.f.Blocks { |
| for _, v := range b.Values { |
| if v.Op != ssa.OpFwdRef { |
| continue |
| } |
| s.fwdrefs = append(s.fwdrefs, v) |
| var_ := v.Aux.(fwdRefAux).N |
| if _, ok := s.defvars[b.ID][var_]; !ok { |
| s.defvars[b.ID][var_] = v // treat FwdDefs as definitions. |
| } |
| } |
| } |
| |
| var args []*ssa.Value |
| |
| loop: |
| for len(s.fwdrefs) > 0 { |
| v := s.fwdrefs[len(s.fwdrefs)-1] |
| s.fwdrefs = s.fwdrefs[:len(s.fwdrefs)-1] |
| b := v.Block |
| var_ := v.Aux.(fwdRefAux).N |
| if b == s.f.Entry { |
| // No variable should be live at entry. |
| s.s.Fatalf("Value live at entry. It shouldn't be. func %s, node %v, value %v", s.f.Name, var_, v) |
| } |
| if !s.reachable[b.ID] { |
| // This block is dead. |
| // It doesn't matter what we use here as long as it is well-formed. |
| v.Op = ssa.OpUnknown |
| v.Aux = nil |
| continue |
| } |
| // Find variable value on each predecessor. |
| args = args[:0] |
| for _, e := range b.Preds { |
| args = append(args, s.lookupVarOutgoing(e.Block(), v.Type, var_, v.Pos)) |
| } |
| |
| // Decide if we need a phi or not. We need a phi if there |
| // are two different args (which are both not v). |
| var w *ssa.Value |
| for _, a := range args { |
| if a == v { |
| continue // self-reference |
| } |
| if a == w { |
| continue // already have this witness |
| } |
| if w != nil { |
| // two witnesses, need a phi value |
| v.Op = ssa.OpPhi |
| v.AddArgs(args...) |
| v.Aux = nil |
| continue loop |
| } |
| w = a // save witness |
| } |
| if w == nil { |
| s.s.Fatalf("no witness for reachable phi %s", v) |
| } |
| // One witness. Make v a copy of w. |
| v.Op = ssa.OpCopy |
| v.Aux = nil |
| v.AddArg(w) |
| } |
| } |
| |
| // lookupVarOutgoing finds the variable's value at the end of block b. |
| func (s *simplePhiState) lookupVarOutgoing(b *ssa.Block, t *types.Type, var_ ir.Node, line src.XPos) *ssa.Value { |
| for { |
| if v := s.defvars[b.ID][var_]; v != nil { |
| return v |
| } |
| // The variable is not defined by b and we haven't looked it up yet. |
| // If b has exactly one predecessor, loop to look it up there. |
| // Otherwise, give up and insert a new FwdRef and resolve it later. |
| if len(b.Preds) != 1 { |
| break |
| } |
| b = b.Preds[0].Block() |
| if !s.reachable[b.ID] { |
| // This is rare; it happens with oddly interleaved infinite loops in dead code. |
| // See issue 19783. |
| break |
| } |
| } |
| // Generate a FwdRef for the variable and return that. |
| v := b.NewValue0A(line, ssa.OpFwdRef, t, fwdRefAux{N: var_}) |
| s.defvars[b.ID][var_] = v |
| if var_.Op() == ir.ONAME { |
| s.s.addNamedValue(var_.(*ir.Name), v) |
| } |
| s.fwdrefs = append(s.fwdrefs, v) |
| return v |
| } |