gocore: support typing composite roots

This change adds support for composite roots to typeHeap, which should
make it possible to type more of the heap.

Change-Id: I7757f23bf18375160c4f2607efd290aeeb89ec93
Reviewed-on: https://go-review.googlesource.com/c/debug/+/659797
Reviewed-by: Nicolas Hillegeer <aktau@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/gocore/gocore_test.go b/internal/gocore/gocore_test.go
index 6899f78..9a5981b 100644
--- a/internal/gocore/gocore_test.go
+++ b/internal/gocore/gocore_test.go
@@ -301,9 +301,7 @@
 					}
 
 					// Check object counts.
-					//
-					// TODO(mknyszek): Support typing roots in pieces.
-					if want := 32 << 10; bigSliceElemObjects != want {
+					if want := 3 * (32 << 10); bigSliceElemObjects != want {
 						t.Errorf("expected exactly %d main.bigSliceElem objects, found %d", want, bigSliceElemObjects)
 					}
 				})
diff --git a/internal/gocore/reverse.go b/internal/gocore/reverse.go
index ee9553a..bb611b2 100644
--- a/internal/gocore/reverse.go
+++ b/internal/gocore/reverse.go
@@ -86,7 +86,7 @@
 	idx, _ := p.findObjectIndex(p.Addr(y))
 	for _, a := range p.redge[p.ridx[idx]:p.ridx[idx+1]] {
 		if a.root != nil {
-			ptr := p.readRootPtr(a.root, a.rOff)
+			ptr, _ := p.readRootPtr(a.root, a.rOff)
 			if !fn(0, a.root, a.rOff, ptr.Sub(p.Addr(y))) {
 				return
 			}
diff --git a/internal/gocore/root.go b/internal/gocore/root.go
index 2a56957..42eb067 100644
--- a/internal/gocore/root.go
+++ b/internal/gocore/root.go
@@ -68,14 +68,14 @@
 	return r
 }
 
-func (pr *Process) readRootPtr(r *Root, offset int64) core.Address {
+func (pr *Process) readRootPtr(r *Root, offset int64) (value, from core.Address) {
 	// TODO(mknyszek): Little-endian only.
 	ptrBuf := make([]byte, pr.proc.PtrSize())
-	pr.readRootAt(r, ptrBuf, offset)
+	from = pr.readRootAt(r, ptrBuf, offset)
 	if pr.proc.PtrSize() == 4 {
-		return core.Address(binary.LittleEndian.Uint32(ptrBuf))
+		return core.Address(binary.LittleEndian.Uint32(ptrBuf)), from
 	}
-	return core.Address(binary.LittleEndian.Uint64(ptrBuf))
+	return core.Address(binary.LittleEndian.Uint64(ptrBuf)), from
 }
 
 // ReadRootAt reads data out of this root. offset+len(b) must be less than r.Type.Size.
diff --git a/internal/gocore/type.go b/internal/gocore/type.go
index 1d052a2..7bab5c5 100644
--- a/internal/gocore/type.go
+++ b/internal/gocore/type.go
@@ -5,6 +5,7 @@
 package gocore
 
 import (
+	"encoding/binary"
 	"fmt"
 	"reflect"
 	"strings"
@@ -525,18 +526,13 @@
 	}
 
 	// Get typings starting at roots.
-	fr := &frameReader{p: p}
 	p.ForEachRoot(func(r *Root) bool {
-		if len(r.pieces) == 1 && r.pieces[0].kind == addrPiece {
-			addr := core.Address(r.pieces[0].value)
-			if r.Frame != nil {
-				fr.live = r.Frame.Live
-				p.typeObject(addr, r.Type, fr, add)
-			} else {
-				p.typeObject(addr, r.Type, p.proc, add)
-			}
+		rr := &rootReaderAt{p: p, r: r}
+		if r.Frame != nil {
+			rr.frameLive = r.Frame.Live
+			p.typeObject(rr, r.Type, add)
 		} else {
-			// TODO(mknyszek): Support roots broken into pieces.
+			p.typeObject(rr, r.Type, add)
 		}
 		return true
 	})
@@ -551,7 +547,7 @@
 			continue
 		}
 		for i := int64(0); i < c.r; i++ {
-			p.typeObject(c.a.Add(i*c.t.Size), c.t, p.proc, add)
+			p.typeObject(&addrReaderAt{p, c.a.Add(i * c.t.Size)}, c.t, add)
 		}
 	}
 
@@ -581,26 +577,82 @@
 	}
 }
 
-type reader interface {
-	ReadPtr(core.Address) core.Address
-	ReadInt(core.Address) int64
+// valueReaderAt is an interface that abstracts over reading logically contiguous
+// values. This is to allow contiguously reading values that are not actually stored
+// contiguously.
+type valueReaderAt interface {
+	// ReadPtrAt loads a core.Address at offset bytes from the start of the value.
+	//
+	// Returns the code.Address read and the address it was read from, if there exists
+	// such a meaningful address (that is, from might be zero if, for example, the
+	// core.Address was read out of a register).
+	ReadPtrAt(offset int64) (value, from core.Address)
+
+	// ReadIntAt loads a Go 'int' at offset bytes from the start of the value.
+	//
+	// Returns the 'int' read and the address it was read from, if there exists
+	// such a meaningful address (that is, from might be zero if, for example, the
+	// 'int' was read out of a register).
+	ReadIntAt(offset int64) (value int64, from core.Address)
+
+	// Advance returns a new valueReaderAt that starts at an offset into the value
+	// represented by this valueReaderAt. This is useful for reading, for example,
+	// struct values. Each field value in the struct can be referenced by advancing
+	// to the field offset.
+	Advance(offset int64) valueReaderAt
 }
 
-// A frameReader reads data out of a stack frame.
-// Any pointer slots marked as dead will read as nil instead of their real value.
-type frameReader struct {
-	p    *Process
-	live map[core.Address]bool
+// addrReaderAt is a trivial valueReaderAt that reads a value stored contiguously
+// in memory at some address.
+type addrReaderAt struct {
+	p *Process
+	a core.Address
 }
 
-func (fr *frameReader) ReadPtr(a core.Address) core.Address {
-	if !fr.live[a] {
-		return 0
+func (ar *addrReaderAt) ReadPtrAt(offset int64) (value, from core.Address) {
+	return ar.p.proc.ReadPtr(ar.a.Add(offset)), ar.a.Add(offset)
+}
+
+func (ar *addrReaderAt) ReadIntAt(offset int64) (value int64, from core.Address) {
+	return ar.p.proc.ReadInt(ar.a.Add(offset)), ar.a.Add(offset)
+}
+
+func (ar *addrReaderAt) Advance(offset int64) valueReaderAt {
+	nar := *ar
+	nar.a = nar.a.Add(offset)
+	return &nar
+}
+
+// rootReaderAt is a valueReaderAt that reads a value stored logically contiguously
+// in a Root.
+type rootReaderAt struct {
+	p         *Process
+	r         *Root
+	offset    int64
+	frameLive map[core.Address]bool
+}
+
+func (rr *rootReaderAt) ReadPtrAt(offset int64) (value, from core.Address) {
+	ptr, from := rr.p.readRootPtr(rr.r, offset+rr.offset)
+	if from != 0 && rr.frameLive != nil && !rr.frameLive[from] {
+		return 0, from
 	}
-	return fr.p.proc.ReadPtr(a)
+	return ptr, from
 }
-func (fr *frameReader) ReadInt(a core.Address) int64 {
-	return fr.p.proc.ReadInt(a)
+
+func (rr *rootReaderAt) ReadIntAt(offset int64) (value int64, from core.Address) {
+	var i [8]byte
+	from = rr.p.readRootAt(rr.r, i[:], offset+rr.offset)
+	if rr.p.proc.PtrSize() == 4 {
+		return int64(binary.LittleEndian.Uint32(i[:])), from
+	}
+	return int64(binary.LittleEndian.Uint64(i[:])), from
+}
+
+func (rr *rootReaderAt) Advance(offset int64) valueReaderAt {
+	nrr := *rr
+	nrr.offset += offset
+	return &nrr
 }
 
 func methodFromMethodValueWrapper(name string) (string, bool) {
@@ -616,7 +668,7 @@
 // typeObject takes an address and a type for the data at that address.
 // For each pointer it finds in the memory at that address, it calls add with the pointer
 // and the type + repeat count of the thing that it points to.
-func (p *Process) typeObject(a core.Address, t *Type, r reader, add func(core.Address, *Type, int64)) {
+func (p *Process) typeObject(r valueReaderAt, t *Type, add func(core.Address, *Type, int64)) {
 	ptrSize := p.proc.PtrSize()
 
 	switch t.Kind {
@@ -631,13 +683,12 @@
 		// a dead eface variable on the stack: the data field
 		// will return nil because it's dead, but the type pointer
 		// will likely be bogus.
-		data := a.Add(ptrSize)
-		if r.ReadPtr(data) == 0 { // nil interface
+		if value, _ := r.ReadPtrAt(ptrSize); value == 0 { // nil interface
 			return
 		}
 		// Use the type word to determine the type
 		// of the pointed-to object.
-		typPtr := r.ReadPtr(a)
+		typPtr, data := r.ReadPtrAt(0)
 		if typPtr == 0 { // nil interface
 			return
 		}
@@ -651,7 +702,8 @@
 			// Indirect interface: the interface introduced a new
 			// level of indirection, not reflected in the type.
 			// Read through it.
-			add(r.ReadPtr(data), typ, 1)
+			value, _ := r.ReadPtrAt(ptrSize)
+			add(value, typ, 1)
 			return
 		}
 
@@ -672,27 +724,24 @@
 					}
 				}
 			}
-			if directTyp.Kind == KindPtr {
-				p.typeObject(data, directTyp, r, add)
-				break
-			}
-			if directTyp.Kind == KindFunc {
-				add(data, directTyp, 1)
+			if directTyp.Kind == KindPtr || directTyp.Kind == KindFunc {
+				p.typeObject(r.Advance(ptrSize), directTyp, add)
 				break
 			}
 			panic(fmt.Sprintf("type of direct interface, originally %s (kind %s), isn't a pointer: %s (kind %s)", typ, typ.Kind, directTyp, directTyp.Kind))
 		}
 	case KindString:
-		ptr := r.ReadPtr(a)
-		len := r.ReadInt(a.Add(ptrSize))
+		ptr, _ := r.ReadPtrAt(0)
+		len, _ := r.ReadIntAt(ptrSize)
 		add(ptr, t.Elem, len)
 	case KindSlice:
-		ptr := r.ReadPtr(a)
-		cap := r.ReadInt(a.Add(2 * ptrSize))
+		ptr, _ := r.ReadPtrAt(0)
+		cap, _ := r.ReadIntAt(2 * ptrSize)
 		add(ptr, t.Elem, cap)
 	case KindPtr:
 		if t.Elem != nil { // unsafe.Pointer has a nil Elem field.
-			add(r.ReadPtr(a), t.Elem, 1)
+			ptr, _ := r.ReadPtrAt(0)
+			add(ptr, t.Elem, 1)
 		}
 	case KindFunc:
 		// The referent is a closure. We don't know much about the
@@ -700,7 +749,7 @@
 		// The runtime._type we want exists in the binary (for all
 		// heap-allocated closures, anyway) but it would be hard to find
 		// just given the pc.
-		closure := r.ReadPtr(a)
+		closure, _ := r.ReadPtrAt(0)
 		if closure == 0 {
 			break
 		}
@@ -716,7 +765,7 @@
 			// TODO: better value for size?
 			f.closure = ft
 		}
-		p.typeObject(closure, ft, r, add)
+		p.typeObject(&addrReaderAt{p, closure}, ft, add)
 
 		// Handle the special case for method value.
 		// It's a single-entry closure laid out like {pc uintptr, x T}.
@@ -726,7 +775,7 @@
 				for _, v := range p.dwarfVars[mf] {
 					if v.kind == dwarfParam {
 						ptr := closure.Add(p.proc.PtrSize())
-						p.typeObject(ptr, v.typ, r, add)
+						p.typeObject(&addrReaderAt{p, ptr}, v.typ, add)
 						break
 					}
 				}
@@ -735,7 +784,7 @@
 	case KindArray:
 		n := t.Elem.Size
 		for i := int64(0); i < t.Count; i++ {
-			p.typeObject(a.Add(i*n), t.Elem, r, add)
+			p.typeObject(r.Advance(i*n), t.Elem, add)
 		}
 	case KindStruct:
 		if strings.HasPrefix(t.Name, "hash<") {
@@ -746,11 +795,12 @@
 			var n int64
 			for _, f := range t.Fields {
 				if f.Name == "buckets" {
-					bPtr = p.proc.ReadPtr(a.Add(f.Off))
+					bPtr, _ = r.ReadPtrAt(f.Off)
 					bTyp = f.Type.Elem
 				}
 				if f.Name == "B" {
-					n = int64(1) << p.proc.ReadUint8(a.Add(f.Off))
+					shift, _ := r.ReadIntAt(f.Off)
+					n = int64(1) << uint8(shift)
 				}
 			}
 			add(bPtr, bTyp, n)
@@ -759,13 +809,13 @@
 		for _, f := range t.Fields {
 			// hchan.buf(in chan) is an unsafe.pointer to an [dataqsiz]elemtype.
 			if strings.HasPrefix(t.Name, "hchan<") && f.Name == "buf" && f.Type.Kind == KindPtr {
-				elemType := p.proc.ReadPtr(a.Add(t.field("elemtype").Off))
-				bufPtr := r.ReadPtr(a.Add(t.field("buf").Off))
+				elemType, _ := r.ReadPtrAt(t.field("elemtype").Off)
+				bufPtr, _ := r.ReadPtrAt(t.field("buf").Off)
 				rTyp := p.runtimeType2Type(elemType, 0)
-				dataqsiz := p.proc.ReadUintptr(a.Add(t.field("dataqsiz").Off))
+				dataqsiz, _ := r.ReadPtrAt(t.field("dataqsiz").Off)
 				add(bufPtr, rTyp, int64(dataqsiz))
 			}
-			p.typeObject(a.Add(f.Off), f.Type, r, add)
+			p.typeObject(r.Advance(f.Off), f.Type, add)
 		}
 	default:
 		panic(fmt.Sprintf("unknown type kind %s\n", t.Kind))