cmd/compile: assign and preserve statement boundaries.

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>
This commit is contained in:
David Chase 2018-03-23 22:46:06 -04:00
parent c06f027520
commit c2c1822b12
27 changed files with 1016 additions and 156 deletions

View 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
}

View 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 {

View file

@ -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()
}
}

View 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

View 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++
}

View 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 {

View 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
}

View 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)

View 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...)
}

View 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
}

View file

@ -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))
}

View 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.

View 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 {

View file

@ -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))
}

View 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: }

View 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)
@ -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: }

View 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: }

View 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: }

View 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: }

View 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: }

View 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: }

View 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: }

View 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
}

View 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 {

View 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)
}

View 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 {

View 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
}