diff --git a/src/cmd/compile/internal/gc/gsubr.go b/src/cmd/compile/internal/gc/gsubr.go index d98e085705..29994afb8c 100644 --- a/src/cmd/compile/internal/gc/gsubr.go +++ b/src/cmd/compile/internal/gc/gsubr.go @@ -113,6 +113,17 @@ func (pp *Progs) Prog(as obj.As) *obj.Prog { 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 } diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 73fe7bdfee..32003843e0 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -787,7 +787,7 @@ func (s *state) stmt(n *Node) { } 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: @@ -935,7 +935,7 @@ func (s *state) stmt(n *Node) { case ORETURN: s.stmtList(n.List) b := s.exit() - b.Pos = s.lastPos + b.Pos = s.lastPos.WithIsStmt() case ORETJMP: s.stmtList(n.List) @@ -966,7 +966,7 @@ func (s *state) stmt(n *Node) { } 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: @@ -4696,11 +4696,30 @@ type SSAGenState struct { // 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. @@ -4723,25 +4742,27 @@ func (s *SSAGenState) Br(op obj.As, target *ssa.Block) *obj.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) } } } @@ -4788,8 +4809,9 @@ func genssa(f *ssa.Func, pp *Progs) { // 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 } } @@ -4797,7 +4819,7 @@ func genssa(f *ssa.Func, pp *Progs) { // 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 { @@ -4898,9 +4920,12 @@ func genssa(f *ssa.Func, pp *Progs) { } } - // 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 { diff --git a/src/cmd/compile/internal/ssa/biasedsparsemap.go b/src/cmd/compile/internal/ssa/biasedsparsemap.go new file mode 100644 index 0000000000..e1901f2135 --- /dev/null +++ b/src/cmd/compile/internal/ssa/biasedsparsemap.go @@ -0,0 +1,112 @@ +// 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() + } +} diff --git a/src/cmd/compile/internal/ssa/compile.go b/src/cmd/compile/internal/ssa/compile.go index 69bb35655a..4bd9ade479 100644 --- a/src/cmd/compile/internal/ssa/compile.go +++ b/src/cmd/compile/internal/ssa/compile.go @@ -356,6 +356,7 @@ commas. For example: // 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 diff --git a/src/cmd/compile/internal/ssa/cse.go b/src/cmd/compile/internal/ssa/cse.go index 7e9c4f4a87..75595c3b3d 100644 --- a/src/cmd/compile/internal/ssa/cse.go +++ b/src/cmd/compile/internal/ssa/cse.go @@ -6,6 +6,7 @@ package ssa import ( "cmd/compile/internal/types" + "cmd/internal/src" "fmt" "sort" ) @@ -233,6 +234,15 @@ func cse(f *Func) { 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++ } diff --git a/src/cmd/compile/internal/ssa/deadcode.go b/src/cmd/compile/internal/ssa/deadcode.go index c9951b45f2..322ea82c8d 100644 --- a/src/cmd/compile/internal/ssa/deadcode.go +++ b/src/cmd/compile/internal/ssa/deadcode.go @@ -4,10 +4,14 @@ 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 } @@ -40,10 +44,12 @@ func ReachableBlocks(f *Func) []bool { 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. @@ -51,7 +57,7 @@ func liveValues(f *Func, reachable []bool) []bool { for i := range live { live[i] = true } - return live + return } // Find all live values @@ -66,16 +72,25 @@ func liveValues(f *Func, reachable []bool) []bool { 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) + } } } } @@ -92,11 +107,14 @@ func liveValues(f *Func, reachable []bool) []bool { 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. @@ -144,7 +162,7 @@ func deadcode(f *Func) { 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()) @@ -177,18 +195,43 @@ func deadcode(f *Func) { } 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 { diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 0a74e6ef86..a2991040ee 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -56,13 +56,13 @@ type Func struct { 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 } @@ -149,6 +149,9 @@ func (f *Func) newValue(op Op, t *types.Type, b *Block, pos src.XPos) *Value { 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 @@ -176,6 +179,9 @@ func (f *Func) newValueNoBlock(op Op, t *types.Type, pos src.XPos) *Value { v.Op = op v.Type = t v.Block = nil // caller must fix this. + if notStmtBoundary(op) { + pos = pos.WithNotStmt() + } v.Pos = pos return v } diff --git a/src/cmd/compile/internal/ssa/func_test.go b/src/cmd/compile/internal/ssa/func_test.go index 79d26c7ea2..74ce62ba7e 100644 --- a/src/cmd/compile/internal/ssa/func_test.go +++ b/src/cmd/compile/internal/ssa/func_test.go @@ -152,6 +152,7 @@ func (c *Conf) Fun(entry string, blocs ...bloc) fun { // 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) diff --git a/src/cmd/compile/internal/ssa/fuse.go b/src/cmd/compile/internal/ssa/fuse.go index 45b13a050d..ca840e37ff 100644 --- a/src/cmd/compile/internal/ssa/fuse.go +++ b/src/cmd/compile/internal/ssa/fuse.go @@ -4,6 +4,10 @@ package ssa +import ( + "cmd/internal/src" +) + // fuse simplifies control flow by joining basic blocks. func fuse(f *Func) { for changed := true; changed; { @@ -121,6 +125,25 @@ func fuseBlockPlain(b *Block) bool { 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 @@ -128,8 +151,25 @@ func fuseBlockPlain(b *Block) bool { // 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...) } diff --git a/src/cmd/compile/internal/ssa/nilcheck.go b/src/cmd/compile/internal/ssa/nilcheck.go index b107f8a836..2e4ad064a9 100644 --- a/src/cmd/compile/internal/ssa/nilcheck.go +++ b/src/cmd/compile/internal/ssa/nilcheck.go @@ -4,6 +4,10 @@ package ssa +import ( + "cmd/internal/src" +) + // nilcheckelim eliminates unnecessary nil checks. // runs on machine-independent code. func nilcheckelim(f *Func) { @@ -103,6 +107,9 @@ 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 { @@ -112,6 +119,10 @@ func nilcheckelim(f *Func) { 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 @@ -125,6 +136,9 @@ func nilcheckelim(f *Func) { 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-- @@ -134,8 +148,18 @@ func nilcheckelim(f *Func) { // 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 } @@ -163,11 +187,15 @@ const minZeroPage = 4096 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-- { @@ -176,6 +204,9 @@ func nilcheckelim2(f *Func) { 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 @@ -231,10 +262,19 @@ func nilcheckelim2(f *Func) { 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 } diff --git a/src/cmd/compile/internal/ssa/numberlines.go b/src/cmd/compile/internal/ssa/numberlines.go new file mode 100644 index 0000000000..14eceec77f --- /dev/null +++ b/src/cmd/compile/internal/ssa/numberlines.go @@ -0,0 +1,162 @@ +// 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)) +} diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go index 9c5486c954..c598377e47 100644 --- a/src/cmd/compile/internal/ssa/regalloc.go +++ b/src/cmd/compile/internal/ssa/regalloc.go @@ -2006,7 +2006,7 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP 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. diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index f4781607fd..7cf3144ba9 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -7,6 +7,7 @@ package ssa import ( "cmd/compile/internal/types" "cmd/internal/obj" + "cmd/internal/src" "fmt" "io" "math" @@ -16,6 +17,8 @@ import ( 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 { @@ -27,7 +30,7 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) { if rb(b) { change = true } - for _, v := range b.Values { + for j, v := range b.Values { change = phielimValue(v) || change // Eliminate copy inputs. @@ -41,7 +44,27 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) { 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] @@ -53,6 +76,13 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) { // 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() + } + } } } } @@ -64,15 +94,27 @@ func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) { 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 { diff --git a/src/cmd/compile/internal/ssa/stmtlines_test.go b/src/cmd/compile/internal/ssa/stmtlines_test.go new file mode 100644 index 0000000000..ff505ae357 --- /dev/null +++ b/src/cmd/compile/internal/ssa/stmtlines_test.go @@ -0,0 +1,107 @@ +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)) +} diff --git a/src/cmd/compile/internal/ssa/testdata/hist.dlv-dbg.nexts b/src/cmd/compile/internal/ssa/testdata/hist.dlv-dbg.nexts index 9c70bb587a..ec79b77de2 100644 --- a/src/cmd/compile/internal/ssa/testdata/hist.dlv-dbg.nexts +++ b/src/cmd/compile/internal/ssa/testdata/hist.dlv-dbg.nexts @@ -96,5 +96,4 @@ 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: } diff --git a/src/cmd/compile/internal/ssa/testdata/hist.dlv-opt.nexts b/src/cmd/compile/internal/ssa/testdata/hist.dlv-opt.nexts index 988e3938f8..3f10e15aa0 100644 --- a/src/cmd/compile/internal/ssa/testdata/hist.dlv-opt.nexts +++ b/src/cmd/compile/internal/ssa/testdata/hist.dlv-opt.nexts @@ -9,54 +9,81 @@ 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) @@ -65,8 +92,14 @@ 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) @@ -75,8 +108,16 @@ 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) @@ -85,8 +126,14 @@ 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) @@ -95,7 +142,10 @@ 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: } diff --git a/src/cmd/compile/internal/ssa/testdata/hist.gdb-opt.nexts b/src/cmd/compile/internal/ssa/testdata/hist.gdb-opt.nexts index 69091d12ed..e04158982e 100644 --- a/src/cmd/compile/internal/ssa/testdata/hist.gdb-opt.nexts +++ b/src/cmd/compile/internal/ssa/testdata/hist.gdb-opt.nexts @@ -2,7 +2,6 @@ 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 = @@ -116,9 +115,15 @@ scanner = (struct bufio.Scanner *) 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 @@ -126,13 +131,20 @@ t = 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 @@ -140,8 +152,10 @@ t = 17 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: } diff --git a/src/cmd/compile/internal/ssa/testdata/i22558.gdb-dbg.nexts b/src/cmd/compile/internal/ssa/testdata/i22558.gdb-dbg.nexts index b88a227ec6..8a49b168bf 100644 --- a/src/cmd/compile/internal/ssa/testdata/i22558.gdb-dbg.nexts +++ b/src/cmd/compile/internal/ssa/testdata/i22558.gdb-dbg.nexts @@ -2,10 +2,3 @@ 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: } diff --git a/src/cmd/compile/internal/ssa/testdata/scopes.dlv-dbg.nexts b/src/cmd/compile/internal/ssa/testdata/scopes.dlv-dbg.nexts index 3a49afc1d2..8151b59475 100644 --- a/src/cmd/compile/internal/ssa/testdata/scopes.dlv-dbg.nexts +++ b/src/cmd/compile/internal/ssa/testdata/scopes.dlv-dbg.nexts @@ -1,19 +1,56 @@ ./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: } diff --git a/src/cmd/compile/internal/ssa/testdata/scopes.dlv-opt.nexts b/src/cmd/compile/internal/ssa/testdata/scopes.dlv-opt.nexts index 749f1d33fd..2036f398a4 100644 --- a/src/cmd/compile/internal/ssa/testdata/scopes.dlv-opt.nexts +++ b/src/cmd/compile/internal/ssa/testdata/scopes.dlv-opt.nexts @@ -1,27 +1,63 @@ ./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: } diff --git a/src/cmd/compile/internal/ssa/testdata/scopes.gdb-dbg.nexts b/src/cmd/compile/internal/ssa/testdata/scopes.gdb-dbg.nexts index 03041282ec..813a71de7e 100644 --- a/src/cmd/compile/internal/ssa/testdata/scopes.gdb-dbg.nexts +++ b/src/cmd/compile/internal/ssa/testdata/scopes.gdb-dbg.nexts @@ -1,27 +1,64 @@ 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: } diff --git a/src/cmd/compile/internal/ssa/testdata/scopes.gdb-opt.nexts b/src/cmd/compile/internal/ssa/testdata/scopes.gdb-opt.nexts index 0a09989f88..b6382375d4 100644 --- a/src/cmd/compile/internal/ssa/testdata/scopes.gdb-opt.nexts +++ b/src/cmd/compile/internal/ssa/testdata/scopes.gdb-opt.nexts @@ -1,39 +1,54 @@ 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 = -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 = -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 = -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: } diff --git a/src/cmd/compile/internal/ssa/testdata/scopes.go b/src/cmd/compile/internal/ssa/testdata/scopes.go index 9434aba6bf..3dab51908a 100644 --- a/src/cmd/compile/internal/ssa/testdata/scopes.go +++ b/src/cmd/compile/internal/ssa/testdata/scopes.go @@ -4,7 +4,10 @@ package main -import "fmt" +import ( + "fmt" + "time" +) func main() { test() @@ -25,4 +28,72 @@ func 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 } diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go index 9a79a99f54..6d5fe9caed 100644 --- a/src/cmd/compile/internal/ssa/value.go +++ b/src/cmd/compile/internal/ssa/value.go @@ -130,16 +130,21 @@ func (v *Value) LongString() string { 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. + } } } } @@ -244,6 +249,10 @@ func (v *Value) resetArgs() { 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 @@ -251,7 +260,7 @@ func (v *Value) reset(op Op) { // 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...) @@ -263,13 +272,6 @@ func (v *Value) copyInto(b *Block) *Value { 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 { diff --git a/src/cmd/internal/src/pos.go b/src/cmd/internal/src/pos.go index 52aabd9a32..1cf68f28b5 100644 --- a/src/cmd/internal/src/pos.go +++ b/src/cmd/internal/src/pos.go @@ -395,9 +395,10 @@ func (x lico) lineNumberHTML() string { 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", style, x.Line(), style) + return fmt.Sprintf("<%s>%s%d", style, pfx, x.Line(), style) } diff --git a/src/cmd/internal/src/xpos.go b/src/cmd/internal/src/xpos.go index ab7fc16df0..b03aafa2c7 100644 --- a/src/cmd/internal/src/xpos.go +++ b/src/cmd/internal/src/xpos.go @@ -30,6 +30,11 @@ func (p XPos) Before(q XPos) bool { 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 { diff --git a/test/codegen/stack.go b/test/codegen/stack.go index 4469b57449..7e12dbc0eb 100644 --- a/test/codegen/stack.go +++ b/test/codegen/stack.go @@ -87,9 +87,9 @@ func ArrayInit(i, j int) [4]int { // 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 }