github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/ssa/numberlines.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssa
     6  
     7  import (
     8  	"fmt"
     9  	"sort"
    10  
    11  	"github.com/go-asm/go/cmd/src"
    12  )
    13  
    14  func isPoorStatementOp(op Op) bool {
    15  	switch op {
    16  	// Note that Nilcheck often vanishes, but when it doesn't, you'd love to start the statement there
    17  	// so that a debugger-user sees the stop before the panic, and can examine the value.
    18  	case OpAddr, OpLocalAddr, OpOffPtr, OpStructSelect, OpPhi, OpITab, OpIData,
    19  		OpIMake, OpStringMake, OpSliceMake, OpStructMake0, OpStructMake1, OpStructMake2, OpStructMake3, OpStructMake4,
    20  		OpConstBool, OpConst8, OpConst16, OpConst32, OpConst64, OpConst32F, OpConst64F, OpSB, OpSP,
    21  		OpArgIntReg, OpArgFloatReg:
    22  		return true
    23  	}
    24  	return false
    25  }
    26  
    27  // nextGoodStatementIndex returns an index at i or later that is believed
    28  // to be a good place to start the statement for b.  This decision is
    29  // based on v's Op, the possibility of a better later operation, and
    30  // whether the values following i are the same line as v.
    31  // If a better statement index isn't found, then i is returned.
    32  func nextGoodStatementIndex(v *Value, i int, b *Block) int {
    33  	// If the value is the last one in the block, too bad, it will have to do
    34  	// (this assumes that the value ordering vaguely corresponds to the source
    35  	// program execution order, which tends to be true directly after ssa is
    36  	// first built).
    37  	if i >= len(b.Values)-1 {
    38  		return i
    39  	}
    40  	// Skip the likely-ephemeral/fragile opcodes expected to vanish in a rewrite.
    41  	if !isPoorStatementOp(v.Op) {
    42  		return i
    43  	}
    44  	// Look ahead to see what the line number is on the next thing that could be a boundary.
    45  	for j := i + 1; j < len(b.Values); j++ {
    46  		u := b.Values[j]
    47  		if u.Pos.IsStmt() == src.PosNotStmt { // ignore non-statements
    48  			continue
    49  		}
    50  		if u.Pos.SameFileAndLine(v.Pos) {
    51  			if isPoorStatementOp(u.Op) {
    52  				continue // Keep looking, this is also not a good statement op
    53  			}
    54  			return j
    55  		}
    56  		return i
    57  	}
    58  	return i
    59  }
    60  
    61  // notStmtBoundary reports whether a value with opcode op can never be a statement
    62  // boundary. Such values don't correspond to a user's understanding of a
    63  // statement boundary.
    64  func notStmtBoundary(op Op) bool {
    65  	switch op {
    66  	case OpCopy, OpPhi, OpVarDef, OpVarLive, OpUnknown, OpFwdRef, OpArg, OpArgIntReg, OpArgFloatReg:
    67  		return true
    68  	}
    69  	return false
    70  }
    71  
    72  func (b *Block) FirstPossibleStmtValue() *Value {
    73  	for _, v := range b.Values {
    74  		if notStmtBoundary(v.Op) {
    75  			continue
    76  		}
    77  		return v
    78  	}
    79  	return nil
    80  }
    81  
    82  func flc(p src.XPos) string {
    83  	if p == src.NoXPos {
    84  		return "none"
    85  	}
    86  	return fmt.Sprintf("(%d):%d:%d", p.FileIndex(), p.Line(), p.Col())
    87  }
    88  
    89  type fileAndPair struct {
    90  	f  int32
    91  	lp lineRange
    92  }
    93  
    94  type fileAndPairs []fileAndPair
    95  
    96  func (fap fileAndPairs) Len() int {
    97  	return len(fap)
    98  }
    99  func (fap fileAndPairs) Less(i, j int) bool {
   100  	return fap[i].f < fap[j].f
   101  }
   102  func (fap fileAndPairs) Swap(i, j int) {
   103  	fap[i], fap[j] = fap[j], fap[i]
   104  }
   105  
   106  // -d=ssa/number_lines/stats=1 (that bit) for line and file distribution statistics
   107  // -d=ssa/number_lines/debug for information about why particular values are marked as statements.
   108  func numberLines(f *Func) {
   109  	po := f.Postorder()
   110  	endlines := make(map[ID]src.XPos)
   111  	ranges := make(map[int]lineRange)
   112  	note := func(p src.XPos) {
   113  		line := uint32(p.Line())
   114  		i := int(p.FileIndex())
   115  		lp, found := ranges[i]
   116  		change := false
   117  		if line < lp.first || !found {
   118  			lp.first = line
   119  			change = true
   120  		}
   121  		if line > lp.last {
   122  			lp.last = line
   123  			change = true
   124  		}
   125  		if change {
   126  			ranges[i] = lp
   127  		}
   128  	}
   129  
   130  	// Visit in reverse post order so that all non-loop predecessors come first.
   131  	for j := len(po) - 1; j >= 0; j-- {
   132  		b := po[j]
   133  		// Find the first interesting position and check to see if it differs from any predecessor
   134  		firstPos := src.NoXPos
   135  		firstPosIndex := -1
   136  		if b.Pos.IsStmt() != src.PosNotStmt {
   137  			note(b.Pos)
   138  		}
   139  		for i := 0; i < len(b.Values); i++ {
   140  			v := b.Values[i]
   141  			if v.Pos.IsStmt() != src.PosNotStmt {
   142  				note(v.Pos)
   143  				// skip ahead to better instruction for this line if possible
   144  				i = nextGoodStatementIndex(v, i, b)
   145  				v = b.Values[i]
   146  				firstPosIndex = i
   147  				firstPos = v.Pos
   148  				v.Pos = firstPos.WithDefaultStmt() // default to default
   149  				break
   150  			}
   151  		}
   152  
   153  		if firstPosIndex == -1 { // Effectively empty block, check block's own Pos, consider preds.
   154  			line := src.NoXPos
   155  			for _, p := range b.Preds {
   156  				pbi := p.Block().ID
   157  				if !endlines[pbi].SameFileAndLine(line) {
   158  					if line == src.NoXPos {
   159  						line = endlines[pbi]
   160  						continue
   161  					} else {
   162  						line = src.NoXPos
   163  						break
   164  					}
   165  
   166  				}
   167  			}
   168  			// If the block has no statement itself and is effectively empty, tag it w/ predecessor(s) but not as a statement
   169  			if b.Pos.IsStmt() == src.PosNotStmt {
   170  				b.Pos = line
   171  				endlines[b.ID] = line
   172  				continue
   173  			}
   174  			// If the block differs from its predecessors, mark it as a statement
   175  			if line == src.NoXPos || !line.SameFileAndLine(b.Pos) {
   176  				b.Pos = b.Pos.WithIsStmt()
   177  				if f.pass.debug > 0 {
   178  					fmt.Printf("Mark stmt effectively-empty-block %s %s %s\n", f.Name, b, flc(b.Pos))
   179  				}
   180  			}
   181  			endlines[b.ID] = b.Pos
   182  			continue
   183  		}
   184  		// check predecessors for any difference; if firstPos differs, then it is a boundary.
   185  		if len(b.Preds) == 0 { // Don't forget the entry block
   186  			b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
   187  			if f.pass.debug > 0 {
   188  				fmt.Printf("Mark stmt entry-block %s %s %s %s\n", f.Name, b, b.Values[firstPosIndex], flc(firstPos))
   189  			}
   190  		} else { // differing pred
   191  			for _, p := range b.Preds {
   192  				pbi := p.Block().ID
   193  				if !endlines[pbi].SameFileAndLine(firstPos) {
   194  					b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
   195  					if f.pass.debug > 0 {
   196  						fmt.Printf("Mark stmt differing-pred %s %s %s %s, different=%s ending %s\n",
   197  							f.Name, b, b.Values[firstPosIndex], flc(firstPos), p.Block(), flc(endlines[pbi]))
   198  					}
   199  					break
   200  				}
   201  			}
   202  		}
   203  		// iterate forward setting each new (interesting) position as a statement boundary.
   204  		for i := firstPosIndex + 1; i < len(b.Values); i++ {
   205  			v := b.Values[i]
   206  			if v.Pos.IsStmt() == src.PosNotStmt {
   207  				continue
   208  			}
   209  			note(v.Pos)
   210  			// skip ahead if possible
   211  			i = nextGoodStatementIndex(v, i, b)
   212  			v = b.Values[i]
   213  			if !v.Pos.SameFileAndLine(firstPos) {
   214  				if f.pass.debug > 0 {
   215  					fmt.Printf("Mark stmt new line %s %s %s %s prev pos = %s\n", f.Name, b, v, flc(v.Pos), flc(firstPos))
   216  				}
   217  				firstPos = v.Pos
   218  				v.Pos = v.Pos.WithIsStmt()
   219  			} else {
   220  				v.Pos = v.Pos.WithDefaultStmt()
   221  			}
   222  		}
   223  		if b.Pos.IsStmt() != src.PosNotStmt && !b.Pos.SameFileAndLine(firstPos) {
   224  			if f.pass.debug > 0 {
   225  				fmt.Printf("Mark stmt end of block differs %s %s %s prev pos = %s\n", f.Name, b, flc(b.Pos), flc(firstPos))
   226  			}
   227  			b.Pos = b.Pos.WithIsStmt()
   228  			firstPos = b.Pos
   229  		}
   230  		endlines[b.ID] = firstPos
   231  	}
   232  	if f.pass.stats&1 != 0 {
   233  		// Report summary statistics on the shape of the sparse map about to be constructed
   234  		// TODO use this information to make sparse maps faster.
   235  		var entries fileAndPairs
   236  		for k, v := range ranges {
   237  			entries = append(entries, fileAndPair{int32(k), v})
   238  		}
   239  		sort.Sort(entries)
   240  		total := uint64(0)            // sum over files of maxline(file) - minline(file)
   241  		maxfile := int32(0)           // max(file indices)
   242  		minline := uint32(0xffffffff) // min over files of minline(file)
   243  		maxline := uint32(0)          // max over files of maxline(file)
   244  		for _, v := range entries {
   245  			if f.pass.stats > 1 {
   246  				f.LogStat("file", v.f, "low", v.lp.first, "high", v.lp.last)
   247  			}
   248  			total += uint64(v.lp.last - v.lp.first)
   249  			if maxfile < v.f {
   250  				maxfile = v.f
   251  			}
   252  			if minline > v.lp.first {
   253  				minline = v.lp.first
   254  			}
   255  			if maxline < v.lp.last {
   256  				maxline = v.lp.last
   257  			}
   258  		}
   259  		f.LogStat("SUM_LINE_RANGE", total, "MAXMIN_LINE_RANGE", maxline-minline, "MAXFILE", maxfile, "NFILES", len(entries))
   260  	}
   261  	// cachedLineStarts is an empty sparse map for values that are included within ranges.
   262  	f.cachedLineStarts = newXposmap(ranges)
   263  }