// cap returns one more than the largest key valid for s
func (s *biasedSparseMap) cap() int {
- if s.s == nil {
+ if s == nil || 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 {
+ if s == nil || s.s == nil {
return 0
}
return s.s.size()
// contains reports whether x is a key in s
func (s *biasedSparseMap) contains(x uint) bool {
- if s.s == nil {
+ if s == nil || s.s == nil {
return false
}
if 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 {
+ if s == nil || s.s == nil {
return -1
}
if int(x) < s.first {
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
+ pendingLines.set(v.Pos, 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 {
+ if j := pendingLines.get(w.Pos); j > -1 && f.Blocks[j] == w.Block {
w.Pos = w.Pos.WithIsStmt()
- pendingLines.remove(w.Pos.Line())
+ pendingLines.remove(w.Pos)
}
}
// 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)
+ pendingLines.foreachEntry(func(j int32, l uint, bi int32) {
b := f.Blocks[bi]
- if b.Pos.Line() == l {
+ if b.Pos.Line() == l && b.Pos.FileIndex() == j {
b.Pos = b.Pos.WithIsStmt()
}
- }
+ })
// Remove dead values from blocks' value list. Return dead
// values to the allocator.
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
- cachedLineStarts *biasedSparseMap // cached map/set of line numbers to integers
+ cachedPostorder []*Block // cached postorder traversal
+ cachedIdom []*Block // cached immediate dominators
+ cachedSdom SparseTree // cached dominator tree
+ cachedLoopnest *loopnest // cached loop nest information
+ cachedLineStarts *xposmap // cached map/set of xpos 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
// But not both.
f.Cache = new(Cache)
f.pass = &emptyPass
- f.cachedLineStarts = newBiasedSparseMap(0, 100)
+ f.cachedLineStarts = newXposmap(map[int]lineRange{0: {0, 100}, 1: {0, 100}, 2: {0, 100}, 3: {0, 100}, 4: {0, 100}})
blocks := make(map[string]*Block)
values := make(map[string]*Value)
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())
+ pendingLines.add(v.Pos)
v.Pos = v.Pos.WithNotStmt()
}
// This is a redundant explicit nil check.
f.Warnl(v.Pos, "removed nil check")
}
if v.Pos.IsStmt() == src.PosIsStmt { // About to lose a statement boundary
- pendingLines.add(v.Pos.Line())
+ pendingLines.add(v.Pos)
}
v.reset(OpUnknown)
f.freeValue(v)
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 {
+ if pendingLines.contains(v.Pos) && v.Pos.IsStmt() != src.PosNotStmt {
v.Pos = v.Pos.WithIsStmt()
- pendingLines.remove(v.Pos.Line())
+ pendingLines.remove(v.Pos)
}
}
}
- if pendingLines.contains(b.Pos.Line()) {
+ if pendingLines.contains(b.Pos) {
b.Pos = b.Pos.WithIsStmt()
- pendingLines.remove(b.Pos.Line())
+ pendingLines.remove(b.Pos)
}
for j := i; j < len(b.Values); j++ {
b.Values[j] = nil
f.Warnl(v.Pos, "removed nil check")
}
if v.Pos.IsStmt() == src.PosIsStmt {
- pendingLines.add(v.Pos.Line())
+ pendingLines.add(v.Pos)
}
v.reset(OpUnknown)
firstToRemove = i
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()) {
+ if v.Pos.IsStmt() != src.PosNotStmt && pendingLines.contains(v.Pos) {
v.Pos = v.Pos.WithIsStmt()
- pendingLines.remove(v.Pos.Line())
+ pendingLines.remove(v.Pos)
}
b.Values[i] = v
i++
}
}
- if pendingLines.contains(b.Pos.Line()) {
+ if pendingLines.contains(b.Pos) {
b.Pos = b.Pos.WithIsStmt()
}
import (
"cmd/internal/obj"
"cmd/internal/src"
- "math"
)
func isPoorStatementOp(op Op) bool {
if b.Values[j].Pos.IsStmt() == src.PosNotStmt { // ignore non-statements
continue
}
- if b.Values[j].Pos.Line() == v.Pos.Line() {
+ if b.Values[j].Pos.Line() == v.Pos.Line() && v.Pos.SameFile(b.Values[j].Pos) {
return j
}
return i
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
+ ranges := make(map[int]lineRange)
+ note := func(p src.XPos) {
+ line := uint32(p.Line())
+ i := int(p.FileIndex())
+ lp, found := ranges[i]
+ change := false
+ if line < lp.first || !found {
+ lp.first = line
+ change = true
}
- if line > last {
- last = line
+ if line > lp.last {
+ lp.last = line
+ change = true
+ }
+ if change {
+ ranges[i] = lp
}
}
firstPos := src.NoXPos
firstPosIndex := -1
if b.Pos.IsStmt() != src.PosNotStmt {
- note(b.Pos.Line())
+ note(b.Pos)
}
for i := 0; i < len(b.Values); i++ {
v := b.Values[i]
if v.Pos.IsStmt() != src.PosNotStmt {
- note(v.Pos.Line())
+ note(v.Pos)
// skip ahead to better instruction for this line if possible
i = nextGoodStatementIndex(v, i, b)
v = b.Values[i]
if v.Pos.IsStmt() == src.PosNotStmt {
continue
}
- note(v.Pos.Line())
+ note(v.Pos)
// skip ahead if possible
i = nextGoodStatementIndex(v, i, b)
v = b.Values[i]
}
endlines[b.ID] = firstPos
}
- f.cachedLineStarts = newBiasedSparseMap(int(first), int(last))
+ // cachedLineStarts is an empty sparse map for values that are included within ranges.
+ f.cachedLineStarts = newXposmap(ranges)
}
// 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))
+ pendingLines.set(a.Pos, int32(a.Block.ID))
}
a.Pos = a.Pos.WithNotStmt()
}
for _, b := range f.Blocks {
j := 0
for i, v := range b.Values {
- vl := v.Pos.Line()
+ vl := v.Pos
if v.Op == OpInvalid {
if v.Pos.IsStmt() == src.PosIsStmt {
pendingLines.set(vl, int32(b.ID))
}
j++
}
- if pendingLines.get(b.Pos.Line()) == int32(b.ID) {
+ if pendingLines.get(b.Pos) == int32(b.ID) {
b.Pos = b.Pos.WithIsStmt()
- pendingLines.remove(b.Pos.Line())
+ pendingLines.remove(b.Pos)
}
if j != len(b.Values) {
tail := b.Values[j:]
--- /dev/null
+// Copyright 2019 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"
+ "fmt"
+)
+
+type lineRange struct {
+ first, last uint32
+}
+
+// An xposmap is a map from fileindex and line of src.XPos to int32,
+// implemented sparsely to save space (column and statement status are ignored).
+// The sparse skeleton is constructed once, and then reused by ssa phases
+// that (re)move values with statements attached.
+type xposmap struct {
+ // A map from file index to maps from line range to integers (block numbers)
+ maps map[int32]*biasedSparseMap
+ // The next two fields provide a single-item cache for common case of repeated lines from same file.
+ lastIndex int32 // -1 means no entry in cache
+ lastMap *biasedSparseMap // map found at maps[lastIndex]
+}
+
+// newXposmap constructs an xposmap valid for inputs which have a file index in the keys of x,
+// and line numbers in the range x[file index].
+// The resulting xposmap will panic if a caller attempts to set or add an XPos not in that range.
+func newXposmap(x map[int]lineRange) *xposmap {
+ maps := make(map[int32]*biasedSparseMap)
+ for i, p := range x {
+ maps[int32(i)] = newBiasedSparseMap(int(p.first), int(p.last))
+ }
+ return &xposmap{maps: maps, lastIndex: -1} // zero for the rest is okay
+}
+
+// clear removes data from the map but leaves the sparse skeleton.
+func (m *xposmap) clear() {
+ for _, l := range m.maps {
+ if l != nil {
+ l.clear()
+ }
+ }
+ m.lastIndex = -1
+ m.lastMap = nil
+}
+
+// mapFor returns the line range map for a given file index.
+func (m *xposmap) mapFor(index int32) *biasedSparseMap {
+ if index == m.lastIndex {
+ return m.lastMap
+ }
+ mf := m.maps[index]
+ m.lastIndex = index
+ m.lastMap = mf
+ return mf
+}
+
+// set inserts p->v into the map.
+// If p does not fall within the set of fileindex->lineRange used to construct m, this will panic.
+func (m *xposmap) set(p src.XPos, v int32) {
+ s := m.mapFor(p.FileIndex())
+ if s == nil {
+ panic(fmt.Sprintf("xposmap.set(%d), file index not found in map\n", p.FileIndex()))
+ }
+ s.set(p.Line(), v)
+}
+
+// get returns the int32 associated with the file index and line of p.
+func (m *xposmap) get(p src.XPos) int32 {
+ s := m.mapFor(p.FileIndex())
+ if s == nil {
+ return -1
+ }
+ return s.get(p.Line())
+}
+
+// add adds p to m, treating m as a set instead of as a map.
+// If p does not fall within the set of fileindex->lineRange used to construct m, this will panic.
+// Use clear() in between set/map interpretations of m.
+func (m *xposmap) add(p src.XPos) {
+ m.set(p, 0)
+}
+
+// contains returns whether the file index and line of p are in m,
+// treating m as a set instead of as a map.
+func (m *xposmap) contains(p src.XPos) bool {
+ s := m.mapFor(p.FileIndex())
+ if s == nil {
+ return false
+ }
+ return s.contains(p.Line())
+}
+
+// remove removes the file index and line for p from m,
+// whether m is currently treated as a map or set.
+func (m *xposmap) remove(p src.XPos) {
+ s := m.mapFor(p.FileIndex())
+ if s == nil {
+ return
+ }
+ s.remove(p.Line())
+}
+
+// foreachEntry applies f to each (fileindex, line, value) triple in m.
+func (m *xposmap) foreachEntry(f func(j int32, l uint, v int32)) {
+ for j, mm := range m.maps {
+ s := mm.size()
+ for i := 0; i < s; i++ {
+ l, v := mm.getEntry(i)
+ f(j, l, v)
+ }
+ }
+}
return p
}
+// LineNumber returns a string for the line number, "?" if it is not known.
func (p XPos) LineNumber() string {
if !p.IsKnown() {
return "?"
return p.lico.lineNumber()
}
+// FileIndex returns a smallish non-negative integer corresponding to the
+// file for this source position. Smallish is relative; it can be thousands
+// large, but not millions.
+func (p XPos) FileIndex() int32 {
+ return p.index
+}
+
func (p XPos) LineNumberHTML() string {
if !p.IsKnown() {
return "?"