|  | // 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 ssa | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | ) | 
|  |  | 
|  | type loop struct { | 
|  | header *Block // The header node of this (reducible) loop | 
|  | outer  *loop  // loop containing this loop | 
|  |  | 
|  | // By default, children, exits, and depth are not initialized. | 
|  | children []*loop  // loops nested directly within this loop. Initialized by assembleChildren(). | 
|  | exits    []*Block // exits records blocks reached by exits from this loop. Initialized by findExits(). | 
|  |  | 
|  | // Next three fields used by regalloc and/or | 
|  | // aid in computation of inner-ness and list of blocks. | 
|  | nBlocks int32 // Number of blocks in this loop but not within inner loops | 
|  | depth   int16 // Nesting depth of the loop; 1 is outermost. Initialized by calculateDepths(). | 
|  | isInner bool  // True if never discovered to contain a loop | 
|  |  | 
|  | // register allocation uses this. | 
|  | containsUnavoidableCall bool // True if all paths through the loop have a call | 
|  | } | 
|  |  | 
|  | // outerinner records that outer contains inner | 
|  | func (sdom SparseTree) outerinner(outer, inner *loop) { | 
|  | // There could be other outer loops found in some random order, | 
|  | // locate the new outer loop appropriately among them. | 
|  |  | 
|  | // Outer loop headers dominate inner loop headers. | 
|  | // Use this to put the "new" "outer" loop in the right place. | 
|  | oldouter := inner.outer | 
|  | for oldouter != nil && sdom.isAncestor(outer.header, oldouter.header) { | 
|  | inner = oldouter | 
|  | oldouter = inner.outer | 
|  | } | 
|  | if outer == oldouter { | 
|  | return | 
|  | } | 
|  | if oldouter != nil { | 
|  | sdom.outerinner(oldouter, outer) | 
|  | } | 
|  |  | 
|  | inner.outer = outer | 
|  | outer.isInner = false | 
|  | } | 
|  |  | 
|  | func checkContainsCall(bb *Block) bool { | 
|  | if bb.Kind == BlockDefer { | 
|  | return true | 
|  | } | 
|  | for _, v := range bb.Values { | 
|  | if opcodeTable[v.Op].call { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | type loopnest struct { | 
|  | f              *Func | 
|  | b2l            []*loop | 
|  | po             []*Block | 
|  | sdom           SparseTree | 
|  | loops          []*loop | 
|  | hasIrreducible bool // TODO current treatment of irreducible loops is very flaky, if accurate loops are needed, must punt at function level. | 
|  |  | 
|  | // Record which of the lazily initialized fields have actually been initialized. | 
|  | initializedChildren, initializedDepth, initializedExits bool | 
|  | } | 
|  |  | 
|  | func min8(a, b int8) int8 { | 
|  | if a < b { | 
|  | return a | 
|  | } | 
|  | return b | 
|  | } | 
|  |  | 
|  | func max8(a, b int8) int8 { | 
|  | if a > b { | 
|  | return a | 
|  | } | 
|  | return b | 
|  | } | 
|  |  | 
|  | const ( | 
|  | blDEFAULT = 0 | 
|  | blMin     = blDEFAULT | 
|  | blCALL    = 1 | 
|  | blRET     = 2 | 
|  | blEXIT    = 3 | 
|  | ) | 
|  |  | 
|  | var bllikelies = [4]string{"default", "call", "ret", "exit"} | 
|  |  | 
|  | func describePredictionAgrees(b *Block, prediction BranchPrediction) string { | 
|  | s := "" | 
|  | if prediction == b.Likely { | 
|  | s = " (agrees with previous)" | 
|  | } else if b.Likely != BranchUnknown { | 
|  | s = " (disagrees with previous, ignored)" | 
|  | } | 
|  | return s | 
|  | } | 
|  |  | 
|  | func describeBranchPrediction(f *Func, b *Block, likely, not int8, prediction BranchPrediction) { | 
|  | f.Warnl(b.Pos, "Branch prediction rule %s < %s%s", | 
|  | bllikelies[likely-blMin], bllikelies[not-blMin], describePredictionAgrees(b, prediction)) | 
|  | } | 
|  |  | 
|  | func likelyadjust(f *Func) { | 
|  | // The values assigned to certain and local only matter | 
|  | // in their rank order.  0 is default, more positive | 
|  | // is less likely. It's possible to assign a negative | 
|  | // unlikeliness (though not currently the case). | 
|  | certain := make([]int8, f.NumBlocks()) // In the long run, all outcomes are at least this bad. Mainly for Exit | 
|  | local := make([]int8, f.NumBlocks())   // for our immediate predecessors. | 
|  |  | 
|  | po := f.postorder() | 
|  | nest := f.loopnest() | 
|  | b2l := nest.b2l | 
|  |  | 
|  | for _, b := range po { | 
|  | switch b.Kind { | 
|  | case BlockExit: | 
|  | // Very unlikely. | 
|  | local[b.ID] = blEXIT | 
|  | certain[b.ID] = blEXIT | 
|  |  | 
|  | // Ret, it depends. | 
|  | case BlockRet, BlockRetJmp: | 
|  | local[b.ID] = blRET | 
|  | certain[b.ID] = blRET | 
|  |  | 
|  | // Calls. TODO not all calls are equal, names give useful clues. | 
|  | // Any name-based heuristics are only relative to other calls, | 
|  | // and less influential than inferences from loop structure. | 
|  | case BlockDefer: | 
|  | local[b.ID] = blCALL | 
|  | certain[b.ID] = max8(blCALL, certain[b.Succs[0].b.ID]) | 
|  |  | 
|  | default: | 
|  | if len(b.Succs) == 1 { | 
|  | certain[b.ID] = certain[b.Succs[0].b.ID] | 
|  | } else if len(b.Succs) == 2 { | 
|  | // If successor is an unvisited backedge, it's in loop and we don't care. | 
|  | // Its default unlikely is also zero which is consistent with favoring loop edges. | 
|  | // Notice that this can act like a "reset" on unlikeliness at loops; the | 
|  | // default "everything returns" unlikeliness is erased by min with the | 
|  | // backedge likeliness; however a loop with calls on every path will be | 
|  | // tagged with call cost. Net effect is that loop entry is favored. | 
|  | b0 := b.Succs[0].b.ID | 
|  | b1 := b.Succs[1].b.ID | 
|  | certain[b.ID] = min8(certain[b0], certain[b1]) | 
|  |  | 
|  | l := b2l[b.ID] | 
|  | l0 := b2l[b0] | 
|  | l1 := b2l[b1] | 
|  |  | 
|  | prediction := b.Likely | 
|  | // Weak loop heuristic -- both source and at least one dest are in loops, | 
|  | // and there is a difference in the destinations. | 
|  | // TODO what is best arrangement for nested loops? | 
|  | if l != nil && l0 != l1 { | 
|  | noprediction := false | 
|  | switch { | 
|  | // prefer not to exit loops | 
|  | case l1 == nil: | 
|  | prediction = BranchLikely | 
|  | case l0 == nil: | 
|  | prediction = BranchUnlikely | 
|  |  | 
|  | // prefer to stay in loop, not exit to outer. | 
|  | case l == l0: | 
|  | prediction = BranchLikely | 
|  | case l == l1: | 
|  | prediction = BranchUnlikely | 
|  | default: | 
|  | noprediction = true | 
|  | } | 
|  | if f.pass.debug > 0 && !noprediction { | 
|  | f.Warnl(b.Pos, "Branch prediction rule stay in loop%s", | 
|  | describePredictionAgrees(b, prediction)) | 
|  | } | 
|  |  | 
|  | } else { | 
|  | // Lacking loop structure, fall back on heuristics. | 
|  | if certain[b1] > certain[b0] { | 
|  | prediction = BranchLikely | 
|  | if f.pass.debug > 0 { | 
|  | describeBranchPrediction(f, b, certain[b0], certain[b1], prediction) | 
|  | } | 
|  | } else if certain[b0] > certain[b1] { | 
|  | prediction = BranchUnlikely | 
|  | if f.pass.debug > 0 { | 
|  | describeBranchPrediction(f, b, certain[b1], certain[b0], prediction) | 
|  | } | 
|  | } else if local[b1] > local[b0] { | 
|  | prediction = BranchLikely | 
|  | if f.pass.debug > 0 { | 
|  | describeBranchPrediction(f, b, local[b0], local[b1], prediction) | 
|  | } | 
|  | } else if local[b0] > local[b1] { | 
|  | prediction = BranchUnlikely | 
|  | if f.pass.debug > 0 { | 
|  | describeBranchPrediction(f, b, local[b1], local[b0], prediction) | 
|  | } | 
|  | } | 
|  | } | 
|  | if b.Likely != prediction { | 
|  | if b.Likely == BranchUnknown { | 
|  | b.Likely = prediction | 
|  | } | 
|  | } | 
|  | } | 
|  | // Look for calls in the block.  If there is one, make this block unlikely. | 
|  | for _, v := range b.Values { | 
|  | if opcodeTable[v.Op].call { | 
|  | local[b.ID] = blCALL | 
|  | certain[b.ID] = max8(blCALL, certain[b.Succs[0].b.ID]) | 
|  | } | 
|  | } | 
|  | } | 
|  | if f.pass.debug > 2 { | 
|  | f.Warnl(b.Pos, "BP: Block %s, local=%s, certain=%s", b, bllikelies[local[b.ID]-blMin], bllikelies[certain[b.ID]-blMin]) | 
|  | } | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  | func (l *loop) String() string { | 
|  | return fmt.Sprintf("hdr:%s", l.header) | 
|  | } | 
|  |  | 
|  | func (l *loop) LongString() string { | 
|  | i := "" | 
|  | o := "" | 
|  | if l.isInner { | 
|  | i = ", INNER" | 
|  | } | 
|  | if l.outer != nil { | 
|  | o = ", o=" + l.outer.header.String() | 
|  | } | 
|  | return fmt.Sprintf("hdr:%s%s%s", l.header, i, o) | 
|  | } | 
|  |  | 
|  | func (l *loop) isWithinOrEq(ll *loop) bool { | 
|  | if ll == nil { // nil means whole program | 
|  | return true | 
|  | } | 
|  | for ; l != nil; l = l.outer { | 
|  | if l == ll { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | // nearestOuterLoop returns the outer loop of loop most nearly | 
|  | // containing block b; the header must dominate b.  loop itself | 
|  | // is assumed to not be that loop. For acceptable performance, | 
|  | // we're relying on loop nests to not be terribly deep. | 
|  | func (l *loop) nearestOuterLoop(sdom SparseTree, b *Block) *loop { | 
|  | var o *loop | 
|  | for o = l.outer; o != nil && !sdom.isAncestorEq(o.header, b); o = o.outer { | 
|  | } | 
|  | return o | 
|  | } | 
|  |  | 
|  | func loopnestfor(f *Func) *loopnest { | 
|  | po := f.postorder() | 
|  | sdom := f.sdom() | 
|  | b2l := make([]*loop, f.NumBlocks()) | 
|  | loops := make([]*loop, 0) | 
|  | visited := make([]bool, f.NumBlocks()) | 
|  | sawIrred := false | 
|  |  | 
|  | if f.pass.debug > 2 { | 
|  | fmt.Printf("loop finding in %s\n", f.Name) | 
|  | } | 
|  |  | 
|  | // Reducible-loop-nest-finding. | 
|  | for _, b := range po { | 
|  | if f.pass != nil && f.pass.debug > 3 { | 
|  | fmt.Printf("loop finding at %s\n", b) | 
|  | } | 
|  |  | 
|  | var innermost *loop // innermost header reachable from this block | 
|  |  | 
|  | // IF any successor s of b is in a loop headed by h | 
|  | // AND h dominates b | 
|  | // THEN b is in the loop headed by h. | 
|  | // | 
|  | // Choose the first/innermost such h. | 
|  | // | 
|  | // IF s itself dominates b, then s is a loop header; | 
|  | // and there may be more than one such s. | 
|  | // Since there's at most 2 successors, the inner/outer ordering | 
|  | // between them can be established with simple comparisons. | 
|  | for _, e := range b.Succs { | 
|  | bb := e.b | 
|  | l := b2l[bb.ID] | 
|  |  | 
|  | if sdom.isAncestorEq(bb, b) { // Found a loop header | 
|  | if f.pass != nil && f.pass.debug > 4 { | 
|  | fmt.Printf("loop finding    succ %s of %s is header\n", bb.String(), b.String()) | 
|  | } | 
|  | if l == nil { | 
|  | l = &loop{header: bb, isInner: true} | 
|  | loops = append(loops, l) | 
|  | b2l[bb.ID] = l | 
|  | } | 
|  | } else if !visited[bb.ID] { // Found an irreducible loop | 
|  | sawIrred = true | 
|  | if f.pass != nil && f.pass.debug > 4 { | 
|  | fmt.Printf("loop finding    succ %s of %s is IRRED, in %s\n", bb.String(), b.String(), f.Name) | 
|  | } | 
|  | } else if l != nil { | 
|  | // TODO handle case where l is irreducible. | 
|  | // Perhaps a loop header is inherited. | 
|  | // is there any loop containing our successor whose | 
|  | // header dominates b? | 
|  | if !sdom.isAncestorEq(l.header, b) { | 
|  | l = l.nearestOuterLoop(sdom, b) | 
|  | } | 
|  | if f.pass != nil && f.pass.debug > 4 { | 
|  | if l == nil { | 
|  | fmt.Printf("loop finding    succ %s of %s has no loop\n", bb.String(), b.String()) | 
|  | } else { | 
|  | fmt.Printf("loop finding    succ %s of %s provides loop with header %s\n", bb.String(), b.String(), l.header.String()) | 
|  | } | 
|  | } | 
|  | } else { // No loop | 
|  | if f.pass != nil && f.pass.debug > 4 { | 
|  | fmt.Printf("loop finding    succ %s of %s has no loop\n", bb.String(), b.String()) | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | if l == nil || innermost == l { | 
|  | continue | 
|  | } | 
|  |  | 
|  | if innermost == nil { | 
|  | innermost = l | 
|  | continue | 
|  | } | 
|  |  | 
|  | if sdom.isAncestor(innermost.header, l.header) { | 
|  | sdom.outerinner(innermost, l) | 
|  | innermost = l | 
|  | } else if sdom.isAncestor(l.header, innermost.header) { | 
|  | sdom.outerinner(l, innermost) | 
|  | } | 
|  | } | 
|  |  | 
|  | if innermost != nil { | 
|  | b2l[b.ID] = innermost | 
|  | innermost.nBlocks++ | 
|  | } | 
|  | visited[b.ID] = true | 
|  | } | 
|  |  | 
|  | ln := &loopnest{f: f, b2l: b2l, po: po, sdom: sdom, loops: loops, hasIrreducible: sawIrred} | 
|  |  | 
|  | // Calculate containsUnavoidableCall for regalloc | 
|  | dominatedByCall := make([]bool, f.NumBlocks()) | 
|  | for _, b := range po { | 
|  | if checkContainsCall(b) { | 
|  | dominatedByCall[b.ID] = true | 
|  | } | 
|  | } | 
|  | // Run dfs to find path through the loop that avoids all calls. | 
|  | // Such path either escapes loop or return back to header. | 
|  | // It isn't enough to have exit not dominated by any call, for example: | 
|  | // ... some loop | 
|  | // call1   call2 | 
|  | //   \      / | 
|  | //     exit | 
|  | // ... | 
|  | // exit is not dominated by any call, but we don't have call-free path to it. | 
|  | for _, l := range loops { | 
|  | // Header contains call. | 
|  | if dominatedByCall[l.header.ID] { | 
|  | l.containsUnavoidableCall = true | 
|  | continue | 
|  | } | 
|  | callfreepath := false | 
|  | tovisit := make([]*Block, 0, len(l.header.Succs)) | 
|  | // Push all non-loop non-exit successors of header onto toVisit. | 
|  | for _, s := range l.header.Succs { | 
|  | nb := s.Block() | 
|  | // This corresponds to loop with zero iterations. | 
|  | if !l.iterationEnd(nb, b2l) { | 
|  | tovisit = append(tovisit, nb) | 
|  | } | 
|  | } | 
|  | for len(tovisit) > 0 { | 
|  | cur := tovisit[len(tovisit)-1] | 
|  | tovisit = tovisit[:len(tovisit)-1] | 
|  | if dominatedByCall[cur.ID] { | 
|  | continue | 
|  | } | 
|  | // Record visited in dominatedByCall. | 
|  | dominatedByCall[cur.ID] = true | 
|  | for _, s := range cur.Succs { | 
|  | nb := s.Block() | 
|  | if l.iterationEnd(nb, b2l) { | 
|  | callfreepath = true | 
|  | } | 
|  | if !dominatedByCall[nb.ID] { | 
|  | tovisit = append(tovisit, nb) | 
|  | } | 
|  |  | 
|  | } | 
|  | if callfreepath { | 
|  | break | 
|  | } | 
|  | } | 
|  | if !callfreepath { | 
|  | l.containsUnavoidableCall = true | 
|  | } | 
|  | } | 
|  |  | 
|  | // Curious about the loopiness? "-d=ssa/likelyadjust/stats" | 
|  | if f.pass != nil && f.pass.stats > 0 && len(loops) > 0 { | 
|  | ln.assembleChildren() | 
|  | ln.calculateDepths() | 
|  | ln.findExits() | 
|  |  | 
|  | // Note stats for non-innermost loops are slightly flawed because | 
|  | // they don't account for inner loop exits that span multiple levels. | 
|  |  | 
|  | for _, l := range loops { | 
|  | x := len(l.exits) | 
|  | cf := 0 | 
|  | if !l.containsUnavoidableCall { | 
|  | cf = 1 | 
|  | } | 
|  | inner := 0 | 
|  | if l.isInner { | 
|  | inner++ | 
|  | } | 
|  |  | 
|  | f.LogStat("loopstats:", | 
|  | l.depth, "depth", x, "exits", | 
|  | inner, "is_inner", cf, "always_calls", l.nBlocks, "n_blocks") | 
|  | } | 
|  | } | 
|  |  | 
|  | if f.pass != nil && f.pass.debug > 1 && len(loops) > 0 { | 
|  | fmt.Printf("Loops in %s:\n", f.Name) | 
|  | for _, l := range loops { | 
|  | fmt.Printf("%s, b=", l.LongString()) | 
|  | for _, b := range f.Blocks { | 
|  | if b2l[b.ID] == l { | 
|  | fmt.Printf(" %s", b) | 
|  | } | 
|  | } | 
|  | fmt.Print("\n") | 
|  | } | 
|  | fmt.Printf("Nonloop blocks in %s:", f.Name) | 
|  | for _, b := range f.Blocks { | 
|  | if b2l[b.ID] == nil { | 
|  | fmt.Printf(" %s", b) | 
|  | } | 
|  | } | 
|  | fmt.Print("\n") | 
|  | } | 
|  | return ln | 
|  | } | 
|  |  | 
|  | // assembleChildren initializes the children field of each | 
|  | // loop in the nest.  Loop A is a child of loop B if A is | 
|  | // directly nested within B (based on the reducible-loops | 
|  | // detection above) | 
|  | func (ln *loopnest) assembleChildren() { | 
|  | if ln.initializedChildren { | 
|  | return | 
|  | } | 
|  | for _, l := range ln.loops { | 
|  | if l.outer != nil { | 
|  | l.outer.children = append(l.outer.children, l) | 
|  | } | 
|  | } | 
|  | ln.initializedChildren = true | 
|  | } | 
|  |  | 
|  | // calculateDepths uses the children field of loops | 
|  | // to determine the nesting depth (outer=1) of each | 
|  | // loop.  This is helpful for finding exit edges. | 
|  | func (ln *loopnest) calculateDepths() { | 
|  | if ln.initializedDepth { | 
|  | return | 
|  | } | 
|  | ln.assembleChildren() | 
|  | for _, l := range ln.loops { | 
|  | if l.outer == nil { | 
|  | l.setDepth(1) | 
|  | } | 
|  | } | 
|  | ln.initializedDepth = true | 
|  | } | 
|  |  | 
|  | // findExits uses loop depth information to find the | 
|  | // exits from a loop. | 
|  | func (ln *loopnest) findExits() { | 
|  | if ln.initializedExits { | 
|  | return | 
|  | } | 
|  | ln.calculateDepths() | 
|  | b2l := ln.b2l | 
|  | for _, b := range ln.po { | 
|  | l := b2l[b.ID] | 
|  | if l != nil && len(b.Succs) == 2 { | 
|  | sl := b2l[b.Succs[0].b.ID] | 
|  | if recordIfExit(l, sl, b.Succs[0].b) { | 
|  | continue | 
|  | } | 
|  | sl = b2l[b.Succs[1].b.ID] | 
|  | if recordIfExit(l, sl, b.Succs[1].b) { | 
|  | continue | 
|  | } | 
|  | } | 
|  | } | 
|  | ln.initializedExits = true | 
|  | } | 
|  |  | 
|  | // depth returns the loop nesting level of block b. | 
|  | func (ln *loopnest) depth(b ID) int16 { | 
|  | if l := ln.b2l[b]; l != nil { | 
|  | return l.depth | 
|  | } | 
|  | return 0 | 
|  | } | 
|  |  | 
|  | // recordIfExit checks sl (the loop containing b) to see if it | 
|  | // is outside of loop l, and if so, records b as an exit block | 
|  | // from l and returns true. | 
|  | func recordIfExit(l, sl *loop, b *Block) bool { | 
|  | if sl != l { | 
|  | if sl == nil || sl.depth <= l.depth { | 
|  | l.exits = append(l.exits, b) | 
|  | return true | 
|  | } | 
|  | // sl is not nil, and is deeper than l | 
|  | // it's possible for this to be a goto into an irreducible loop made from gotos. | 
|  | for sl.depth > l.depth { | 
|  | sl = sl.outer | 
|  | } | 
|  | if sl != l { | 
|  | l.exits = append(l.exits, b) | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | func (l *loop) setDepth(d int16) { | 
|  | l.depth = d | 
|  | for _, c := range l.children { | 
|  | c.setDepth(d + 1) | 
|  | } | 
|  | } | 
|  |  | 
|  | // iterationEnd checks if block b ends iteration of loop l. | 
|  | // Ending iteration means either escaping to outer loop/code or | 
|  | // going back to header | 
|  | func (l *loop) iterationEnd(b *Block, b2l []*loop) bool { | 
|  | return b == l.header || b2l[b.ID] == nil || (b2l[b.ID] != l && b2l[b.ID].depth <= l.depth) | 
|  | } |