Skip to content

Commit

Permalink
Redo object so we don't have to allocate a
Browse files Browse the repository at this point in the history
data structure per object.  Just the heapBits
structure is sufficient.
  • Loading branch information
randall77 committed Oct 2, 2017
1 parent 5fce4c6 commit 5e17bfc
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 156 deletions.
15 changes: 9 additions & 6 deletions gocore/dwarf.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ func (c typeChunk) String() string {

// typeHeap tries to label all the heap objects with types.
func (p *Program) typeHeap() {
nobj := len(p.objects)
nobj := p.nObj

// Mapping from object index to the type info we have for that object.
// Type information is arranged in chunks. Chunks are stored in an
Expand Down Expand Up @@ -416,13 +416,16 @@ func (p *Program) typeHeap() {
}

// Extract types for each object from the result.
p.types = make([]typeInfo, nobj)
for i, chunks := range types {
x := &p.objects[i]
if len(chunks) == 1 && chunks[0].a == x.Addr {
x.Type = chunks[0].t
x.Repeat = chunks[0].r
for _, c := range chunks {
_, off := p.FindObject(c.a)
if off != 0 {
// TODO: report something useful for interior typings.
continue
}
p.types[i] = typeInfo{Type: c.t, Repeat: c.r}
}
// TODO: report something useful for various interior typings.
}
}

Expand Down
178 changes: 118 additions & 60 deletions gocore/object.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,49 @@
package gocore

import (
"sort"
"math/bits"

"github.com/randall77/corelib/core"
)

// readObjects finds all the live objects in the heap and adds them to
// the p.objects list. It does not fill in the Type fields for those objects.
// readObjects finds all the live objects in the heap and marks them
// in the p.heapInfo mark fields.
// It also fills in the p.sizes array.
func (p *Program) readObjects() {
ptrSize := p.proc.PtrSize()

// number of objects in p.objects that have been scanned
// number of live objects found so far
n := 0
// total size of live objects
var live int64

var q []Object

// Function to call when we find a new pointer.
add := func(x core.Address) {
if x == 0 || x < p.arenaStart || x >= p.arenaUsed { // not in heap
return
}
i := x.Sub(p.arenaStart) / 512
s := &p.heapInfo[i]
if s.base == 0 { // not in a valid span
h := &p.heapInfo[i]
if h.base == 0 { // not in a valid span
// TODO: probably a runtime/compiler error?
return
}
// Round down to object start.
x = s.base.Add(x.Sub(s.base) / s.size * s.size)
x = h.base.Add(x.Sub(h.base) / h.size * h.size)
// Find mark bit
off := uint64(x.Sub(p.arenaStart))
j := off / 512
s = &p.heapInfo[j]
h = &p.heapInfo[j]
b := off % 512 / 8
if s.mark&(uint64(1)<<b) != 0 { // already found
if h.mark&(uint64(1)<<b) != 0 { // already found
return
}
s.mark |= uint64(1) << b
p.objects = append(p.objects, Object{Addr: x, Size: s.size})
live += s.size
h.mark |= uint64(1) << b
n++
live += h.size
q = append(q, Object(x))
}

// Goroutine roots
Expand All @@ -57,8 +61,8 @@ func (p *Program) readObjects() {
min := core.Address(m.r.Field(s).Uintptr())
max := core.Address(m.r.Field("e" + s).Uintptr())
gc := m.r.Field("gc" + s + "mask").Field("bytedata").Address()
n := max.Sub(min) / ptrSize
for i := int64(0); i < n; i++ {
num := max.Sub(min) / ptrSize
for i := int64(0); i < num; i++ {
if p.proc.ReadUint8(gc.Add(i/8))>>uint(i%8)&1 != 0 {
add(p.proc.ReadPtr(min.Add(i * ptrSize)))
}
Expand All @@ -70,33 +74,31 @@ func (p *Program) readObjects() {
// TODO: specials

// Expand root set to all reachable objects.
for n < len(p.objects) {
obj := p.objects[n]
n++
// TODO: run in parallel?
for len(q) > 0 {
x := q[len(q)-1]
q = q[:len(q)-1]

// scan [obj.Addr,obj.Addr+obj.Size]
for a := obj.Addr; a < obj.Addr.Add(obj.Size); a = a.Add(ptrSize) {
// Scan object for pointers.
size := p.Size(x)
for i := int64(0); i < size; i += ptrSize {
a := core.Address(x).Add(i)
if p.isPtr(a) {
add(p.proc.ReadPtr(a))
}
}
}

// Sort objects for later search in increasing address order.
sort.Slice(p.objects, func(i, j int) bool {
return p.objects[i].Addr < p.objects[j].Addr
})

// Initialize firstIdx fields in the heapInfo, for fast
// address->object lookups.
for i := len(p.objects) - 1; i >= 0; i-- {
x := p.objects[i]
// last byte
p.heapInfo[x.Addr.Add(x.Size-1).Sub(p.arenaStart)/512].firstIdx = i
// first byte, plus every 512th byte
for j := int64(0); j < x.Size; j += 512 {
p.heapInfo[x.Addr.Add(j).Sub(p.arenaStart)/512].firstIdx = i
p.nObj = n

// Initialize firstIdx fields in the heapInfo, for fast object index lookups.
for i := len(p.heapInfo) - 1; i >= 0; i-- {
h := &p.heapInfo[i]
if h.mark == 0 { // not really necessary, just leave -1 sentinel as a double check.
continue
}
n -= bits.OnesCount64(h.mark)
h.firstIdx = n
}

// Update stats to include the live/garbage distinction.
Expand All @@ -121,32 +123,66 @@ func (p *Program) isPtr(a core.Address) bool {

// FindObject finds the object containing a. Returns that object and the offset within
// that object to which a points.
// Returns nil,0 if a doesn't point to a live heap object.
func (p *Program) FindObject(a core.Address) (*Object, int64) {
i, off := p.findObjectIndex(a)
if i < 0 {
return nil, 0
// Returns 0,0 if a doesn't point to a live heap object.
func (p *Program) FindObject(a core.Address) (Object, int64) {
if a < p.arenaStart || a >= p.arenaUsed {
// Not in Go heap.
return 0, 0
}
// Round down to the start of an object.
h := &p.heapInfo[a.Sub(p.arenaStart)/512]
if h.size == 0 {
// In a span that doesn't hold Go objects (freed, stacks, ...)
return 0, 0
}
x := h.base.Add(a.Sub(h.base) / h.size * h.size)
// Check if object is marked.
h = &p.heapInfo[x.Sub(p.arenaStart)/512]
if h.mark>>(uint64(x)%512/8)&1 == 0 {
return 0, 0
}
return &p.objects[i], off
return Object(x), a.Sub(x)
}

func (p *Program) findObjectIndex(a core.Address) (int, int64) {
if a < p.arenaStart || a >= p.arenaUsed {
return -1, 0
}
i := p.heapInfo[a.Sub(p.arenaStart)/512].firstIdx
if i < 0 {
x, off := p.FindObject(a)
if x == 0 {
return -1, 0
}
// Linear search within 512-byte heap region.
// Skip over objects completely less than a.
for i < len(p.objects) && p.objects[i].Addr.Add(p.objects[i].Size) <= a {
i++
}
if i == len(p.objects) || a < p.objects[i].Addr {
return -1, 0
h := &p.heapInfo[core.Address(x).Sub(p.arenaStart)/512]
return h.firstIdx + bits.OnesCount64(h.mark&(uint64(1)<<(uint64(x)%512/8)-1)), off
}

// ForEachObject calls fn with each object in the Go heap.
// If fn returns false, ForEachObject returns immediately.
func (p *Program) ForEachObject(fn func(x Object) bool) {
for i := 0; i < len(p.heapInfo); i++ {
m := p.heapInfo[i].mark
for m != 0 {
j := bits.TrailingZeros64(m)
m &= m - 1
if !fn(Object(p.arenaStart.Add(int64(i)*512 + int64(j)*8))) {
return
}
}
}
return i, a.Sub(p.objects[i].Addr)
}

// Addr returns the starting address of x.
func (p *Program) Addr(x Object) core.Address {
return core.Address(x)
}

// Size returns the size of x in bytes.
func (p *Program) Size(x Object) int64 {
return p.heapInfo[uint64(core.Address(x).Sub(p.arenaStart))/512].size
}

// Type returns the type and repeat count for the object x.
// x contains at least repeat copies of the returned type.
func (p *Program) Type(x Object) (*Type, int64) {
i, _ := p.findObjectIndex(core.Address(x))
return p.types[i].Type, p.types[i].Repeat
}

// ForEachPtr calls fn for all heap pointers it finds in x.
Expand All @@ -155,15 +191,16 @@ func (p *Program) findObjectIndex(a core.Address) (int, int64) {
// the pointed-to object y
// the offset in y where the pointer points.
// If fn returns false, ForEachPtr returns immediately.
func (p *Program) ForEachPtr(x *Object, fn func(int64, *Object, int64) bool) {
for i := int64(0); i < x.Size; i += p.proc.PtrSize() {
a := x.Addr.Add(i)
func (p *Program) ForEachPtr(x Object, fn func(int64, Object, int64) bool) {
size := p.Size(x)
for i := int64(0); i < size; i += p.proc.PtrSize() {
a := core.Address(x).Add(i)
if !p.isPtr(a) {
continue
}
ptr := p.proc.ReadPtr(a)
y, off := p.FindObject(ptr)
if y != nil {
if y != 0 {
if !fn(i, y, off) {
return
}
Expand All @@ -172,13 +209,13 @@ func (p *Program) ForEachPtr(x *Object, fn func(int64, *Object, int64) bool) {
}

// ForEachRootPtr behaves like ForEachPtr but it starts with a Root instead of an Object.
func (p *Program) ForEachRootPtr(r *Root, fn func(int64, *Object, int64) bool) {
func (p *Program) ForEachRootPtr(r *Root, fn func(int64, Object, int64) bool) {
edges1(p, r, 0, r.Type, fn)
}

// edges1 calls fn for the edges found in an object of type t living at offset off in the root r.
// If fn returns false, return immediately with false.
func edges1(p *Program, r *Root, off int64, t *Type, fn func(int64, *Object, int64) bool) bool {
func edges1(p *Program, r *Root, off int64, t *Type, fn func(int64, Object, int64) bool) bool {
switch t.Kind {
case KindBool, KindInt, KindUint, KindFloat, KindComplex:
// no edges here
Expand All @@ -189,7 +226,7 @@ func edges1(p *Program, r *Root, off int64, t *Type, fn func(int64, *Object, int
a := r.Addr.Add(off)
if r.Live == nil || r.Live[a] {
dst, off2 := p.FindObject(p.proc.ReadPtr(a))
if dst != nil {
if dst != 0 {
if !fn(off, dst, off2) {
return false
}
Expand All @@ -202,7 +239,7 @@ func edges1(p *Program, r *Root, off int64, t *Type, fn func(int64, *Object, int
a := r.Addr.Add(off)
if r.Live == nil || r.Live[a] {
dst, off2 := p.FindObject(p.proc.ReadPtr(a))
if dst != nil {
if dst != 0 {
if !fn(off, dst, off2) {
return false
}
Expand All @@ -224,3 +261,24 @@ func edges1(p *Program, r *Root, off int64, t *Type, fn func(int64, *Object, int
}
return true
}

// A revEdge is an incoming edge to an object.
// Exactly one of fromObj or fromRoot will be non-nil.
// fromIdx is the offset in fromObj/fromRoot where the pointer was found.
// toIdx is the offset in the pointed-to object where the pointer lands.
type revEdge struct {
fromObj Object
fromRoot *Root
fromIdx int64
toIdx int64 // TODO: compute when needed?
}

// ForEachIncomingPtr calls fn for all incoming pointers into object x.
// It calls fn with:
// the object or root containing the pointer (exactly one will be non-nil)
// the offset in the object/root where the pointer is found
// the offset of the target of the edge in x.
// If fn returns false, ForEachIncomingPtr returns immediately.
func (p *Program) ForEachIncomingPtr(x Object, fn func(Object, *Root, int64, int64) bool) {
//TODO
}
30 changes: 12 additions & 18 deletions gocore/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type Program struct {

heapInfo []heapInfo

// number of live objects
nObj int

goroutines []*Goroutine

// runtime info
Expand All @@ -38,15 +41,15 @@ type Program struct {
// Used to find candidates to put in the runtimeMap map.
runtimeNameMap map[string][]*Type

// All live objects in the heap.
objects []Object

// memory usage by category
stats *Stats

buildVersion string

globals []*Root

// Information about objects, indexed by Object
types []typeInfo
}

// Process returns the core.Process used to construct this Program.
Expand All @@ -58,16 +61,6 @@ func (p *Program) Goroutines() []*Goroutine {
return p.goroutines
}

// ForEachObject calls fn with each object in the Go heap.
// If fn returns false, ForEachObject returns immediately.
func (p *Program) ForEachObject(fn func(o *Object) bool) {
for i := 0; i < len(p.objects); i++ {
if !fn(&p.objects[i]) {
return
}
}
}

// Stats returns a breakdown of the program's memory use by category.
func (p *Program) Stats() *Stats {
return p.stats
Expand Down Expand Up @@ -270,10 +263,11 @@ func (f *Func) Entry() core.Address {
return f.entry
}

// An Object represents an object in the Go heap.
type Object struct {
Addr core.Address
Size int64
// An Object represents a single object in the Go heap.
type Object core.Address

// A typeInfo contains information about the type of an object.
type typeInfo struct {
// This object has an effective type of [Repeat]Type.
// Parts of the object beyond the first Repeat*Type.Size bytes have unknown type.
// If Type == nil, the type is unknown. (TODO: provide access to ptr/nonptr bits in this case.)
Expand Down Expand Up @@ -306,5 +300,5 @@ type heapInfo struct {
base core.Address // start of the span containing this heap region
size int64 // size of objects in the span
mark uint64 // 64 mark bits, one for every 8 bytes
firstIdx int // the index of the first object that has any overlap with this region
firstIdx int // the index of the first object that starts in this region
}
Loading

0 comments on commit 5e17bfc

Please sign in to comment.