func (q *Query) ReachableValues() *Query {
	if q.outType != typeofValue && q.outType != typeofVar {
		panic(fmt.Sprintf("cannot compute ReachableValues on elements of type %s, expected Value or Var", q.outType))
	}
	if q.outType == typeofVar {
		q = q.Map(varToValue)
	}
	return &Query{
		op:      queryReachableValues,
		inner:   q,
		outType: typeofValue,
	}
}
ReachableValues enumerates all values reachable from the input set
of values. Reachability is transitive and reflexive. The input
collection must contain elements of type Value or Var. The types are:

in:  Query producing Collection<Value or Var>
out: Query producing Collection<Value>

TODO: explain subtleties of repeated visits; in particular, we might
reach an interior pointer of an object, only to later reach the full
object at some later time; this is sort-of a repated visit of an address.
When walking heap objects with GCObjectType values, we can guarantee that
this won't happen, since the heap walk will always visit full objects.

TODO: options?
- visit heap objects only (i.e., not GC roots)
- edge filter (apply "fn(srcVal, srcPtr, dstVal Value) bool" to each edge)
package corefile
import (
	"fmt"
	"reflect"
	"sync"
)
var (
	typeofBool               = reflect.TypeOf(true)
	typeofValue              = reflect.TypeOf(Value{})
	typeofVar                = reflect.TypeOf(Var{})
	typeofVarSet             = reflect.TypeOf(VarSet{})
	typeofGoroutine          = reflect.TypeOf(Goroutine{})
	typeofStackFrame         = reflect.TypeOf(StackFrame{})
	typeofProgram            = reflect.TypeOf(Program{})
	typeofQueryGrouping      = reflect.TypeOf(QueryGrouping{})
	typeofRuntimeFinalizers  = reflect.TypeOf(runtimeFinalizers{})
	typeofRuntimeGCRoots     = reflect.TypeOf(runtimeGCRoots{})
	typeofRuntimeHeapObjects = reflect.TypeOf(runtimeHeapObjects{})
)
type queryOp int
const (
	queryInput queryOp = iota
	queryFlatten
	queryWhere
	queryMap
	queryReduce
	queryExists
	queryGroupBy
	queryGroupByAndReduce
	queryReachableValues
)
func (op queryOp) String() string {
	switch op {
	case queryInput:
		return "Input"
	case queryFlatten:
		return "Flatten"
	case queryWhere:
		return "Where"
	case queryMap:
		return "Map"
	case queryReduce:
		return "Reduce"
	case queryExists:
		return "Exists"
	case queryGroupBy:
		return "GroupBy"
	case queryGroupByAndReduce:
		return "GroupByAndReduce"
	case queryReachableValues:
		return "ReachableValues"
	default:
		return "<unknown>"
	}
}
func varToValue(v Var) Value {
	return v.Value
}
type Query struct {
	op        queryOp
	inner     *Query
	arg, arg2 interface{}
	outType     reflect.Type
	outKeyType  reflect.Type
	outElemType reflect.Type
	inputFromReduction bool
}
func NewQuery(x interface{}) *Query {
	switch x := x.(type) {
	case *StackFrame:
		return NewQuery(x.LocalVars).Map(varToValue)
	case *Goroutine:
		var q *Query
		for sf := x.Stack; sf != nil; sf = sf.Caller {
			q0 := NewQuery(sf)
			if q == nil {
				q = q0
			} else {
				q = q.Flatten(q0)
			}
		}
		return q
	case *Program:
		return NewQuery(x.GlobalVars)
		q0 := NewQuery(x.GlobalVars).Map(varToValue)
		q1 := NewQuery(x.Goroutines)
		return q0.Flatten(q1)  
	}
	xt := reflect.TypeOf(x)
	if xt.Kind() == reflect.Array || xt.Kind() == reflect.Slice {
		var q *Query
		xv := reflect.ValueOf(x)
		for k := 0; k < xv.Len(); k++ {
			q0 := NewQuery(xv.Index(k).Interface())
			if q == nil {
				q = q0
			} else {
				q = q.Flatten(q0)
			}
		}
		return q
	}
	q := &Query{
		op:  queryInput,
		arg: x,
	}
	switch xt {
	case typeofValue, reflect.PtrTo(typeofValue),
		typeofRuntimeFinalizers, typeofRuntimeGCRoots, typeofRuntimeHeapObjects:
		q.outType = typeofValue
	case typeofVar, reflect.PtrTo(typeofVar), reflect.PtrTo(typeofVarSet):
		q.outType = typeofVar
	default:
		panic(fmt.Sprintf("bad type %T for NewQuery", x))
	}
	return q
}
func (q *Query) Flatten(arg *Query) *Query {
	if q.outType != arg.outType {
		panic(fmt.Sprintf("cannot flatten collections with elements of type %s and %s", q.outType, arg.outType))
	}
	return &Query{
		op:      queryFlatten,
		inner:   q,
		arg:     arg,
		outType: q.outType,
	}
}
func (q *Query) Where(fn interface{}) *Query {
	t := reflect.TypeOf(fn)
	if t.Kind() != reflect.Func || t.NumIn() != 1 || t.NumOut() != 1 || t.IsVariadic() || t.In(0) != q.outType || t.Out(0) != typeofBool {
		panic(fmt.Sprintf("bad type %T for fn, want: func(%s) bool", fn, q.outType))
	}
	return &Query{
		op:      queryWhere,
		inner:   q,
		arg:     fn,
		outType: t.In(0),
	}
}
func (q *Query) Map(fn interface{}) *Query {
	t := reflect.TypeOf(fn)
	if t.Kind() != reflect.Func || t.NumIn() != 1 || t.NumOut() != 1 || t.IsVariadic() || t.In(0) != q.outType {
		panic(fmt.Sprintf("bad type %T for fn, want: func(%s) T", fn, q.outType))
	}
	return &Query{
		op:      queryMap,
		inner:   q,
		arg:     fn,
		outType: t.Out(0),
	}
}
func (q *Query) Reduce(fn interface{}) *Query {
	t := reflect.TypeOf(fn)
	if t.Kind() != reflect.Func || t.NumIn() != 2 || t.NumOut() != 1 || t.IsVariadic() ||
		t.In(0) != q.outType || t.In(1) != q.outType || t.Out(0) != q.outType {
		panic(fmt.Sprintf("bad type %T for fn, want: func(%s, %s) %s", fn, q.outType, q.outType, q.outType))
	}
	return &Query{
		op:      queryReduce,
		inner:   q,
		arg:     fn,
		outType: q.outType,
	}
}
func (q *Query) Exists(fn interface{}) *Query {
	t := reflect.TypeOf(fn)
	if t.Kind() != reflect.Func || t.NumIn() != 1 || t.NumOut() != 1 || t.IsVariadic() || t.In(0) != q.outType || t.Out(0) != typeofBool {
		panic(fmt.Sprintf("bad type %T for fn, want: func(%s) bool", fn, q.outType))
	}
	return &Query{
		op:      queryExists,
		inner:   q,
		arg:     fn,
		outType: typeofBool,
	}
}
func (q *Query) GroupBy(fn interface{}) *Query {
	return q.groupByAndReduce(queryGroupBy, fn, nil)
}
func (q *Query) GroupByAndReduce(fn interface{}, reduceFn interface{}) *Query {
	return q.groupByAndReduce(queryGroupByAndReduce, fn, reduceFn)
}
func (q *Query) groupByAndReduce(op queryOp, fn interface{}, reduceFn interface{}) *Query {
	t := reflect.TypeOf(fn)
	if t.Kind() != reflect.Func || t.NumIn() != 1 || (t.NumOut() != 1 && t.NumOut() != 2) || t.IsVariadic() || t.In(0) != q.outType {
		panic(fmt.Sprintf("bad type %T for fn, want: func(%s, T) T", fn, q.outType))
	}
	newq := &Query{
		op:         op,
		inner:      q,
		arg:        fn,
		arg2:       reduceFn,
		outType:    typeofQueryGrouping,
		outKeyType: t.Out(0),
	}
	if t.NumOut() == 1 {
		newq.outElemType = q.outType
	} else {
		newq.outElemType = t.Out(1)
	}
	if op == queryGroupByAndReduce {
		t := reflect.TypeOf(reduceFn)
		if t.Kind() != reflect.Func || t.NumIn() != 2 || t.NumOut() != 1 || t.IsVariadic() ||
			t.In(0) != newq.outElemType || t.In(1) != newq.outElemType || t.Out(0) != newq.outElemType {
			panic(fmt.Sprintf("bad type %T for fn, want: func(%s, %s) %s", fn, newq.outElemType, newq.outElemType, newq.outElemType))
		}
	}
	return newq
}
type QueryGrouping struct {
	Key interface{}
	Elems *Query
}