]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: assign and preserve statement boundaries.
authorDavid Chase <drchase@google.com>
Sat, 24 Mar 2018 02:46:06 +0000 (22:46 -0400)
committerDavid Chase <drchase@google.com>
Mon, 14 May 2018 14:09:49 +0000 (14:09 +0000)
A new pass run after ssa building (before any other
optimization) identifies the "first" ssa node for each
statement. Other "noise" nodes are tagged as being never
appropriate for a statement boundary (e.g., VarKill, VarDef,
Phi).

Rewrite, deadcode, cse, and nilcheck are modified to move
the statement boundaries forward whenever possible if a
boundary-tagged ssa value is removed; never-boundary nodes
are ignored in this search (some operations involving
constants are also tagged as never-boundary and also ignored
because they are likely to be moved or removed during
optimization).

Code generation treats all nodes except those explicitly
marked as statement boundaries as "not statement" nodes,
and floats statement boundaries to the beginning of each
same-line run of instructions found within a basic block.

Line number html conversion was modified to make statement
boundary nodes a bit more obvious by prepending a "+".

The code in fuse.go that glued together the value slices
of two blocks produced a result that depended on the
former capacities (not lengths) of the two slices.  This
causes differences in the 386 bootstrap, and also can
sometimes put values into an order that does a worse job
of preserving statement boundaries when values are removed.

Portions of two delve tests that had caught problems were
incorporated into ssa/debug_test.go.  There are some
opportunities to do better with optimized code, but the
next-ing is not lying or overly jumpy.

Over 4 CLs, compilebench geomean measured binary size
increase of 3.5% and compile user time increase of 3.8%
(this is after optimization to reuse a sparse map instead
of creating multiple maps.)

This CL worsens the optimized-debugging experience with
Delve; we need to work with the delve team so that
they can use the is_stmt marks that we're emitting now.

The reference output changes from time to time depending
on other changes in the compiler, sometimes better,
sometimes worse.

This CL now includes a test ensuring that 99+% of the lines
in the Go command itself (a handy optimized binary) include
is_stmt markers.

Change-Id: I359c94e06843f1eb41f9da437bd614885aa9644a
Reviewed-on: https://go-review.googlesource.com/102435
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
27 files changed:
src/cmd/compile/internal/gc/gsubr.go
src/cmd/compile/internal/gc/ssa.go
src/cmd/compile/internal/ssa/biasedsparsemap.go [new file with mode: 0644]
src/cmd/compile/internal/ssa/compile.go
src/cmd/compile/internal/ssa/cse.go
src/cmd/compile/internal/ssa/deadcode.go
src/cmd/compile/internal/ssa/func.go
src/cmd/compile/internal/ssa/func_test.go
src/cmd/compile/internal/ssa/fuse.go
src/cmd/compile/internal/ssa/nilcheck.go
src/cmd/compile/internal/ssa/numberlines.go [new file with mode: 0644]
src/cmd/compile/internal/ssa/regalloc.go
src/cmd/compile/internal/ssa/rewrite.go
src/cmd/compile/internal/ssa/stmtlines_test.go [new file with mode: 0644]
src/cmd/compile/internal/ssa/testdata/hist.dlv-dbg.nexts
src/cmd/compile/internal/ssa/testdata/hist.dlv-opt.nexts
src/cmd/compile/internal/ssa/testdata/hist.gdb-opt.nexts
src/cmd/compile/internal/ssa/testdata/i22558.gdb-dbg.nexts
src/cmd/compile/internal/ssa/testdata/scopes.dlv-dbg.nexts
src/cmd/compile/internal/ssa/testdata/scopes.dlv-opt.nexts
src/cmd/compile/internal/ssa/testdata/scopes.gdb-dbg.nexts
src/cmd/compile/internal/ssa/testdata/scopes.gdb-opt.nexts
src/cmd/compile/internal/ssa/testdata/scopes.go
src/cmd/compile/internal/ssa/value.go
src/cmd/internal/src/pos.go
src/cmd/internal/src/xpos.go
test/codegen/stack.go

index d98e085705ad7e571d0fdabbbb0d09b16a5aa100..29994afb8cf23596154b81babb3583d2532d51a7 100644 (file)
@@ -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
 }
 
index 73fe7bdfeed050edeecb75545ba167d2c1498655..32003843e0d57583f2f8af35d15a4172765755dd 100644 (file)
@@ -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 (file)
index 0000000..e1901f2
--- /dev/null
@@ -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()
+       }
+}
index 69bb35655af575d822741aad9638e75d3dd13807..4bd9ade4799b4ab6b4c3a4d43ff8385cc636dc45 100644 (file)
@@ -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
index 7e9c4f4a87f3459b02ebc8ee8fd0df7ed53b4032..75595c3b3ddfde76aacf970fdd1805f22f08c97a 100644 (file)
@@ -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++
                                }
index c9951b45f27eba8d840053ee96b5b56fd355cb87..322ea82c8d603f6b73b769710bacfd35b264e0e6 100644 (file)
@@ -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 {
index 0a74e6ef86c0a44641460fc01269f01b5cc307e2..a2991040eec3574aafa0f1571bb27e224e71143e 100644 (file)
@@ -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
 }
index 79d26c7ea274b8454d0bfff922aec7394e762503..74ce62ba7e91b90314b38997904c37d57c3f1870 100644 (file)
@@ -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)
index 45b13a050d939c71364315dba0edadd5948b041c..ca840e37ff4b1ab3ecd5f71fbcc5596eee1bc9d8 100644 (file)
@@ -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...)
        }
index b107f8a836c1bf49b0b5a0672081d6ca28d7b51e..2e4ad064a9a04bc27844bd497ac6a04940892a7b 100644 (file)
@@ -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 (file)
index 0000000..14eceec
--- /dev/null
@@ -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))
+}
index 9c5486c9548bf49693ed743666e2f34feb0f35ba..c598377e4787bc42eb854ffe0c453f49da99885d 100644 (file)
@@ -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.
index f4781607fd82be9739ea47a416b1c2eca1069d9d..7cf3144ba916bf621ff6a99f2b9d28d3021efae2 100644 (file)
@@ -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 (file)
index 0000000..ff505ae
--- /dev/null
@@ -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))
+}
index 9c70bb587a912c42df76e9830b3ac66e3ad6a3fc..ec79b77de277c8dc9742a62a15c99e2cf139b1de 100644 (file)
@@ -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:    }
index 988e3938f80721f557eae5eba476fe05f5214696..3f10e15aa0ad09db24d70b3df44c68b207c89d58 100644 (file)
@@ -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)
 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:    }
index 69091d12ed7464bb9b8cff9c617bc3c17a603232..e04158982e35de3f6a487ccf1d6c7be561566d61 100644 (file)
@@ -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 = <Optimized out, as expected>
@@ -116,9 +115,15 @@ scanner = (struct bufio.Scanner *) <A>
 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:    }
index b88a227ec6424f9fab83816dd40445a497587473..8a49b168bf1172a9584911b579bea1a5126df91a 100644 (file)
@@ -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:    }
index 3a49afc1d2b06de81e73f246e8a2c85647701788..8151b59475e52bef8aaba62786d23036f4336b8f 100644 (file)
@@ -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:    }
index 749f1d33fdb25c60075afe822e82013b91a443da..2036f398a45b3f5fb7e3f21dc3811c90c7e3f32e 100644 (file)
@@ -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:    }
index 03041282ecd4ba2baf70690a9b1207bae4d79a26..813a71de7eea46b0f98a4c4b97a35a6c086e3c5f 100644 (file)
@@ -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:    }
index 0a09989f88b6ca20f6d551c4e7f26893d3383b00..b6382375d40f013ed0d8b42b703927e4d61be663 100644 (file)
@@ -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 = <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:    }
index 9434aba6bf528c565528c23875257e92b2863fcb..3dab51908a4dc798adabb521fc0c9ed2797357a9 100644 (file)
@@ -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
 }
index 9a79a99f54ba5bd41d2748bf7d827b65d70f4340..6d5fe9caedcfba97e8387322192c56acb1d8d32f 100644 (file)
@@ -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 {
index 52aabd9a32c5ba3b8ce8473e6bdeaa16c6eee724..1cf68f28b5114f2d9d8b6eaa84424648c463630d 100644 (file)
@@ -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</%s>", style, x.Line(), style)
+       return fmt.Sprintf("<%s>%s%d</%s>", style, pfx, x.Line(), style)
 }
index ab7fc16df0d2c5a1790fe2997ca3e6c44344b2db..b03aafa2c7ebd4d1b7ea7ba4d9a0f3715009052e 100644 (file)
@@ -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 {
index 4469b574491bcb0803379063cd6f6ff9b8c70e18..7e12dbc0eb6bbe0af61e3c3d34df2a8137c44050 100644 (file)
@@ -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
 }