p.As = as
p.Pos = pp.pos
+ if pp.pos.IsStmt() == src.PosIsStmt {
+ // Clear IsStmt for later Progs at this pos provided that as generates executable code.
+ switch as {
+ // TODO: this is an artifact of how funcpctab combines information for instructions at a single PC.
+ // Should try to fix it there. There is a similar workaround in *SSAGenState.Prog in gc/ssa.go.
+ case obj.APCDATA, obj.AFUNCDATA:
+ // is_stmt does not work for these; it DOES for ANOP
+ return p
+ }
+ pp.pos = pp.pos.WithNotStmt()
+ }
return p
}
}
b := s.endBlock()
- b.Pos = s.lastPos // Do this even if b is an empty block.
+ b.Pos = s.lastPos.WithIsStmt() // Do this even if b is an empty block.
b.AddEdgeTo(lab.target)
case OAS:
case ORETURN:
s.stmtList(n.List)
b := s.exit()
- b.Pos = s.lastPos
+ b.Pos = s.lastPos.WithIsStmt()
case ORETJMP:
s.stmtList(n.List)
}
b := s.endBlock()
- b.Pos = s.lastPos // Do this even if b is an empty block.
+ b.Pos = s.lastPos.WithIsStmt() // Do this even if b is an empty block.
b.AddEdgeTo(to)
case OFOR, OFORUNTIL:
// Map from GC safe points to stack map index, generated by
// liveness analysis.
stackMapIndex map[*ssa.Value]int
+
+ // lineRunStart records the beginning of the current run of instructions
+ // within a single block sharing the same line number
+ // Used to move statement marks to the beginning of such runs.
+ lineRunStart *obj.Prog
}
// Prog appends a new Prog.
func (s *SSAGenState) Prog(as obj.As) *obj.Prog {
- return s.pp.Prog(as)
+ p := s.pp.Prog(as)
+ switch as {
+ case obj.APCDATA, obj.AFUNCDATA:
+ // is_stmt does not work for these; it DOES for ANOP
+ return p
+ }
+ // Float a statement start to the beginning of any same-line run.
+ // lineRunStart is reset at block boundaries, which appears to work well.
+ if s.lineRunStart == nil || s.lineRunStart.Pos.Line() != p.Pos.Line() {
+ s.lineRunStart = p
+ } else if p.Pos.IsStmt() == src.PosIsStmt {
+ s.lineRunStart.Pos = s.lineRunStart.Pos.WithIsStmt()
+ p.Pos = p.Pos.WithNotStmt()
+ }
+ return p
}
// Pc returns the current Prog.
return p
}
-// DebugFriendlySetPos sets the position subject to heuristics
+// DebugFriendlySetPos adjusts Pos.IsStmt subject to heuristics
// that reduce "jumpy" line number churn when debugging.
// Spill/fill/copy instructions from the register allocator,
// phi functions, and instructions with a no-pos position
// are examples of instructions that can cause churn.
func (s *SSAGenState) DebugFriendlySetPosFrom(v *ssa.Value) {
- // The two choices here are either to leave lineno unchanged,
- // or to explicitly set it to src.NoXPos. Leaving it unchanged
- // (reusing the preceding line number) produces slightly better-
- // looking assembly language output from the compiler, and is
- // expected by some already-existing tests.
- // The debug information appears to be the same in either case
switch v.Op {
case ssa.OpPhi, ssa.OpCopy, ssa.OpLoadReg, ssa.OpStoreReg:
- // leave the position unchanged from beginning of block
- // or previous line number.
+ // These are not statements
+ s.SetPos(v.Pos.WithNotStmt())
default:
- if v.Pos != src.NoXPos {
- s.SetPos(v.Pos)
+ p := v.Pos
+ if p != src.NoXPos {
+ // If the position is defined, update the position.
+ // Also convert default IsStmt to NotStmt; only
+ // explicit statement boundaries should appear
+ // in the generated code.
+ if p.IsStmt() != src.PosIsStmt {
+ p = p.WithNotStmt()
+ }
+ s.SetPos(p)
}
}
}
// debuggers may attribute it to previous function in program.
firstPos := src.NoXPos
for _, v := range f.Entry.Values {
- if v.Op != ssa.OpArg && v.Op != ssa.OpVarDef && v.Pos.IsStmt() != src.PosNotStmt { // TODO will be == src.PosIsStmt in pending CL, more accurate
- firstPos = v.Pos.WithIsStmt()
+ if v.Pos.IsStmt() == src.PosIsStmt {
+ firstPos = v.Pos
+ v.Pos = firstPos.WithDefaultStmt()
break
}
}
// Emit basic blocks
for i, b := range f.Blocks {
s.bstart[b.ID] = s.pp.next
-
+ s.lineRunStart = nil
// Emit values in block
thearch.SSAMarkMoves(&s, b)
for _, v := range b.Values {
}
}
- // Resolve branches
+ // Resolove branchers, and relax DefaultStmt into NotStmt
for _, br := range s.Branches {
br.P.To.Val = s.bstart[br.B.ID]
+ if br.P.Pos.IsStmt() != src.PosIsStmt {
+ br.P.Pos = br.P.Pos.WithNotStmt()
+ }
}
if logProgs {
--- /dev/null
+// Copyright 2018 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"
+ "math"
+)
+
+// A biasedSparseMap is a sparseMap for integers between J and K inclusive,
+// where J might be somewhat larger than zero (and K-J is probably much smaller than J).
+// (The motivating use case is the line numbers of statements for a single function.)
+// Not all features of a SparseMap are exported, and it is also easy to treat a
+// biasedSparseMap like a SparseSet.
+type biasedSparseMap struct {
+ s *sparseMap
+ first int
+}
+
+// newBiasedSparseMap returns a new biasedSparseMap for values between first and last, inclusive.
+func newBiasedSparseMap(first, last int) *biasedSparseMap {
+ if first > last {
+ return &biasedSparseMap{first: math.MaxInt32, s: nil}
+ }
+ return &biasedSparseMap{first: first, s: newSparseMap(1 + last - first)}
+}
+
+// cap returns one more than the largest key valid for s
+func (s *biasedSparseMap) cap() int {
+ if s.s == nil {
+ return 0
+ }
+ return s.s.cap() + int(s.first)
+}
+
+// size returns the number of entries stored in s
+func (s *biasedSparseMap) size() int {
+ if s.s == nil {
+ return 0
+ }
+ return s.s.size()
+}
+
+// contains returns whether x is a key in s
+func (s *biasedSparseMap) contains(x uint) bool {
+ if s.s == nil {
+ return false
+ }
+ if int(x) < s.first {
+ return false
+ }
+ if int(x) >= s.cap() {
+ return false
+ }
+ return s.s.contains(ID(int(x) - s.first))
+}
+
+// get returns the value s maps for key x, or -1 if
+// x is not mapped or is out of range for s.
+func (s *biasedSparseMap) get(x uint) int32 {
+ if s.s == nil {
+ return -1
+ }
+ if int(x) < s.first {
+ return -1
+ }
+ if int(x) >= s.cap() {
+ return -1
+ }
+ return s.s.get(ID(int(x) - s.first))
+}
+
+// getEntry returns the i'th key and value stored in s,
+// where 0 <= i < s.size()
+func (s *biasedSparseMap) getEntry(i int) (x uint, v int32) {
+ e := s.s.contents()[i]
+ x = uint(int(e.key) + s.first)
+ v = e.val
+ return
+}
+
+// add inserts x->0 into s, provided that x is in the range of keys stored in s.
+func (s *biasedSparseMap) add(x uint) {
+ if int(x) < s.first || int(x) >= s.cap() {
+ return
+ }
+ s.s.set(ID(int(x)-s.first), 0, src.NoXPos)
+}
+
+// add inserts x->v into s, provided that x is in the range of keys stored in s.
+func (s *biasedSparseMap) set(x uint, v int32) {
+ if int(x) < s.first || int(x) >= s.cap() {
+ return
+ }
+ s.s.set(ID(int(x)-s.first), v, src.NoXPos)
+}
+
+// remove removes key x from s.
+func (s *biasedSparseMap) remove(x uint) {
+ if int(x) < s.first || int(x) >= s.cap() {
+ return
+ }
+ s.s.remove(ID(int(x) - s.first))
+}
+
+func (s *biasedSparseMap) clear() {
+ if s.s != nil {
+ s.s.clear()
+ }
+}
// list of passes for the compiler
var passes = [...]pass{
// TODO: combine phielim and copyelim into a single pass?
+ {name: "number lines", fn: numberLines, required: true},
{name: "early phielim", fn: phielim},
{name: "early copyelim", fn: copyelim},
{name: "early deadcode", fn: deadcode}, // remove generated dead code to avoid doing pointless work during opt
import (
"cmd/compile/internal/types"
+ "cmd/internal/src"
"fmt"
"sort"
)
for _, v := range b.Values {
for i, w := range v.Args {
if x := rewrite[w.ID]; x != nil {
+ if w.Pos.IsStmt() == src.PosIsStmt {
+ // about to lose a statement marker, w
+ // w is an input to v; if they're in the same block
+ // and the same line, v is a good-enough new statement boundary.
+ if w.Block == v.Block && w.Pos.Line() == v.Pos.Line() {
+ v.Pos = v.Pos.WithIsStmt()
+ w.Pos = w.Pos.WithNotStmt()
+ } // TODO and if this fails?
+ }
v.SetArg(i, x)
rewrites++
}
package ssa
+import (
+ "cmd/internal/src"
+)
+
// findlive returns the reachable blocks and live values in f.
func findlive(f *Func) (reachable []bool, live []bool) {
reachable = ReachableBlocks(f)
- live = liveValues(f, reachable)
+ live, _ = liveValues(f, reachable)
return
}
return reachable
}
-// liveValues returns the live values in f.
+// liveValues returns the live values in f and a list of values that are eligible
+// to be statements in reversed data flow order.
+// The second result is used to help conserve statement boundaries for debugging.
// reachable is a map from block ID to whether the block is reachable.
-func liveValues(f *Func, reachable []bool) []bool {
- live := make([]bool, f.NumValues())
+func liveValues(f *Func, reachable []bool) (live []bool, liveOrderStmts []*Value) {
+ live = make([]bool, f.NumValues())
// After regalloc, consider all values to be live.
// See the comment at the top of regalloc.go and in deadcode for details.
for i := range live {
live[i] = true
}
- return live
+ return
}
// Find all live values
if v := b.Control; v != nil && !live[v.ID] {
live[v.ID] = true
q = append(q, v)
+ if v.Pos.IsStmt() != src.PosNotStmt {
+ liveOrderStmts = append(liveOrderStmts, v)
+ }
}
for _, v := range b.Values {
if (opcodeTable[v.Op].call || opcodeTable[v.Op].hasSideEffects) && !live[v.ID] {
live[v.ID] = true
q = append(q, v)
+ if v.Pos.IsStmt() != src.PosNotStmt {
+ liveOrderStmts = append(liveOrderStmts, v)
+ }
}
if v.Type.IsVoid() && !live[v.ID] {
// The only Void ops are nil checks. We must keep these.
live[v.ID] = true
q = append(q, v)
+ if v.Pos.IsStmt() != src.PosNotStmt {
+ liveOrderStmts = append(liveOrderStmts, v)
+ }
}
}
}
if !live[x.ID] {
live[x.ID] = true
q = append(q, x) // push
+ if x.Pos.IsStmt() != src.PosNotStmt {
+ liveOrderStmts = append(liveOrderStmts, x)
+ }
}
}
}
- return live
+ return
}
// deadcode removes dead code from f.
copyelim(f)
// Find live values.
- live := liveValues(f, reachable)
+ live, order := liveValues(f, reachable)
// Remove dead & duplicate entries from namedValues map.
s := f.newSparseSet(f.NumValues())
}
f.Names = f.Names[:i]
- // Unlink values.
- for _, b := range f.Blocks {
+ pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block
+ pendingLines.clear()
+
+ // Unlink values and conserve statement boundaries
+ for i, b := range f.Blocks {
if !reachable[b.ID] {
+ // TODO what if control is statement boundary? Too late here.
b.SetControl(nil)
}
for _, v := range b.Values {
if !live[v.ID] {
v.resetArgs()
+ if v.Pos.IsStmt() == src.PosIsStmt && reachable[b.ID] {
+ pendingLines.set(v.Pos.Line(), int32(i)) // TODO could be more than one pos for a line
+ }
}
}
}
+ // Find new homes for lost lines -- require earliest in data flow with same line that is also in same block
+ for i := len(order) - 1; i >= 0; i-- {
+ w := order[i]
+ if j := pendingLines.get(w.Pos.Line()); j > -1 && f.Blocks[j] == w.Block {
+ w.Pos = w.Pos.WithIsStmt()
+ pendingLines.remove(w.Pos.Line())
+ }
+ }
+
+ // Any boundary that failed to match a live value can move to a block end
+ for i := 0; i < pendingLines.size(); i++ {
+ l, bi := pendingLines.getEntry(i)
+ b := f.Blocks[bi]
+ if b.Pos.Line() == l {
+ b.Pos = b.Pos.WithIsStmt()
+ }
+ }
+
// Remove dead values from blocks' value list. Return dead
// values to the allocator.
for _, b := range f.Blocks {
freeValues *Value // free Values linked by argstorage[0]. All other fields except ID are 0/nil.
freeBlocks *Block // free Blocks linked by succstorage[0].b. All other fields except ID are 0/nil.
- cachedPostorder []*Block // cached postorder traversal
- cachedIdom []*Block // cached immediate dominators
- cachedSdom SparseTree // cached dominator tree
- cachedLoopnest *loopnest // cached loop nest information
-
- auxmap auxmap // map from aux values to opaque ids used by CSE
+ cachedPostorder []*Block // cached postorder traversal
+ cachedIdom []*Block // cached immediate dominators
+ cachedSdom SparseTree // cached dominator tree
+ cachedLoopnest *loopnest // cached loop nest information
+ cachedLineStarts *biasedSparseMap // cached map/set of line numbers to integers
+ auxmap auxmap // map from aux values to opaque ids used by CSE
constants map[int64][]*Value // constants cache, keyed by constant value; users must check value's Op and Type
}
v.Op = op
v.Type = t
v.Block = b
+ if notStmtBoundary(op) {
+ pos = pos.WithNotStmt()
+ }
v.Pos = pos
b.Values = append(b.Values, v)
return v
v.Op = op
v.Type = t
v.Block = nil // caller must fix this.
+ if notStmtBoundary(op) {
+ pos = pos.WithNotStmt()
+ }
v.Pos = pos
return v
}
// But not both.
f.Cache = new(Cache)
f.pass = &emptyPass
+ f.cachedLineStarts = newBiasedSparseMap(0, 100)
blocks := make(map[string]*Block)
values := make(map[string]*Value)
package ssa
+import (
+ "cmd/internal/src"
+)
+
// fuse simplifies control flow by joining basic blocks.
func fuse(f *Func) {
for changed := true; changed; {
return false
}
+ // If a block happened to end in a statement marker,
+ // try to preserve it.
+ if b.Pos.IsStmt() == src.PosIsStmt {
+ l := b.Pos.Line()
+ for _, v := range c.Values {
+ if v.Pos.IsStmt() == src.PosNotStmt {
+ continue
+ }
+ if l == v.Pos.Line() {
+ v.Pos = v.Pos.WithIsStmt()
+ l = 0
+ break
+ }
+ }
+ if l != 0 && c.Pos.Line() == l {
+ c.Pos = c.Pos.WithIsStmt()
+ }
+ }
+
// move all of b's values to c.
for _, v := range b.Values {
v.Block = c
// Use whichever value slice is larger, in the hopes of avoiding growth.
// However, take care to avoid c.Values pointing to b.valstorage.
// See golang.org/issue/18602.
+ // It's important to keep the elements in the same order; maintenance of
+ // debugging information depends on the order of *Values in Blocks.
+ // This can also cause changes in the order (which may affect other
+ // optimizations and possibly compiler output) for 32-vs-64 bit compilation
+ // platforms (word size affects allocation bucket size affects slice size).
if cap(c.Values) >= cap(b.Values) || len(b.Values) <= len(b.valstorage) {
- c.Values = append(c.Values, b.Values...)
+ bl := len(b.Values)
+ cl := len(c.Values)
+ if cap(c.Values) < bl+cl {
+ // reallocate
+ t := make([]*Value, 0, bl+cl)
+ t = append(t, b.Values...)
+ c.Values = append(t, c.Values...)
+ } else {
+ // in place.
+ c.Values = c.Values[0 : bl+cl]
+ copy(c.Values[bl:], c.Values)
+ copy(c.Values, b.Values)
+ }
} else {
c.Values = append(b.Values, c.Values...)
}
package ssa
+import (
+ "cmd/internal/src"
+)
+
// nilcheckelim eliminates unnecessary nil checks.
// runs on machine-independent code.
func nilcheckelim(f *Func) {
// Next, order values in the current block w.r.t. stores.
b.Values = storeOrder(b.Values, sset, storeNumber)
+ pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block
+ pendingLines.clear()
+
// Next, process values in the block.
i := 0
for _, v := range b.Values {
case OpIsNonNil:
ptr := v.Args[0]
if nonNilValues[ptr.ID] {
+ if v.Pos.IsStmt() == src.PosIsStmt { // Boolean true is a terrible statement boundary.
+ pendingLines.add(v.Pos.Line())
+ v.Pos = v.Pos.WithNotStmt()
+ }
// This is a redundant explicit nil check.
v.reset(OpConstBool)
v.AuxInt = 1 // true
if f.fe.Debug_checknil() && v.Pos.Line() > 1 {
f.Warnl(v.Pos, "removed nil check")
}
+ if v.Pos.IsStmt() == src.PosIsStmt { // About to lose a statement boundary
+ pendingLines.add(v.Pos.Line())
+ }
v.reset(OpUnknown)
f.freeValue(v)
i--
// undo that information when this dominator subtree is done.
nonNilValues[ptr.ID] = true
work = append(work, bp{op: ClearPtr, ptr: ptr})
+ fallthrough // a non-eliminated nil check might be a good place for a statement boundary.
+ default:
+ if pendingLines.contains(v.Pos.Line()) && v.Pos.IsStmt() != src.PosNotStmt {
+ v.Pos = v.Pos.WithIsStmt()
+ pendingLines.remove(v.Pos.Line())
+ }
}
}
+ if pendingLines.contains(b.Pos.Line()) {
+ b.Pos = b.Pos.WithIsStmt()
+ pendingLines.remove(b.Pos.Line())
+ }
for j := i; j < len(b.Values); j++ {
b.Values[j] = nil
}
func nilcheckelim2(f *Func) {
unnecessary := f.newSparseSet(f.NumValues())
defer f.retSparseSet(unnecessary)
+
+ pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block
+
for _, b := range f.Blocks {
// Walk the block backwards. Find instructions that will fault if their
// input pointer is nil. Remove nil checks on those pointers, as the
// faulting instruction effectively does the nil check for free.
unnecessary.clear()
+ pendingLines.clear()
// Optimization: keep track of removed nilcheck with smallest index
firstToRemove := len(b.Values)
for i := len(b.Values) - 1; i >= 0; i-- {
if f.fe.Debug_checknil() && v.Pos.Line() > 1 {
f.Warnl(v.Pos, "removed nil check")
}
+ if v.Pos.IsStmt() == src.PosIsStmt {
+ pendingLines.add(v.Pos.Line())
+ }
v.reset(OpUnknown)
firstToRemove = i
continue
for j := i; j < len(b.Values); j++ {
v := b.Values[j]
if v.Op != OpUnknown {
+ if v.Pos.IsStmt() != src.PosNotStmt && pendingLines.contains(v.Pos.Line()) {
+ v.Pos = v.Pos.WithIsStmt()
+ pendingLines.remove(v.Pos.Line())
+ }
b.Values[i] = v
i++
}
}
+
+ if pendingLines.contains(b.Pos.Line()) {
+ b.Pos = b.Pos.WithIsStmt()
+ }
+
for j := i; j < len(b.Values); j++ {
b.Values[j] = nil
}
--- /dev/null
+// Copyright 2018 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"
+ "math"
+)
+
+func isPoorStatementOp(op Op) bool {
+ switch op {
+ // Note that Nilcheck often vanishes, but when it doesn't, you'd love to start the statement there
+ // so that a debugger-user sees the stop before the panic, and can examine the value.
+ case OpAddr, OpOffPtr, OpStructSelect, OpConstBool, OpConst8, OpConst16, OpConst32, OpConst64, OpConst32F, OpConst64F:
+ return true
+ }
+ return false
+}
+
+// nextGoodStatementIndex returns an index at i or later that is believed
+// to be a good place to start the statement for b. This decision is
+// based on v's Op, the possibility of a better later operation, and
+// whether the values following i are the same line as v.
+// If a better statement index isn't found, then i is returned.
+func nextGoodStatementIndex(v *Value, i int, b *Block) int {
+ // If the value is the last one in the block, too bad, it will have to do
+ // (this assumes that the value ordering vaguely corresponds to the source
+ // program execution order, which tends to be true directly after ssa is
+ // first built.
+ if i >= len(b.Values)-1 {
+ return i
+ }
+ // Only consider the likely-ephemeral/fragile opcodes expected to vanish in a rewrite.
+ if !isPoorStatementOp(v.Op) {
+ return i
+ }
+ // Look ahead to see what the line number is on the next thing that could be a boundary.
+ for j := i + 1; j < len(b.Values); j++ {
+ if b.Values[j].Pos.IsStmt() == src.PosNotStmt { // ignore non-statements
+ continue
+ }
+ if b.Values[j].Pos.Line() == v.Pos.Line() {
+ return j
+ }
+ return i
+ }
+ return i
+}
+
+// notStmtBoundary indicates which value opcodes can never be a statement
+// boundary because they don't correspond to a user's understanding of a
+// statement boundary. Called from *Value.reset(), and *Func.newValue(),
+// located here to keep all the statement boundary heuristics in one place.
+// Note: *Value.reset() filters out OpCopy because of how that is used in
+// rewrite.
+func notStmtBoundary(op Op) bool {
+ switch op {
+ case OpCopy, OpPhi, OpVarKill, OpVarDef, OpUnknown, OpFwdRef, OpArg:
+ return true
+ }
+ return false
+}
+
+func numberLines(f *Func) {
+ po := f.Postorder()
+ endlines := make(map[ID]src.XPos)
+ last := uint(0) // uint follows type of XPos.Line()
+ first := uint(math.MaxInt32) // unsigned, but large valid int when cast
+ note := func(line uint) {
+ if line < first {
+ first = line
+ }
+ if line > last {
+ last = line
+ }
+ }
+
+ // Visit in reverse post order so that all non-loop predecessors come first.
+ for j := len(po) - 1; j >= 0; j-- {
+ b := po[j]
+ // Find the first interesting position and check to see if it differs from any predecessor
+ firstPos := src.NoXPos
+ firstPosIndex := -1
+ if b.Pos.IsStmt() != src.PosNotStmt {
+ note(b.Pos.Line())
+ }
+ for i := 0; i < len(b.Values); i++ {
+ v := b.Values[i]
+ if v.Pos.IsStmt() != src.PosNotStmt {
+ note(v.Pos.Line())
+ // skip ahead to better instruction for this line if possible
+ i = nextGoodStatementIndex(v, i, b)
+ v = b.Values[i]
+ firstPosIndex = i
+ firstPos = v.Pos
+ v.Pos = firstPos.WithDefaultStmt() // default to default
+ break
+ }
+ }
+
+ if firstPosIndex == -1 { // Effectively empty block, check block's own Pos, consider preds.
+ if b.Pos.IsStmt() != src.PosNotStmt {
+ b.Pos = b.Pos.WithIsStmt()
+ endlines[b.ID] = b.Pos
+ continue
+ }
+ line := src.NoXPos
+ for _, p := range b.Preds {
+ pbi := p.Block().ID
+ if endlines[pbi] != line {
+ if line == src.NoXPos {
+ line = endlines[pbi]
+ continue
+ } else {
+ line = src.NoXPos
+ break
+ }
+
+ }
+ }
+ endlines[b.ID] = line
+ continue
+ }
+ // check predecessors for any difference; if firstPos differs, then it is a boundary.
+ if len(b.Preds) == 0 { // Don't forget the entry block
+ b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
+ } else {
+ for _, p := range b.Preds {
+ pbi := p.Block().ID
+ if endlines[pbi] != firstPos {
+ b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
+ break
+ }
+ }
+ }
+ // iterate forward setting each new (interesting) position as a statement boundary.
+ for i := firstPosIndex + 1; i < len(b.Values); i++ {
+ v := b.Values[i]
+ if v.Pos.IsStmt() == src.PosNotStmt {
+ continue
+ }
+ note(v.Pos.Line())
+ // skip ahead if possible
+ i = nextGoodStatementIndex(v, i, b)
+ v = b.Values[i]
+ if v.Pos.Line() != firstPos.Line() || !v.Pos.SameFile(firstPos) {
+ firstPos = v.Pos
+ v.Pos = v.Pos.WithIsStmt()
+ } else {
+ v.Pos = v.Pos.WithDefaultStmt()
+ }
+ }
+ if b.Pos.IsStmt() != src.PosNotStmt && (b.Pos.Line() != firstPos.Line() || !b.Pos.SameFile(firstPos)) {
+ b.Pos = b.Pos.WithIsStmt()
+ firstPos = b.Pos
+ }
+ endlines[b.ID] = firstPos
+ }
+ f.cachedLineStarts = newBiasedSparseMap(int(first), int(last))
+}
e.s.f.Fatalf("can't find source for %s->%s: %s\n", e.p, e.b, v.LongString())
}
if dstReg {
- x = v.copyIntoNoXPos(e.p)
+ x = v.copyInto(e.p)
} else {
// Rematerialize into stack slot. Need a free
// register to accomplish this.
import (
"cmd/compile/internal/types"
"cmd/internal/obj"
+ "cmd/internal/src"
"fmt"
"io"
"math"
func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) {
// repeat rewrites until we find no more rewrites
+ pendingLines := f.cachedLineStarts // Holds statement boundaries that need to be moved to a new value/block
+ pendingLines.clear()
for {
change := false
for _, b := range f.Blocks {
if rb(b) {
change = true
}
- for _, v := range b.Values {
+ for j, v := range b.Values {
change = phielimValue(v) || change
// Eliminate copy inputs.
if a.Op != OpCopy {
continue
}
- v.SetArg(i, copySource(a))
+ aa := copySource(a)
+ v.SetArg(i, aa)
+ // If a, a copy, has a line boundary indicator, attempt to find a new value
+ // to hold it. The first candidate is the value that will replace a (aa),
+ // if it shares the same block and line and is eligible.
+ // The second option is v, which has a as an input. Because aa is earlier in
+ // the data flow, it is the better choice.
+ if a.Pos.IsStmt() == src.PosIsStmt {
+ if aa.Block == a.Block && aa.Pos.Line() == a.Pos.Line() && aa.Pos.IsStmt() != src.PosNotStmt {
+ aa.Pos = aa.Pos.WithIsStmt()
+ } else if v.Block == a.Block && v.Pos.Line() == a.Pos.Line() && v.Pos.IsStmt() != src.PosNotStmt {
+ v.Pos = v.Pos.WithIsStmt()
+ } else {
+ // Record the lost line and look for a new home after all rewrites are complete.
+ // TODO: it's possible (in FOR loops, in particular) for statement boundaries for the same
+ // line to appear in more than one block, but only one block is stored, so if both end
+ // up here, then one will be lost.
+ pendingLines.set(a.Pos.Line(), int32(a.Block.ID))
+ }
+ a.Pos = a.Pos.WithNotStmt()
+ }
change = true
for a.Uses == 0 {
b := a.Args[0]
// apply rewrite function
if rv(v) {
change = true
+ // If value changed to a poor choice for a statement boundary, move the boundary
+ if v.Pos.IsStmt() == src.PosIsStmt {
+ if k := nextGoodStatementIndex(v, j, b); k != j {
+ v.Pos = v.Pos.WithNotStmt()
+ b.Values[k].Pos = b.Values[k].Pos.WithIsStmt()
+ }
+ }
}
}
}
for _, b := range f.Blocks {
j := 0
for i, v := range b.Values {
+ vl := v.Pos.Line()
if v.Op == OpInvalid {
+ if v.Pos.IsStmt() == src.PosIsStmt {
+ pendingLines.set(vl, int32(b.ID))
+ }
f.freeValue(v)
continue
}
+ if v.Pos.IsStmt() != src.PosNotStmt && pendingLines.get(vl) == int32(b.ID) {
+ pendingLines.remove(vl)
+ v.Pos = v.Pos.WithIsStmt()
+ }
if i != j {
b.Values[j] = v
}
j++
}
+ if pendingLines.get(b.Pos.Line()) == int32(b.ID) {
+ b.Pos = b.Pos.WithIsStmt()
+ pendingLines.remove(b.Pos.Line())
+ }
if j != len(b.Values) {
tail := b.Values[j:]
for j := range tail {
--- /dev/null
+package ssa_test
+
+import (
+ "debug/dwarf"
+ "debug/elf"
+ "debug/macho"
+ "debug/pe"
+ "fmt"
+ "internal/testenv"
+ "io"
+ "runtime"
+ "testing"
+)
+
+func open(path string) (*dwarf.Data, error) {
+ if fh, err := elf.Open(path); err == nil {
+ return fh.DWARF()
+ }
+
+ if fh, err := pe.Open(path); err == nil {
+ return fh.DWARF()
+ }
+
+ if fh, err := macho.Open(path); err == nil {
+ return fh.DWARF()
+ }
+
+ return nil, fmt.Errorf("unrecognized executable format")
+}
+
+func must(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
+
+type Line struct {
+ File string
+ Line int
+}
+
+type File struct {
+ lines []string
+}
+
+var fileCache = map[string]*File{}
+
+func (f *File) Get(lineno int) (string, bool) {
+ if f == nil {
+ return "", false
+ }
+ if lineno-1 < 0 || lineno-1 >= len(f.lines) {
+ return "", false
+ }
+ return f.lines[lineno-1], true
+}
+
+func TestStmtLines(t *testing.T) {
+ lines := map[Line]bool{}
+ dw, err := open(testenv.GoToolPath(t))
+ must(err)
+ rdr := dw.Reader()
+ rdr.Seek(0)
+ for {
+ e, err := rdr.Next()
+ must(err)
+ if e == nil {
+ break
+ }
+ if e.Tag != dwarf.TagCompileUnit {
+ continue
+ }
+ pkgname, _ := e.Val(dwarf.AttrName).(string)
+ if pkgname == "runtime" {
+ continue
+ }
+ lrdr, err := dw.LineReader(e)
+ must(err)
+
+ var le dwarf.LineEntry
+
+ for {
+ err := lrdr.Next(&le)
+ if err == io.EOF {
+ break
+ }
+ must(err)
+ fl := Line{le.File.Name, le.Line}
+ lines[fl] = lines[fl] || le.IsStmt
+ }
+ }
+
+ nonStmtLines := []Line{}
+ for line, isstmt := range lines {
+ if !isstmt {
+ nonStmtLines = append(nonStmtLines, line)
+ }
+ }
+
+ if runtime.GOARCH == "amd64" && len(nonStmtLines)*100 > len(lines) { // > 99% obtained on amd64, no backsliding
+ t.Errorf("Saw too many (amd64, > 1%%) lines without statement marks, total=%d, nostmt=%d\n", len(lines), len(nonStmtLines))
+ }
+ if len(nonStmtLines)*100 > 2*len(lines) { // expect 98% elsewhere.
+ t.Errorf("Saw too many (not amd64, > 2%%) lines without statement marks, total=%d, nostmt=%d\n", len(lines), len(nonStmtLines))
+ }
+ t.Logf("total=%d, nostmt=%d\n", len(lines), len(nonStmtLines))
+}
87: if a == 0 { //gdb-opt=(a,n,t)
88: continue
86: for i, a := range hist {
-92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
98: }
63: hist := make([]int, 7) //gdb-opt=(dx/O,dy/O) // TODO sink is missing if this code is in 'test' instead of 'main'
64: var reader io.Reader = strings.NewReader(cannedInput) //gdb-dbg=(hist/A) // TODO cannedInput/A is missing if this code is in 'test' instead of 'main'
65: if len(os.Args) > 1 {
+73: scanner := bufio.NewScanner(reader)
+63: hist := make([]int, 7) //gdb-opt=(dx/O,dy/O) // TODO sink is missing if this code is in 'test' instead of 'main'
+74: for scanner.Scan() { //gdb-opt=(scanner/A)
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
76: i, err := strconv.ParseInt(s, 10, 64)
77: if err != nil { //gdb-dbg=(i) //gdb-opt=(err,hist,i)
+76: i, err := strconv.ParseInt(s, 10, 64)
81: hist = ensure(int(i), hist)
82: hist[int(i)]++
+81: hist = ensure(int(i), hist)
74: for scanner.Scan() { //gdb-opt=(scanner/A)
86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
+86: for i, a := range hist {
+87: if a == 0 { //gdb-opt=(a,n,t)
+86: for i, a := range hist {
+91: n += a
+90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
91: n += a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
86: for i, a := range hist {
+90: t += i * a
+91: n += a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
+86: for i, a := range hist {
+91: n += a
+90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
91: n += a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
86: for i, a := range hist {
+90: t += i * a
+91: n += a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
+87: if a == 0 { //gdb-opt=(a,n,t)
+86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
+86: for i, a := range hist {
+91: n += a
+90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
91: n += a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
86: for i, a := range hist {
+90: t += i * a
+91: n += a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
+86: for i, a := range hist {
+91: n += a
+90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
91: n += a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
86: for i, a := range hist {
+90: t += i * a
+91: n += a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
-92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
98: }
55: func test() {
57: l := line{point{1 + zero, 2 + zero}, point{3 + zero, 4 + zero}}
58: tinycall() // this forces l etc to stack
-57: l := line{point{1 + zero, 2 + zero}, point{3 + zero, 4 + zero}}
59: dx := l.end.x - l.begin.x //gdb-dbg=(l.begin.x,l.end.y)//gdb-opt=(l,dx/O,dy/O)
l = {begin = {x = 1, y = 2}, end = {x = 3, y = 4}}
dx = <Optimized out, as expected>
a = 0
n = 0
t = 0
+86: for i, a := range hist {
+87: if a == 0 { //gdb-opt=(a,n,t)
+a = 3
+n = 0
+t = 0
91: n += a
90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
a = 3
n = 3
91: n += a
90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
a = 0
n = 6
t = 9
+86: for i, a := range hist {
+87: if a == 0 { //gdb-opt=(a,n,t)
+a = 2
+n = 6
+t = 9
91: n += a
90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
a = 1
n = 8
91: n += a
90: t += i * a
92: fmt.Fprintf(os.Stderr, "%d\t%d\t%d\t%d\t%d\n", i, a, n, i*a, t) //gdb-dbg=(n,i,t)
+86: for i, a := range hist {
87: if a == 0 { //gdb-opt=(a,n,t)
a = 0
n = 9
t = 22
+86: for i, a := range hist {
98: }
19: func test(t *thing, u *thing) {
20: if t.next != nil {
23: fmt.Fprintf(os.Stderr, "%s\n", t.name)
-24: u.self = u
-25: t.self = t
-26: t.next = u
-27: for _, p := range t.stuff {
-28: if isFoo(t, p) {
-29: return
-43: }
./testdata/scopes.go
-18: func test() {
-19: x := id(0)
-20: y := id(0)
-21: fmt.Println(x)
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-26: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
-27: fmt.Println(x, y)
-28: }
-11: }
+21: func test() {
+22: x := id(0)
+23: y := id(0)
+24: fmt.Println(x)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+30: fmt.Println(x, y)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+33: a := y
+34: f1(a)
+36: b := 0
+37: f2(b)
+38: if gretbool() {
+39: c := 0
+40: f3(c)
+45: f5(b)
+47: f6(a)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+33: a := y
+34: f1(a)
+36: b := 0
+37: f2(b)
+38: if gretbool() {
+42: c := 1.1
+43: f4(int(c))
+45: f5(b)
+47: f6(a)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+52: j = id(1)
+53: f = id(2)
+55: for i := 0; i <= 5; i++ {
+56: j += j * (j ^ 3) / 100
+57: if i == f {
+61: sleepytime()
+55: for i := 0; i <= 5; i++ {
+56: j += j * (j ^ 3) / 100
+57: if i == f {
+61: sleepytime()
+55: for i := 0; i <= 5; i++ {
+56: j += j * (j ^ 3) / 100
+57: if i == f {
+58: fmt.Println("foo")
+59: break
+63: helloworld()
+65: }
+14: }
./testdata/scopes.go
-18: func test() {
-19: x := id(0)
-20: y := id(0)
-21: fmt.Println(x)
-18: func test() {
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-22: for i := x; i < 3; i++ {
-27: fmt.Println(x, y)
-26: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
-27: fmt.Println(x, y)
-28: }
-11: }
+21: func test() {
+22: x := id(0)
+23: y := id(0)
+24: fmt.Println(x)
+25: for i := x; i < 3; i++ {
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+30: fmt.Println(x, y)
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+30: fmt.Println(x, y)
+21: func test() {
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+34: f1(a)
+37: f2(b)
+38: if gretbool() {
+40: f3(c)
+45: f5(b)
+47: f6(a)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+34: f1(a)
+37: f2(b)
+38: if gretbool() {
+43: f4(int(c))
+45: f5(b)
+47: f6(a)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+52: j = id(1)
+53: f = id(2)
+55: for i := 0; i <= 5; i++ {
+57: if i == f {
+55: for i := 0; i <= 5; i++ {
+61: sleepytime()
+55: for i := 0; i <= 5; i++ {
+57: if i == f {
+55: for i := 0; i <= 5; i++ {
+61: sleepytime()
+55: for i := 0; i <= 5; i++ {
+57: if i == f {
+58: fmt.Println("foo")
+63: helloworld()
+65: }
+14: }
src/cmd/compile/internal/ssa/testdata/scopes.go
-18: func test() {
-19: x := id(0)
-20: y := id(0)
-21: fmt.Println(x)
+21: func test() {
+22: x := id(0)
+23: y := id(0)
+24: fmt.Println(x)
0:
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
x = 0
y = 0
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
x = 1
y = 0
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
x = 4
y = 1
-22: for i := x; i < 3; i++ {
-26: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
x = 0
y = 5
-27: fmt.Println(x, y)
+30: fmt.Println(x, y)
0: 5
-11: }
+33: a := y
+34: f1(a)
+36: b := 0
+37: f2(b)
+38: if gretbool() {
+39: c := 0
+40: f3(c)
+45: f5(b)
+47: f6(a)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+33: a := y
+34: f1(a)
+36: b := 0
+37: f2(b)
+38: if gretbool() {
+42: c := 1.1
+43: f4(int(c))
+45: f5(b)
+47: f6(a)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+52: j = id(1)
+53: f = id(2)
+55: for i := 0; i <= 5; i++ {
+56: j += j * (j ^ 3) / 100
+57: if i == f {
+61: sleepytime()
+55: for i := 0; i <= 5; i++ {
+56: j += j * (j ^ 3) / 100
+57: if i == f {
+61: sleepytime()
+55: for i := 0; i <= 5; i++ {
+56: j += j * (j ^ 3) / 100
+57: if i == f {
+58: fmt.Println("foo")
+59: break
+63: helloworld()
+65: }
+14: }
src/cmd/compile/internal/ssa/testdata/scopes.go
-18: func test() {
-19: x := id(0)
-20: y := id(0)
-21: fmt.Println(x)
+21: func test() {
+22: x := id(0)
+23: y := id(0)
+24: fmt.Println(x)
0:
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
x = 0
y = 0
-22: for i := x; i < 3; i++ {
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-x = <optimized out>
-y = 0
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
x = 1
y = 0
-22: for i := x; i < 3; i++ {
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-x = <optimized out>
-y = 0
-22: for i := x; i < 3; i++ {
-23: x := i * i
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+26: x := i * i
+27: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
x = 4
y = 1
-22: for i := x; i < 3; i++ {
-24: y += id(x) //gdb-dbg=(x,y)//gdb-opt=(x,y)
-x = <optimized out>
-y = 1
-22: for i := x; i < 3; i++ {
-26: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
+25: for i := x; i < 3; i++ {
+30: fmt.Println(x, y)
+29: y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
x = 0
-y = 1
-27: fmt.Println(x, y)
+y = 5
0: 5
-11: }
+34: f1(a)
+37: f2(b)
+38: if gretbool() {
+40: f3(c)
+45: f5(b)
+47: f6(a)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+34: f1(a)
+37: f2(b)
+38: if gretbool() {
+43: f4(int(c))
+45: f5(b)
+47: f6(a)
+32: for x := 0; x <= 1; x++ { // From delve scopetest.go
+52: j = id(1)
+53: f = id(2)
+55: for i := 0; i <= 5; i++ {
+57: if i == f {
+61: sleepytime()
+55: for i := 0; i <= 5; i++ {
+57: if i == f {
+61: sleepytime()
+55: for i := 0; i <= 5; i++ {
+57: if i == f {
+58: fmt.Println("foo")
+63: helloworld()
+65: }
+14: }
package main
-import "fmt"
+import (
+ "fmt"
+ "time"
+)
func main() {
test()
}
y = x + y //gdb-dbg=(x,y)//gdb-opt=(x,y)
fmt.Println(x, y)
+
+ for x := 0; x <= 1; x++ { // From delve scopetest.go
+ a := y
+ f1(a)
+ {
+ b := 0
+ f2(b)
+ if gretbool() {
+ c := 0
+ f3(c)
+ } else {
+ c := 1.1
+ f4(int(c))
+ }
+ f5(b)
+ }
+ f6(a)
+ }
+
+ { // From delve testnextprog.go
+ var (
+ j = id(1)
+ f = id(2)
+ )
+ for i := 0; i <= 5; i++ {
+ j += j * (j ^ 3) / 100
+ if i == f {
+ fmt.Println("foo")
+ break
+ }
+ sleepytime()
+ }
+ helloworld()
+ }
+}
+
+func sleepytime() {
+ time.Sleep(5 * time.Millisecond)
+}
+
+func helloworld() {
+ fmt.Println("Hello, World!")
+}
+
+//go:noinline
+func f1(x int) {}
+
+//go:noinline
+func f2(x int) {}
+
+//go:noinline
+func f3(x int) {}
+
+//go:noinline
+func f4(x int) {}
+
+//go:noinline
+func f5(x int) {}
+
+//go:noinline
+func f6(x int) {}
+
+var boolvar = true
+
+func gretbool() bool {
+ x := boolvar
+ boolvar = !boolvar
+ return x
}
for _, a := range v.Args {
s += fmt.Sprintf(" %v", a)
}
- r := v.Block.Func.RegAlloc
+ var r []Location
+ if v.Block != nil {
+ r = v.Block.Func.RegAlloc
+ }
if int(v.ID) < len(r) && r[v.ID] != nil {
s += " : " + r[v.ID].String()
}
var names []string
- for name, values := range v.Block.Func.NamedValues {
- for _, value := range values {
- if value == v {
- names = append(names, name.String())
- break // drop duplicates.
+ if v.Block != nil {
+ for name, values := range v.Block.Func.NamedValues {
+ for _, value := range values {
+ if value == v {
+ names = append(names, name.String())
+ break // drop duplicates.
+ }
}
}
}
func (v *Value) reset(op Op) {
v.Op = op
+ if op != OpCopy && notStmtBoundary(op) {
+ // Special case for OpCopy because of how it is used in rewrite
+ v.Pos = v.Pos.WithNotStmt()
+ }
v.resetArgs()
v.AuxInt = 0
v.Aux = nil
// copyInto makes a new value identical to v and adds it to the end of b.
func (v *Value) copyInto(b *Block) *Value {
- c := b.NewValue0(v.Pos, v.Op, v.Type) // Lose the position, this causes line number churn otherwise.
+ c := b.NewValue0(v.Pos.WithNotStmt(), v.Op, v.Type) // Lose the position, this causes line number churn otherwise.
c.Aux = v.Aux
c.AuxInt = v.AuxInt
c.AddArgs(v.Args...)
return c
}
-// copyIntoNoXPos makes a new value identical to v and adds it to the end of b.
-// The copied value receives no source code position to avoid confusing changes
-// in debugger information (the intended user is the register allocator).
-func (v *Value) copyIntoNoXPos(b *Block) *Value {
- return v.copyIntoWithXPos(b, src.NoXPos)
-}
-
// copyIntoWithXPos makes a new value identical to v and adds it to the end of b.
// The supplied position is used as the position of the new value.
func (v *Value) copyIntoWithXPos(b *Block, pos src.XPos) *Value {
if x.IsStmt() == PosDefaultStmt {
return fmt.Sprintf("%d", x.Line())
}
- style := "b"
+ style, pfx := "b", "+"
if x.IsStmt() == PosNotStmt {
style = "s" // /strike not supported in HTML5
+ pfx = ""
}
- return fmt.Sprintf("<%s>%d</%s>", style, x.Line(), style)
+ return fmt.Sprintf("<%s>%s%d</%s>", style, pfx, x.Line(), style)
}
return n < m || n == m && p.lico < q.lico
}
+// SameFile reports whether p and q are positions in the same file.
+func (p XPos) SameFile(q XPos) bool {
+ return p.index == q.index
+}
+
// After reports whether the position p comes after q in the source.
// For positions with different bases, ordering is by base index.
func (p XPos) After(q XPos) bool {
// Check that assembly output has matching offset and base register
// (issue #21064).
-// amd64:`.*b\+24\(SP\)`
-// arm:`.*b\+4\(FP\)`
func check_asmout(a, b int) int {
runtime.GC() // use some frame
+ // amd64:`.*b\+24\(SP\)`
+ // arm:`.*b\+4\(FP\)`
return b
}