go.tools/pointer: inclusion-based pointer analysis for Go.

Suggested reading order:
- doc.go
- api.go, analysis.go, callgraph.go, labels.go
- print.go, util.go
- gen.go
- solve.go
- pointer_test.go, testdata/*
- intrinsics.go (none are implemented yet)

R=dannyb, gri, crawshaw, 0xjnml
CC=golang-dev
https://golang.org/cl/10618043
diff --git a/pointer/TODO b/pointer/TODO
new file mode 100644
index 0000000..0c9daec
--- /dev/null
+++ b/pointer/TODO
@@ -0,0 +1,31 @@
+-*- text -*-
+
+Pointer analysis to-do list
+===========================
+
+CONSTRAINT GENERATION:
+- reflection intrinsics
+- unsafe.Pointer conversions.  Three options:
+  1) unsoundly (but type-safely) treat p=unsafe.Pointer(x) conversions as
+     allocations, losing aliases.  This is what's currently implemented.
+  2) unsoundly (but type-safely) treat p=unsafe.Pointer(x) and T(p)
+     conversions as interface boxing and unboxing operations.  
+     This may preserve some aliasing relations at little cost.
+  3) soundly track physical field offsets.  (Summarise dannyb's email here.)
+     A downside is that we can't keep the identity field of struct
+     allocations that identifies the object.    
+
+PRESOLVER OPTIMISATIONS
+- use HVN, HRU, LE, PE, HCD, LCD.
+
+SOLVER:
+- use BDDs and/or sparse bitvectors for ptsets
+- use sparse bitvectors for graph edges
+- use BDD-based relational composition for e.g. offset computations.
+- experiment with different worklist algorithms:
+   priority queue (solver visit-time order)
+   red-black tree (node id order)
+   double-ended queue (insertion order)
+   fast doubly-linked list (See Zhanh et al PLDI'13)
+     (insertion order with fast membership test)
+  dannyb recommends sparse bitmap.
diff --git a/pointer/analysis.go b/pointer/analysis.go
new file mode 100644
index 0000000..e00cc9f
--- /dev/null
+++ b/pointer/analysis.go
@@ -0,0 +1,255 @@
+package pointer
+
+// This file defines the entry points into the pointer analysis.
+
+import (
+	"fmt"
+	"go/token"
+	"io"
+	"os"
+
+	"code.google.com/p/go.tools/go/types"
+	"code.google.com/p/go.tools/ssa"
+)
+
+// nodeid denotes a node.
+// It is an index within analysis.nodes.
+// We use small integers, not *node pointers, for many reasons:
+// - they are smaller on 64-bit systems.
+// - sets of them can be represented compactly in bitvectors or BDDs.
+// - order matters; a field offset can be computed by simple addition.
+type nodeid uint32
+
+// node.flags bitmask values.
+const (
+	ntObject    = 1 << iota // start of an object (addressable memory location)
+	ntInterface             // conctype node of interface object (=> ntObject)
+	ntFunction              // identity node of function object (=> ntObject)
+)
+
+// A node is an equivalence class of memory locations.
+// Nodes may be pointers, pointed-to locations, neither, or both.
+type node struct {
+	// flags is a bitset of the node type (nt*) flags defined above.
+	flags uint32
+
+	// Number of following words belonging to the same "object" allocation.
+	// (Set by endObject.)  Zero for all other nodes.
+	size uint32
+
+	// The type of the field denoted by this node.  Non-aggregate,
+	// unless this is an iface.conctype node (i.e. the thing
+	// pointed to by an interface) in which case typ is that type.
+	typ types.Type
+
+	// data holds additional attributes of this node, depending on
+	// its flags.
+	//
+	// If ntObject is set, data is the ssa.Value of the
+	// instruction that allocated this memory, or nil if it was
+	// implicit.
+	//
+	// Special cases:
+	// - If ntInterface is also set, data will be a *ssa.MakeInterface.
+	// - If ntFunction is also set, this node is the first word of a
+	//   function block, and data is a *cgnode (not an ssa.Value)
+	//   representing this function.
+	data interface{}
+
+	// subelement indicates which directly embedded subelement of
+	// an object of aggregate type (struct, tuple, array) this is.
+	subelement *fieldInfo // e.g. ".a.b[*].c"
+
+	// Points-to sets.
+	pts     nodeset // points-to set of this node
+	prevPts nodeset // pts(n) in previous iteration (for difference propagation)
+
+	// Graph edges
+	copyTo nodeset // simple copy constraint edges
+
+	// Complex constraints attached to this node (x).
+	// - *loadConstraint       y=*x
+	// - *offsetAddrConstraint y=&x.f or y=&x[0]
+	// - *storeConstraint      *x=z
+	// - *typeAssertConstraint y=x.(T)
+	// - *invokeConstraint     y=x.f(params...)
+	complex constraintset
+}
+
+type constraint interface {
+	String() string
+
+	// Called by solver to prepare a constraint, e.g. to
+	// - initialize a points-to set (addrConstraint).
+	// - attach it to a pointer node (complex constraints).
+	init(a *analysis)
+
+	// solve is called for complex constraints when the pts for
+	// the node to which they are attached has changed.
+	solve(a *analysis, n *node, delta nodeset)
+}
+
+// dst = &src
+// pts(dst) ⊇ {src}
+// A base constraint used to initialize the solver's pt sets
+type addrConstraint struct {
+	dst nodeid
+	src nodeid
+}
+
+// dst = src
+// A simple constraint represented directly as a copyTo graph edge.
+type copyConstraint struct {
+	dst nodeid
+	src nodeid
+}
+
+// dst = src[offset]
+// A complex constraint attached to src (the pointer)
+type loadConstraint struct {
+	offset uint32
+	dst    nodeid
+	src    nodeid
+}
+
+// dst[offset] = src
+// A complex constraint attached to dst (the pointer)
+type storeConstraint struct {
+	offset uint32
+	dst    nodeid
+	src    nodeid
+}
+
+// dst = &src.f  or  dst = &src[0]
+// A complex constraint attached to dst (the pointer)
+type offsetAddrConstraint struct {
+	offset uint32
+	dst    nodeid
+	src    nodeid
+}
+
+// dst = src.(typ)
+// A complex constraint attached to src (the interface).
+type typeAssertConstraint struct {
+	typ types.Type
+	dst nodeid
+	src nodeid
+}
+
+// src.method(params...)
+// A complex constraint attached to iface.
+type invokeConstraint struct {
+	method *types.Func // the abstract method
+	iface  nodeid      // the interface
+	params nodeid      // the first parameter in the params/results block
+}
+
+// An analysis instance holds the state of a single pointer analysis problem.
+type analysis struct {
+	config          *Config                     // the client's control/observer interface
+	prog            *ssa.Program                // the program being analyzed
+	log             io.Writer                   // log stream; nil to disable
+	panicNode       nodeid                      // sink for panic, source for recover
+	nodes           []*node                     // indexed by nodeid
+	flattenMemo     map[types.Type][]*fieldInfo // memoization of flatten()
+	constraints     []constraint                // set of constraints
+	callsites       []*callsite                 // all callsites
+	genq            []*cgnode                   // queue of functions to generate constraints for
+	intrinsics      map[*ssa.Function]intrinsic // non-nil values are summaries for intrinsic fns
+	reflectValueObj types.Object                // type symbol for reflect.Value (if present)
+	reflectRtypeObj types.Object                // type symbol for reflect.rtype (if present)
+	reflectRtype    *types.Pointer              // *reflect.rtype
+	funcObj         map[*ssa.Function]nodeid    // default function object for each func
+	probes          map[*ssa.CallCommon]nodeid  // maps call to print() to argument variable
+	valNode         map[ssa.Value]nodeid        // node for each ssa.Value
+	work            worklist                    // solver's worklist
+}
+
+func (a *analysis) warnf(pos token.Pos, format string, args ...interface{}) {
+	if Warn := a.config.Warn; Warn != nil {
+		Warn(pos, format, args...)
+	} else {
+		fmt.Fprintf(os.Stderr, "%s: warning: ", a.prog.Fset.Position(pos))
+		fmt.Fprintf(os.Stderr, format, args...)
+		fmt.Fprintln(os.Stderr)
+	}
+}
+
+// Analyze runs the pointer analysis with the scope and options
+// specified by config, and returns the (synthetic) root of the callgraph.
+//
+func Analyze(config *Config) CallGraphNode {
+	a := &analysis{
+		config:      config,
+		log:         config.Log,
+		prog:        config.prog(),
+		valNode:     make(map[ssa.Value]nodeid),
+		flattenMemo: make(map[types.Type][]*fieldInfo),
+		intrinsics:  make(map[*ssa.Function]intrinsic),
+		funcObj:     make(map[*ssa.Function]nodeid),
+		probes:      make(map[*ssa.CallCommon]nodeid),
+		work:        makeMapWorklist(),
+	}
+
+	if reflect := a.prog.PackagesByPath["reflect"]; reflect != nil {
+		a.reflectValueObj = reflect.Object.Scope().Lookup("Value")
+		a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
+		a.reflectRtype = types.NewPointer(a.reflectRtypeObj.Type())
+	}
+
+	if false {
+		a.log = os.Stderr // for debugging crashes; extremely verbose
+	}
+
+	if a.log != nil {
+		fmt.Fprintln(a.log, "======== NEW ANALYSIS ========")
+	}
+
+	root := a.generate()
+
+	// ---------- Presolver ----------
+
+	// TODO(adonovan): opt: presolver optimisations.
+
+	// ---------- Solver ----------
+
+	a.solve()
+
+	if a.log != nil {
+		// Dump solution.
+		for i, n := range a.nodes {
+			if n.pts != nil {
+				fmt.Fprintf(a.log, "pts(n%d) = %s : %s\n", i, n.pts, n.typ)
+			}
+		}
+	}
+
+	// Notify the client of the callsites if they're interested.
+	if CallSite := a.config.CallSite; CallSite != nil {
+		for _, site := range a.callsites {
+			CallSite(site)
+		}
+	}
+
+	Call := a.config.Call
+	for _, site := range a.callsites {
+		for nid := range a.nodes[site.targets].pts {
+			cgn := a.nodes[nid].data.(*cgnode)
+
+			// Notify the client of the call graph, if
+			// they're interested.
+			if Call != nil {
+				Call(site, site.caller, cgn)
+			}
+
+			// Warn about calls to non-intrinsic external functions.
+
+			if fn := cgn.fn; fn.Blocks == nil && a.findIntrinsic(fn) == nil {
+				a.warnf(site.Pos(), "unsound call to unknown intrinsic: %s", fn)
+				a.warnf(fn.Pos(), " (declared here)")
+			}
+		}
+	}
+
+	return root
+}
diff --git a/pointer/api.go b/pointer/api.go
new file mode 100644
index 0000000..be594d1
--- /dev/null
+++ b/pointer/api.go
@@ -0,0 +1,232 @@
+package pointer
+
+import (
+	"fmt"
+	"go/token"
+	"io"
+
+	"code.google.com/p/go.tools/go/types/typemap"
+	"code.google.com/p/go.tools/ssa"
+)
+
+type Config struct {
+	// -------- Scope of the analysis --------
+
+	// Clients must provide the analysis with at least one package defining a main() function.
+	Mains []*ssa.Package // set of 'main' packages to analyze
+	root  *ssa.Function  // synthetic analysis root
+
+	// -------- Optional callbacks invoked by the analysis --------
+
+	// Call is invoked for each discovered call-graph edge.  The
+	// call-graph is a multigraph over CallGraphNodes with edges
+	// labelled by the CallSite that gives rise to the edge.
+	// Clients that wish to construct a call graph may provide
+	// CallGraph.AddEdge here.
+	//
+	// The callgraph may be context-sensitive, i.e. it may
+	// distinguish separate calls to the same function depending
+	// on the context.
+	//
+	Call func(site CallSite, caller, callee CallGraphNode)
+
+	// CallSite is invoked for each call-site encountered in the
+	// program.
+	//
+	// The callgraph may be context-sensitive, i.e. it may
+	// distinguish separate calls to the same function depending
+	// on the context.
+	//
+	CallSite func(site CallSite)
+
+	// Warn is invoked for each warning encountered by the analysis,
+	// e.g. unknown external function, unsound use of unsafe.Pointer.
+	// pos may be zero if the position is not known.
+	Warn func(pos token.Pos, format string, args ...interface{})
+
+	// Print is invoked during the analysis for each discovered
+	// call to the built-in print(x).
+	//
+	// Pointer p may be saved until the analysis is complete, at
+	// which point its methods provide access to the analysis
+	// (The result of callings its methods within the Print
+	// callback is undefined.)  p is nil if x is non-pointerlike.
+	//
+	// TODO(adonovan): this was a stop-gap measure for identifing
+	// arbitrary expressions of interest in the tests.  Now that
+	// ssa.ValueForExpr exists, we should use that instead.
+	//
+	Print func(site *ssa.CallCommon, p Pointer)
+
+	// The client populates QueryValues with {v, nil} for each
+	// ssa.Value v of interest.  The pointer analysis will
+	// populate the corresponding map value when it creates the
+	// pointer variable for v.  Upon completion the client can
+	// inspect the map for the results.
+	//
+	// If a Value belongs to a function that the analysis treats
+	// context-sensitively, the corresponding slice may have
+	// multiple Pointers, one per distinct context.
+	// Use PointsToCombined to merge them.
+	//
+	// TODO(adonovan): separate the keys set (input) from the
+	// key/value associations (result) and perhaps return the
+	// latter from Analyze().
+	//
+	QueryValues map[ssa.Value][]Pointer
+
+	// -------- Other configuration options --------
+
+	// If Log is non-nil, a log messages are written to it.
+	// Logging is extremely verbose.
+	Log io.Writer
+}
+
+func (c *Config) prog() *ssa.Program {
+	for _, main := range c.Mains {
+		return main.Prog
+	}
+	panic("empty scope")
+}
+
+// A Pointer is an equivalence class of pointerlike values.
+//
+// TODO(adonovan): add a method
+//    Context() CallGraphNode
+// for pointers corresponding to local variables,
+//
+type Pointer interface {
+	// PointsTo returns the points-to set of this pointer.
+	PointsTo() PointsToSet
+
+	// MayAlias reports whether the receiver pointer may alias
+	// the argument pointer.
+	MayAlias(Pointer) bool
+
+	String() string
+}
+
+// A PointsToSet is a set of labels (locations or allocations).
+//
+type PointsToSet interface {
+	// PointsTo returns the set of labels that this points-to set
+	// contains.
+	Labels() []*Label
+
+	// Intersects reports whether this points-to set and the
+	// argument points-to set contain common members.
+	Intersects(PointsToSet) bool
+
+	// If this PointsToSet came from a Pointer of interface kind,
+	// ConcreteTypes returns the set of concrete types the
+	// interface may contain.
+	//
+	// The result is a mapping whose keys are the concrete types
+	// to which this interface may point.  For each pointer-like
+	// key type, the corresponding map value is a set of pointer
+	// abstractions of that concrete type, represented as a
+	// []Pointer slice.  Use PointsToCombined to merge them.
+	ConcreteTypes() *typemap.M
+}
+
+// Union returns the set containing all the elements of each set in sets.
+func Union(sets ...PointsToSet) PointsToSet {
+	var union ptset
+	for _, set := range sets {
+		set := set.(ptset)
+		union.a = set.a
+		union.pts.addAll(set.pts)
+	}
+	return union
+}
+
+// PointsToCombined returns the combined points-to set of all the
+// specified pointers.
+func PointsToCombined(ptrs []Pointer) PointsToSet {
+	var ptsets []PointsToSet
+	for _, ptr := range ptrs {
+		ptsets = append(ptsets, ptr.PointsTo())
+	}
+	return Union(ptsets...)
+}
+
+// ---- PointsToSet public interface
+
+type ptset struct {
+	a   *analysis // may be nil if pts is nil
+	pts nodeset
+}
+
+func (s ptset) Labels() []*Label {
+	var labels []*Label
+	for l := range s.pts {
+		// Scan back to the previous object start.
+		for i := l; i >= 0; i-- {
+			n := s.a.nodes[i]
+			if n.flags&ntObject != 0 {
+				// TODO(adonovan): do bounds-check against n.size.
+				var v ssa.Value
+				if n.flags&ntFunction != 0 {
+					v = n.data.(*cgnode).fn
+				} else {
+					v = n.data.(ssa.Value)
+					// TODO(adonovan): what if v is nil?
+				}
+				labels = append(labels, &Label{
+					Value:      v,
+					subelement: s.a.nodes[l].subelement,
+				})
+				break
+			}
+		}
+	}
+	return labels
+}
+
+func (s ptset) ConcreteTypes() *typemap.M {
+	var tmap typemap.M // default hasher // TODO(adonovan): opt: memoize per analysis
+	for ifaceObjId := range s.pts {
+		if s.a.nodes[ifaceObjId].flags&ntInterface == 0 {
+			// ConcreteTypes called on non-interface PT set.
+			continue // shouldn't happen
+		}
+		v, tconc := s.a.interfaceValue(ifaceObjId)
+		prev, _ := tmap.At(tconc).([]Pointer)
+		tmap.Set(tconc, append(prev, ptr{s.a, v}))
+	}
+	return &tmap
+}
+
+func (x ptset) Intersects(y_ PointsToSet) bool {
+	y := y_.(ptset)
+	for l := range x.pts {
+		if _, ok := y.pts[l]; ok {
+			return true
+		}
+	}
+	return false
+}
+
+// ---- Pointer public interface
+
+// ptr adapts a node to the Pointer interface.
+type ptr struct {
+	a *analysis
+	n nodeid // non-zero
+}
+
+func (p ptr) String() string {
+	return fmt.Sprintf("n%d", p.n)
+}
+
+func (p ptr) PointsTo() PointsToSet {
+	return ptset{p.a, p.a.nodes[p.n].pts}
+}
+
+func (p ptr) MayAlias(q Pointer) bool {
+	return p.PointsTo().Intersects(q.PointsTo())
+}
+
+func (p ptr) ConcreteTypes() *typemap.M {
+	return p.PointsTo().ConcreteTypes()
+}
diff --git a/pointer/callgraph.go b/pointer/callgraph.go
new file mode 100644
index 0000000..de8489e
--- /dev/null
+++ b/pointer/callgraph.go
@@ -0,0 +1,114 @@
+package pointer
+
+import (
+	"fmt"
+	"go/token"
+
+	"code.google.com/p/go.tools/ssa"
+)
+
+// TODO(adonovan): move the CallGraph, CallGraphNode, CallSite types
+// into a separate package 'callgraph', and make them pure interfaces
+// capable of supporting several implementations (context-sensitive
+// and insensitive PTA, RTA, etc).
+
+// ---------- CallGraphNode ----------
+
+// A CallGraphNode is a context-sensitive representation of a node in
+// the callgraph.  In other words, there may be multiple nodes
+// representing a single *Function, depending on the contexts in which
+// it is called.  The identity of the node is therefore important.
+//
+type CallGraphNode interface {
+	Func() *ssa.Function // the function this node represents
+	String() string      // diagnostic description of this callgraph node
+}
+
+type cgnode struct {
+	fn  *ssa.Function
+	obj nodeid // start of this contour's object block
+}
+
+func (n *cgnode) Func() *ssa.Function {
+	return n.fn
+}
+
+func (n *cgnode) String() string {
+	return fmt.Sprintf("cg%d:%s", n.obj, n.fn)
+}
+
+// ---------- CallSite ----------
+
+// A CallSite is a context-sensitive representation of a function call
+// site in the program.
+//
+type CallSite interface {
+	Caller() CallGraphNode // the enclosing context of this call
+	Pos() token.Pos        // source position; token.NoPos for synthetic calls
+	Description() string   // UI description of call kind; see (*ssa.CallCommon).Description
+	String() string        // diagnostic description of this callsite
+}
+
+// A callsite represents a single function or method callsite within a
+// function.  callsites never represent calls to built-ins; they are
+// handled as intrinsics.
+//
+type callsite struct {
+	caller  *cgnode             // the origin of the call
+	targets nodeid              // pts(targets) contains identities of all called functions.
+	instr   ssa.CallInstruction // optional call instruction; provides IsInvoke, position, etc.
+	pos     token.Pos           // position, if instr == nil, i.e. synthetic callsites.
+}
+
+// Caller returns the node in the callgraph from which this call originated.
+func (c *callsite) Caller() CallGraphNode {
+	return c.caller
+}
+
+// Description returns a description of this kind of call, in the
+// manner of ssa.CallCommon.Description().
+//
+func (c *callsite) Description() string {
+	if c.instr != nil {
+		return c.instr.Common().Description()
+	}
+	return "synthetic function call"
+}
+
+// Pos returns the source position of this callsite, or token.NoPos if implicit.
+func (c *callsite) Pos() token.Pos {
+	if c.instr != nil {
+		return c.instr.Pos()
+	}
+	return c.pos
+}
+
+func (c *callsite) String() string {
+	// TODO(adonovan): provide more info, e.g. target of static
+	// call, arguments, location.
+	return c.Description()
+}
+
+// ---------- CallGraph ----------
+
+// CallGraph is a forward directed graph of functions labelled by an
+// arbitrary site within the caller.
+//
+// CallGraph.AddEdge may be used as the Context.Call callback for
+// clients that wish to construct a call graph.
+//
+// TODO(adonovan): this is just a starting point.  Add options to
+// control whether we record no callsite, an arbitrary callsite, or
+// all callsites for a given graph edge.  Also, this could live in
+// another package since it's just a client utility.
+//
+type CallGraph map[CallGraphNode]map[CallGraphNode]CallSite
+
+func (cg CallGraph) AddEdge(site CallSite, caller, callee CallGraphNode) {
+	callees := cg[caller]
+	if callees == nil {
+		callees = make(map[CallGraphNode]CallSite)
+		cg[caller] = callees
+	}
+	callees[callee] = site // save an arbitrary site
+}
diff --git a/pointer/doc.go b/pointer/doc.go
new file mode 100644
index 0000000..6eabfbe
--- /dev/null
+++ b/pointer/doc.go
@@ -0,0 +1,391 @@
+/*
+
+Package pointer implements Andersen's analysis, an inclusion-based
+pointer analysis algorithm first described in (Andersen, 1994).
+
+The implementation is similar to that described in (Pearce et al,
+PASTE'04).  Unlike many algorithms which interleave constraint
+generation and solving, constructing the callgraph as they go, this
+implementation has a strict phase ordering: generation before solving.
+Only simple (copy) constraints may be generated during solving.  This
+improves the traction of presolver optimisations, but imposes certain
+restrictions, e.g. potential context sensitivity is limited since all
+variants must be created a priori.
+
+We intend to add various presolving optimisations such as Pointer and
+Location Equivalence from (Hardekopf & Lin, SAS '07) and solver
+optimisatisions such as Hybrid- and Lazy- Cycle Detection from
+(Hardekopf & Lin, PLDI'07),
+
+
+TERMINOLOGY
+
+We occasionally use C's x->f notation to distinguish the case where x
+is a struct pointer from x.f where is a struct value.
+
+
+NODES
+
+Nodes are the key datastructure of the analysis, and have a dual role:
+they represent both constraint variables (equivalence classes of
+pointers) and members of points-to sets (things that can be pointed
+at, i.e. "labels").
+
+Nodes are naturally numbered.  The numbering enables compact
+representations of sets of nodes such as bitvectors or BDDs; and the
+ordering enables a very cheap way to group related nodes together.
+(For example, passing n parameters consists of generating n parallel
+constraints from caller+i to callee+i for 0<=i<n.)
+
+The zero nodeid means "not a pointer".  Currently it's only used for
+struct{} or ().  We generate all flow constraints, even for non-pointer
+types, with the expectations that (a) presolver optimisations will
+quickly collapse all the non-pointer ones and (b) we may get more
+precise results by treating uintptr as a possible pointer.
+
+Each node represents a scalar (word-sized) part of a value or object.
+Aggregate types (structs, tuples, arrays) are recursively flattened
+out into a sequential list of scalar component types, and all the
+elements of an array are represented by a single node.  (The
+flattening of a basic type is a list containing a single node.)
+
+Nodes are connected into a graph with various kinds of labelled edges:
+simple edges (or copy constraints) represent value flow.  Complex
+edges (load, store, etc) trigger the creation of new simple edges
+during the solving phase.
+
+
+OBJECTS
+
+An "object" is a contiguous sequence of nodes denoting an addressable
+location: something that a pointer can point to.  The first node of an
+object has the ntObject flag, and its size indicates the extent of the
+object.
+
+Objects include:
+   - functions and globals;
+   - variable allocations in the stack frame or heap;
+   - maps, channels and slices created by calls to make();
+   - allocations to construct an interface;
+   - allocations caused by literals and conversions,
+     e.g. []byte("foo"), []byte(str).
+   - arrays allocated by calls to append();
+
+Many objects have no Go types.  For example, the func, map and chan
+type kinds in Go are all varieties of pointers, but the respective
+objects are actual functions, maps, and channels.  Given the way we
+model interfaces, they too are pointers to interface objects with no
+Go type.  And an *ssa.Global denotes the address of a global variable,
+but the object for a Global is the actual data.  So, types of objects
+are usually "off by one indirection".
+
+The individual nodes of an object are sometimes referred to as
+"labels".
+
+Objects containing no nodes (i.e. just empty structs; tuples may be
+values but never objects in Go) are padded with an invalid-type node
+to have at least one node so that there is something to point to.
+(TODO(adonovan): I think this is unnecessary now that we have identity
+nodes; check.)
+
+
+ANALYSIS ABSTRACTION OF EACH TYPE
+
+Variables of the following "scalar" types may be represented by a
+single node: basic types, pointers, channels, maps, slices, 'func'
+pointers, interfaces.
+
+Pointers
+  Nothing to say here.
+
+Basic types (bool, string, numbers, unsafe.Pointer)
+  Currently all fields in the flattening of a type, including
+  non-pointer basic types such as int, are represented in objects and
+  values.  Though non-pointer nodes within values are uninteresting,
+  non-pointer nodes in objects may be useful (if address-taken)
+  because they permit the analysis to deduce, in this example,
+
+     var s struct{ ...; x int; ... }
+     p := &s.x
+
+  that p points to s.x.  If we ignored such object fields, we could only
+  say that p points somewhere within s.
+
+  All other basic types are ignored.  Expressions of these types have
+  zero nodeid, and fields of these types within aggregate other types
+  are omitted.
+
+  unsafe.Pointer conversions are not yet modelled as pointer
+  conversions.  Consequently uintptr is always a number and uintptr
+  nodes do not point to any object.
+
+Channels
+  An expression of type 'chan T' is a kind of pointer that points
+  exclusively to channel objects, i.e. objects created by MakeChan (or
+  reflection).
+
+  'chan T' is treated like *T.
+  *ssa.MakeChan is treated as equivalent to new(T).
+  *ssa.Send and receive (*ssa.UnOp(ARROW)) and are equivalent to store
+   and load.
+
+Maps
+  An expression of type 'map[K]V' is a kind of pointer that points
+  exclusively to map objects, i.e. objects created by MakeMap (or
+  reflection).
+
+  map K[V] is treated like *M where M = struct{k K; v V}.
+  *ssa.MakeMap is equivalent to new(M).
+  *ssa.MapUpdate is equivalent to *y=x where *y and x have type M.
+  *ssa.Lookup is equivalent to y=x.v where x has type *M.
+
+Slices
+  A slice []T, which dynamically resembles a struct{array *T, len, cap int},
+  is treated as if it were just a *T pointer; the len and cap fields are
+  ignored.
+
+  *ssa.MakeSlice is treated like new([1]T): an allocation of a
+   singleton array.
+  *ssa.Index on a slice is equivalent to a load.
+  *ssa.IndexAddr on a slice returns the address of the sole element of the
+  slice, i.e. the same address.
+  *ssa.Slice is treated as a simple copy.
+
+Functions
+  An expression of type 'func...' is a kind of pointer that points
+  exclusively to function objects.
+
+  A function object has the following layout:
+
+     identity         -- typ:*types.Signature; flags ⊇ {ntFunction}; data:*cgnode
+     params_0         -- (the receiver, if a method)
+     ...
+     params_n-1
+     results_0
+     ...
+     results_m-1
+
+  There may be multiple function objects for the same *ssa.Function
+  due to context-sensitive treatment of some functions.
+
+  The first node is the function's identity node; its .data is the
+  callgraph node (*cgnode) this object represents.
+  Associated with every callsite is a special "targets" variable,
+  whose pts(·) contains the identity node of each function to which
+  the call may dispatch.  Identity words are not otherwise used.
+
+  The following block of nodes represent the flattened-out types of
+  the parameters and results of the function object, and are
+  collectively known as its "P/R block".
+
+  The treatment of free variables of closures (*ssa.Capture) is like
+  that of global variables; it is not context-sensitive.
+  *ssa.MakeClosure instructions create copy edges to Captures.
+
+  A Go value of type 'func' (i.e. a pointer to one or more functions)
+  is a pointer whose pts() contains function objects.  The valueNode()
+  for an *ssa.Function returns a singleton for that function.
+
+Interfaces
+ An expression of type 'interface{...}' is a kind of pointer that
+ points exclusively to interface objects.
+
+ An interface object has the following layout:
+
+    conctype          -- flags ⊇ {ntInterface}; data:*ssa.MakeInterface?
+    value
+    ...
+
+ The conctype node's typ field is the concrete type of the interface
+ value which follows, flattened out.  It has the ntInterface flag.
+ Its associated data is the originating MakeInterface instruction, if
+ any.
+
+ Constructing an interface value causes generation of constraints for
+ all of the concrete type's methods; we can't tell a priori which ones
+ may be called.
+
+ TypeAssert y = x.(T) is implemented by a dynamic filter triggered by
+ each interface object E added to pts(x).  If T is an interface that
+ E.conctype implements, pts(y) gets E.  If T is a concrete type then
+ edge pts(y) <- E.value is added.
+
+ ChangeInterface is a simple copy because the representation of
+ interface objects is independent of the interface type (in contrast
+ to the "method tables" approach used by the gc runtime).
+
+ y := Invoke x.m(...) is implemented by allocating a contiguous P/R
+ block for the callsite and adding a dynamic rule triggered by each
+ interface object E added to pts(x).  The rule adds param/results copy
+ edges to/from each discovered concrete method.
+
+ (Q. Why do we model an interface as a pointer to a pair of type and
+  value, rather than as a pair of a pointer to type and a pointer to
+  value?
+  A. Control-flow joins would merge interfaces ({T1}, {V1}) and ({T2},
+  {V2}) to make ({T1,T2}, {V1,V2}), leading to the infeasible and
+  type-unsafe combination (T1,V2).  Treating the value and its concrete
+  type as inseparable makes the analysis type-safe.)
+
+
+Aggregate types:
+
+Aggregate types are treated as if all directly contained
+aggregates are recursively flattened out.
+
+Structs
+  *ssa.Field y = x.f creates a simple edge to y from x's node at f's offset.
+
+  *ssa.FieldAddr y = &x->f requires a dynamic closure rule to create
+   simple edges for each struct discovered in pts(x).
+
+  The nodes of a struct consist of a special 'identity' node (whose
+  type is that of the struct itself), followed by the nodes for all
+  the struct's fields, recursively flattened out.  A pointer to the
+  struct is a pointer to its identity node.  That node allows us to
+  distinguish a pointer to a struct from a pointer to its first field.
+
+  Field offsets are currently the logical field offsets (plus one for
+  the identity node), so the sizes of the fields can be ignored by the
+  analysis.
+
+  Sound treatment of unsafe.Pointer conversions (not yet implemented)
+  would require us to model memory layout using physical field offsets
+  to ascertain which object field(s) might be aliased by a given
+  FieldAddr of a different base pointer type.  It would also require
+  us to dispense with the identity node.
+
+  *ssa.Field y = x.f creates a simple edge to y from x's node at f's offset.
+
+  *ssa.FieldAddr y = &x->f requires a dynamic closure rule to create
+   simple edges for each struct discovered in pts(x).
+
+Arrays
+  We model an array by an identity node (whose type is that of the
+  array itself) followed by a node representing all the elements of
+  the array; the analysis does not distinguish elements with different
+  indices.  Effectively, an array is treated like struct{elem T}, a
+  load y=x[i] like y=x.elem, and a store x[i]=y like x.elem=y; the
+  index i is ignored.
+
+  A pointer to an array is pointer to its identity node.  (A slice is
+  also a pointer to an array's identity node.)  The identity node
+  allows us to distinguish a pointer to an array from a pointer to one
+  of its elements, but it is rather costly because it introduces more
+  offset constraints into the system.  Furthermore, sound treatment of
+  unsafe.Pointer would require us to dispense with this node.
+
+  Arrays may be allocated by Alloc, by make([]T), by calls to append,
+  and via reflection.
+
+Tuples (T, ...)
+  Tuples are treated like structs with naturally numbered fields.
+  *ssa.Extract is analogous to *ssa.Field.
+
+  However, tuples have no identity field since by construction, they
+  cannot be address-taken.
+
+
+FUNCTION CALLS
+
+  There are three kinds of function call:
+  (1) static "call"-mode calls of functions.
+  (2) dynamic "call"-mode calls of functions.
+  (3) dynamic "invoke"-mode calls of interface methods.
+  Cases 1 and 2 apply equally to methods and standalone functions.
+
+  Static calls.
+    A static call consists three steps:
+    - finding the function object of the callee;
+    - creating copy edges from the actual parameter value nodes to the
+      params block in the function object (this includes the receiver
+      if the callee is a method);
+    - creating copy edges from the results block in the function
+      object to the value nodes for the result of the call.
+
+    Context sensitivity
+
+      Static calls (alone) may be treated context sensitively,
+      i.e. each callsite may cause a distinct re-analysis of the
+      callee, improving precision.  Our current context-sensitivity
+      policy treats all intrinsics and getter/setter methods in this
+      manner since such functions are small and seem like an obvious
+      source of spurious confluences, though this has not yet been
+      evaluated.
+
+  Dynamic function calls
+
+    Dynamic calls work in a similar manner except that the creation of
+    copy edges occurs dynamically, in a similar fashion to a pair of
+    struct copies:
+
+      *fn->params = callargs
+      callresult = *fn->results
+
+    (Recall that the function object's params and results blocks are
+    contiguous.)
+
+  Interface method invocation
+
+    For invoke-mode calls, we create a params/results block for the
+    callsite and attach a dynamic closure rule to the interface.  For
+    each new interface object that flows to the interface, we look up
+    the concrete method, find its function object, and connect its P/R
+    block to the callsite's P/R block.
+
+  Recording call targets
+
+    The analysis notifies its clients of each callsite it encounters,
+    passing a CallSite interface.  Among other things, the CallSite
+    contains a synthetic constraint variable ("targets") whose
+    points-to solution includes the set of all function objects to
+    which the call may dispatch.
+
+    It is via this mechanism that the callgraph is made available.
+    Clients may also elect to be notified of callgraph edges directly;
+    internally this just iterates all "targets" variables' pts(·)s.
+
+
+SOLVER
+
+The solver is currently a very naive Andersen-style implementation,
+although it does use difference propagation (Pearce et al, SQC'04).
+There is much to do.
+
+
+FURTHER READING.
+
+Andersen, L. O. 1994. Program analysis and specialization for the C
+programming language. Ph.D. dissertation. DIKU, University of
+Copenhagen.
+
+David J. Pearce, Paul H. J. Kelly, and Chris Hankin. 2004.  Efficient
+field-sensitive pointer analysis for C. In Proceedings of the 5th ACM
+SIGPLAN-SIGSOFT workshop on Program analysis for software tools and
+engineering (PASTE '04). ACM, New York, NY, USA, 37-42.
+http://doi.acm.org/10.1145/996821.996835
+
+David J. Pearce, Paul H. J. Kelly, and Chris Hankin. 2004. Online
+Cycle Detection and Difference Propagation: Applications to Pointer
+Analysis. Software Quality Control 12, 4 (December 2004), 311-337.
+http://dx.doi.org/10.1023/B:SQJO.0000039791.93071.a2
+
+David Grove and Craig Chambers. 2001. A framework for call graph
+construction algorithms. ACM Trans. Program. Lang. Syst. 23, 6
+(November 2001), 685-746.
+http://doi.acm.org/10.1145/506315.506316
+
+Ben Hardekopf and Calvin Lin. 2007. The ant and the grasshopper: fast
+and accurate pointer analysis for millions of lines of code. In
+Proceedings of the 2007 ACM SIGPLAN conference on Programming language
+design and implementation (PLDI '07). ACM, New York, NY, USA, 290-299.
+http://doi.acm.org/10.1145/1250734.1250767
+
+Ben Hardekopf and Calvin Lin. 2007. Exploiting pointer and location
+equivalence to optimize pointer analysis. In Proceedings of the 14th
+international conference on Static Analysis (SAS'07), Hanne Riis
+Nielson and Gilberto Filé (Eds.). Springer-Verlag, Berlin, Heidelberg,
+265-280.
+
+*/
+package pointer
diff --git a/pointer/gen.go b/pointer/gen.go
new file mode 100644
index 0000000..b13d47a
--- /dev/null
+++ b/pointer/gen.go
@@ -0,0 +1,1069 @@
+package pointer
+
+// This file defines the constraint generation phase.
+
+import (
+	"fmt"
+	"go/ast"
+	"go/token"
+
+	"code.google.com/p/go.tools/go/types"
+	"code.google.com/p/go.tools/ssa"
+)
+
+var (
+	tEface     = types.NewInterface(nil)
+	tInvalid   = types.Typ[types.Invalid]
+	tUnsafePtr = types.Typ[types.UnsafePointer]
+)
+
+// ---------- Node creation ----------
+
+// nextNode returns the index of the next unused node.
+func (a *analysis) nextNode() nodeid {
+	return nodeid(len(a.nodes))
+}
+
+// addNodes creates nodes for all scalar elements in type typ, and
+// returns the id of the first one, or zero if the type was
+// analytically uninteresting.
+//
+// comment explains the origin of the nodes, as a debugging aid.
+//
+func (a *analysis) addNodes(typ types.Type, comment string) nodeid {
+	id := a.nextNode()
+	for _, fi := range a.flatten(typ) {
+		a.addOneNode(fi.typ, comment, fi)
+	}
+	if id == a.nextNode() {
+		return 0 // type contained no pointers
+	}
+	return id
+}
+
+// addOneNode creates a single node with type typ, and returns its id.
+//
+// typ should generally be scalar (except for interface.conctype nodes
+// and struct/array identity nodes).  Use addNodes for non-scalar types.
+//
+// comment explains the origin of the nodes, as a debugging aid.
+// subelement indicates the subelement, e.g. ".a.b[*].c".
+//
+func (a *analysis) addOneNode(typ types.Type, comment string, subelement *fieldInfo) nodeid {
+	id := a.nextNode()
+	a.nodes = append(a.nodes, &node{typ: typ, subelement: subelement})
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\tcreate n%d %s for %s%s\n",
+			id, typ, comment, subelement.path())
+	}
+	return id
+}
+
+// setValueNode associates node id with the value v.
+// TODO(adonovan): disambiguate v by its CallGraphNode, if it's a local.
+func (a *analysis) setValueNode(v ssa.Value, id nodeid) {
+	a.valNode[v] = id
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\tval[%s] = n%d  (%T)\n", v.Name(), id, v)
+	}
+
+	// Record the (v, id) relation if the client has queried v.
+	qv := a.config.QueryValues
+	if ptrs, ok := qv[v]; ok {
+		qv[v] = append(ptrs, ptr{a, id})
+	}
+}
+
+// endObject marks the end of a sequence of calls to addNodes denoting
+// a single object allocation.
+//
+// obj is the start node of the object, from a prior call to nextNode.
+// Its size, flags and (optionally) data will be updated.
+//
+func (a *analysis) endObject(obj nodeid, data ssa.Value) {
+	// Ensure object is non-empty by padding;
+	// the pad will be the object node.
+	size := uint32(a.nextNode() - obj)
+	if size == 0 {
+		a.addOneNode(tInvalid, "padding", nil)
+	}
+	objNode := a.nodes[obj]
+	objNode.size = size // excludes padding
+	objNode.flags = ntObject
+
+	if data != nil {
+		objNode.data = data
+		if a.log != nil {
+			fmt.Fprintf(a.log, "\tobj[%s] = n%d\n", data, obj)
+		}
+	}
+}
+
+// makeFunctionObject creates and returns a new function object for
+// fn, and returns the id of its first node.  It also enqueues fn for
+// subsequent constraint generation.
+//
+func (a *analysis) makeFunctionObject(fn *ssa.Function) nodeid {
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\t---- makeFunctionObject %s\n", fn)
+	}
+
+	// obj is the function object (identity, params, results).
+	obj := a.nextNode()
+	sig := fn.Signature
+	a.addOneNode(sig, "func.cgnode", nil) // (scalar with Signature type)
+	if recv := sig.Recv(); recv != nil {
+		a.addNodes(recv.Type(), "func.recv")
+	}
+	a.addNodes(sig.Params(), "func.params")
+	a.addNodes(sig.Results(), "func.results")
+	a.endObject(obj, fn)
+
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\t----\n")
+	}
+
+	cgn := &cgnode{fn: fn, obj: obj}
+	a.nodes[obj].flags |= ntFunction
+	a.nodes[obj].data = cgn
+
+	// Queue it up for constraint processing.
+	a.genq = append(a.genq, cgn)
+
+	return obj
+}
+
+// makeFunction creates the shared function object (aka contour) for
+// function fn and returns a 'func' value node that points to it.
+//
+func (a *analysis) makeFunction(fn *ssa.Function) nodeid {
+	obj := a.makeFunctionObject(fn)
+	a.funcObj[fn] = obj
+
+	var comment string
+	if a.log != nil {
+		comment = fn.String()
+	}
+	id := a.addOneNode(fn.Type(), comment, nil)
+	a.addressOf(id, obj)
+	return id
+}
+
+// makeGlobal creates the value node and object node for global g,
+// and returns the value node.
+//
+// The value node represents the address of the global variable, and
+// points to the object (and nothing else).
+//
+// The object consists of the global variable itself (conceptually,
+// the BSS address).
+//
+func (a *analysis) makeGlobal(g *ssa.Global) nodeid {
+	var comment string
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\t---- makeGlobal %s\n", g)
+		comment = g.FullName()
+	}
+
+	// The nodes representing the object itself.
+	obj := a.nextNode()
+	a.addNodes(mustDeref(g.Type()), "global")
+	a.endObject(obj, g)
+
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\t----\n")
+	}
+
+	// The node representing the address of the global.
+	id := a.addOneNode(g.Type(), comment, nil)
+	a.addressOf(id, obj)
+
+	return id
+}
+
+// makeConstant creates the value node and object node (if needed) for
+// constant c, and returns the value node.
+// An object node is created only for []byte or []rune constants.
+// The value node points to the object node, iff present.
+//
+func (a *analysis) makeConstant(l *ssa.Const) nodeid {
+	id := a.addNodes(l.Type(), "const")
+	if !l.IsNil() {
+		// []byte or []rune?
+		if t, ok := l.Type().Underlying().(*types.Slice); ok {
+			// Treat []T like *[1]T, 'make []T' like new([1]T).
+			obj := a.nextNode()
+			a.addNodes(sliceToArray(t), "array in slice constant")
+			a.endObject(obj, l)
+
+			a.addressOf(id, obj)
+		}
+	}
+	return id
+}
+
+// valueNode returns the id of the value node for v, creating it (and
+// the association) as needed.  It may return zero for uninteresting
+// values containing no pointers.
+//
+// Nodes for locals are created en masse during genFunc and are
+// implicitly contextualized by the function currently being analyzed
+// (i.e. parameter to genFunc).
+//
+func (a *analysis) valueNode(v ssa.Value) nodeid {
+	id, ok := a.valNode[v]
+	if !ok {
+		switch v := v.(type) {
+		case *ssa.Function:
+			id = a.makeFunction(v)
+
+		case *ssa.Global:
+			id = a.makeGlobal(v)
+
+		case *ssa.Const:
+			id = a.makeConstant(v)
+
+		case *ssa.Capture:
+			// TODO(adonovan): treat captures context-sensitively.
+			id = a.addNodes(v.Type(), "capture")
+
+		default:
+			// *ssa.Parameters and ssa.Instruction values
+			// are created by genFunc.
+			// *Builtins are not true values.
+			panic(v)
+		}
+		a.setValueNode(v, id)
+	}
+	return id
+}
+
+// valueOffsetNode ascertains the node for tuple/struct value v,
+// then returns the node for its subfield #index.
+//
+func (a *analysis) valueOffsetNode(v ssa.Value, index int) nodeid {
+	id := a.valueNode(v)
+	if id == 0 {
+		panic(fmt.Sprintf("cannot offset within n0: %s = %s", v.Name(), v))
+	}
+	return id + nodeid(a.offsetOf(v.Type(), index))
+}
+
+// interfaceValue returns the (first node of) the value, and the
+// concrete type, of the interface object (flags&ntInterface) starting
+// at id.
+//
+func (a *analysis) interfaceValue(id nodeid) (nodeid, types.Type) {
+	n := a.nodes[id]
+	if n.flags&ntInterface == 0 {
+		panic(fmt.Sprintf("interfaceValue(n%d): not an interface object; typ=%s", id, n.typ))
+	}
+	return id + 1, n.typ
+}
+
+// funcParams returns the first node of the params block of the
+// function whose object node (flags&ntFunction) is id.
+//
+func (a *analysis) funcParams(id nodeid) nodeid {
+	if a.nodes[id].flags&ntFunction == 0 {
+		panic(fmt.Sprintf("funcParams(n%d): not a function object block", id))
+	}
+	return id + 1
+}
+
+// funcResults returns the first node of the results block of the
+// function whose object node (flags&ntFunction) is id.
+//
+func (a *analysis) funcResults(id nodeid) nodeid {
+	n := a.nodes[id]
+	if n.flags&ntFunction == 0 {
+		panic(fmt.Sprintf("funcResults(n%d): not a function object block", id))
+	}
+	sig := n.typ.(*types.Signature)
+	id += 1 + nodeid(a.sizeof(sig.Params()))
+	if sig.Recv() != nil {
+		id += nodeid(a.sizeof(sig.Recv().Type()))
+	}
+	return id
+}
+
+// ---------- Constraint creation ----------
+
+// copy creates a constraint of the form dst = src.
+// sizeof is the width (in logical fields) of the copied type.
+//
+func (a *analysis) copy(dst, src nodeid, sizeof uint32) {
+	if src == dst || sizeof == 0 {
+		return // trivial
+	}
+	if src == 0 || dst == 0 {
+		panic(fmt.Sprintf("ill-typed copy dst=n%d src=n%d", dst, src))
+	}
+	for i := uint32(0); i < sizeof; i++ {
+		a.addConstraint(&copyConstraint{dst, src})
+		src++
+		dst++
+	}
+}
+
+// addressOf creates a constraint of the form id = &obj.
+func (a *analysis) addressOf(id, obj nodeid) {
+	if id == 0 {
+		panic("addressOf: zero id")
+	}
+	if obj == 0 {
+		panic("addressOf: zero obj")
+	}
+	a.addConstraint(&addrConstraint{id, obj})
+}
+
+// load creates a load constraint of the form dst = *src.
+// sizeof is the width (in logical fields) of the loaded type.
+//
+func (a *analysis) load(dst, src nodeid, sizeof uint32) {
+	a.loadOffset(dst, src, 0, sizeof)
+}
+
+// loadOffset creates a load constraint of the form dst = src[offset].
+// offset is the pointer offset in logical fields.
+// sizeof is the width (in logical fields) of the loaded type.
+//
+func (a *analysis) loadOffset(dst, src nodeid, offset uint32, sizeof uint32) {
+	if dst == 0 {
+		return // load of non-pointerlike value
+	}
+	if src == 0 && dst == 0 {
+		return // non-pointerlike operation
+	}
+	if src == 0 || dst == 0 {
+		panic(fmt.Sprintf("ill-typed load dst=n%d src=n%d", dst, src))
+	}
+	for i := uint32(0); i < sizeof; i++ {
+		a.addConstraint(&loadConstraint{offset, dst, src})
+		offset++
+		dst++
+	}
+}
+
+// store creates a store constraint of the form *dst = src.
+// sizeof is the width (in logical fields) of the stored type.
+//
+func (a *analysis) store(dst, src nodeid, sizeof uint32) {
+	a.storeOffset(dst, src, 0, sizeof)
+}
+
+// storeOffset creates a store constraint of the form dst[offset] = src.
+// offset is the pointer offset in logical fields.
+// sizeof is the width (in logical fields) of the stored type.
+//
+func (a *analysis) storeOffset(dst, src nodeid, offset uint32, sizeof uint32) {
+	if src == 0 {
+		return // store of non-pointerlike value
+	}
+	if src == 0 && dst == 0 {
+		return // non-pointerlike operation
+	}
+	if src == 0 || dst == 0 {
+		panic(fmt.Sprintf("ill-typed store dst=n%d src=n%d", dst, src))
+	}
+	for i := uint32(0); i < sizeof; i++ {
+		a.addConstraint(&storeConstraint{offset, dst, src})
+		offset++
+		src++
+	}
+}
+
+// offsetAddr creates an offsetAddr constraint of the form dst = &src.#offset.
+// offset is the field offset in logical fields.
+//
+func (a *analysis) offsetAddr(dst, src nodeid, offset uint32) {
+	if offset == 0 {
+		// Simplify  dst = &src->f0
+		//       to  dst = src
+		// (NB: this optimisation is defeated by the identity
+		// field prepended to struct and array objects.)
+		a.copy(dst, src, 1)
+	} else {
+		a.addConstraint(&offsetAddrConstraint{offset, dst, src})
+	}
+}
+
+// addConstraint adds c to the constraint set.
+func (a *analysis) addConstraint(c constraint) {
+	a.constraints = append(a.constraints, c)
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\t%s\n", c)
+	}
+}
+
+// copyElems generates load/store constraints for *dst = *src,
+// where src and dst are slices or *arrays.
+// (If pts(·) of either is a known singleton, this is suboptimal.)
+//
+func (a *analysis) copyElems(typ types.Type, dst, src nodeid) {
+	tmp := a.addNodes(typ, "copy")
+	sz := a.sizeof(typ)
+	a.loadOffset(tmp, src, 1, sz)
+	a.storeOffset(dst, tmp, 1, sz)
+}
+
+// ---------- Constraint generation ----------
+
+// genConv generates constraints for the conversion operation conv.
+func (a *analysis) genConv(conv *ssa.Convert) {
+	res := a.valueNode(conv)
+	if res == 0 {
+		return // result is non-pointerlike
+	}
+
+	tSrc := conv.X.Type()
+	tDst := conv.Type()
+
+	switch utSrc := tSrc.Underlying().(type) {
+	case *types.Slice:
+		// []byte/[]rune -> string?
+		return
+
+	case *types.Pointer:
+		// *T -> unsafe.Pointer?
+		if tDst == tUnsafePtr {
+			// ignore for now
+			// a.copy(res, a.valueNode(conv.X), 1)
+			return
+		}
+
+	case *types.Basic:
+		switch utDst := tDst.Underlying().(type) {
+		case *types.Pointer:
+			// unsafe.Pointer -> *T?  (currently unsound)
+			if utSrc == tUnsafePtr {
+				a.warnf(conv.Pos(),
+					"unsound: %s contains an unsafe.Pointer conversion (to %s)",
+					conv.Parent(), tDst)
+
+				// For now, we treat unsafe.Pointer->*T
+				// conversion like new(T) and create an
+				// unaliased object.  In future we may handle
+				// unsafe conversions soundly; see TODO file.
+				obj := a.addNodes(mustDeref(tDst), "unsafe.Pointer conversion")
+				a.endObject(obj, conv)
+				a.addressOf(res, obj)
+				return
+			}
+
+		case *types.Slice:
+			// string -> []byte/[]rune (or named aliases)?
+			if utSrc.Info()&types.IsString != 0 {
+				obj := a.addNodes(sliceToArray(tDst), "convert")
+				a.endObject(obj, conv)
+				a.addressOf(res, obj)
+				return
+			}
+
+		case *types.Basic:
+			// TODO(adonovan):
+			// unsafe.Pointer -> uintptr?
+			// uintptr -> unsafe.Pointer
+			//
+			// The language doesn't adequately specify the
+			// behaviour of these operations, but almost
+			// all uses of these conversions (even in the
+			// spec) seem to imply a non-moving garbage
+			// collection strategy, or implicit "pinning"
+			// semantics for unsafe.Pointer conversions.
+
+			// TODO(adonovan): we need more work before we can handle
+			// cryptopointers well.
+			if utSrc == tUnsafePtr || utDst == tUnsafePtr {
+				// Ignore for now.  See TODO file for ideas.
+				return
+			}
+
+			return // ignore all other basic type conversions
+		}
+	}
+
+	panic(fmt.Sprintf("illegal *ssa.Convert %s -> %s: %s", tSrc, tDst, conv.Parent()))
+}
+
+// genAppend generates constraints for a call to append.
+func (a *analysis) genAppend(instr *ssa.Call) {
+	// Consider z = append(x, y).   y is optional.
+	// This may allocate a new [1]T array; call its object w.
+	// We get the following constraints:
+	// 	z = x
+	// 	z = &w
+	//     *z = *y
+
+	x := a.valueNode(instr.Call.Args[0])
+
+	z := a.valueNode(instr)
+	a.copy(z, x, 1) // z = x
+
+	if len(instr.Call.Args) == 1 {
+		return // no allocation for z = append(x) or _ = append(x).
+	}
+
+	// TODO(adonovan): test append([]byte, ...string) []byte.
+
+	y := a.valueNode(instr.Call.Args[1])
+	tArray := sliceToArray(instr.Call.Args[0].Type())
+
+	var w nodeid
+	w = a.nextNode()
+	a.addNodes(tArray, "append")
+	a.endObject(w, instr)
+
+	a.copyElems(tArray.Elem(), z, y) // *z = *y
+	a.addressOf(z, w)                //  z = &w
+}
+
+// genBuiltinCall generates contraints for a call to a built-in.
+func (a *analysis) genBuiltinCall(instr ssa.CallInstruction) {
+	call := instr.Common()
+	switch call.Value.(*ssa.Builtin).Object().Name() {
+	case "append":
+		// Safe cast: append cannot appear in a go or defer statement.
+		a.genAppend(instr.(*ssa.Call))
+
+	case "copy":
+		tElem := call.Args[0].Type().Underlying().(*types.Slice).Elem()
+		a.copyElems(tElem, a.valueNode(call.Args[0]), a.valueNode(call.Args[1]))
+
+	case "panic":
+		a.copy(a.panicNode, a.valueNode(call.Args[0]), 1)
+
+	case "recover":
+		if v := instr.Value(); v != nil {
+			a.copy(a.valueNode(v), a.panicNode, 1)
+		}
+
+	case "print":
+		// Analytically print is a no-op, but it's a convenient hook
+		// for testing the pts of an expression, so we notify the client.
+		// Existing uses in Go core libraries are few and harmless.
+		if Print := a.config.Print; Print != nil {
+			// Due to context-sensitivity, we may encounter
+			// the same print() call in many contexts, so
+			// we merge them to a canonical node.
+			probe := a.probes[call]
+			t := call.Args[0].Type()
+
+			// First time?  Create the canonical probe node.
+			if probe == 0 {
+				probe = a.addNodes(t, "print")
+				a.probes[call] = probe
+				Print(call, ptr{a, probe}) // notify client
+			}
+
+			a.copy(probe, a.valueNode(call.Args[0]), a.sizeof(t))
+		}
+
+	default:
+		// No-ops: close len cap real imag complex println delete.
+	}
+}
+
+// shouldUseContext defines the context-sensitivity policy.  It
+// returns true if we should analyse all static calls to fn anew.
+//
+// Obviously this interface rather limits how much freedom we have to
+// choose a policy.  The current policy, rather arbitrarily, is true
+// for intrinsics and accessor methods (actually: short, single-block,
+// call-free functions).  This is just a starting point.
+//
+func (a *analysis) shouldUseContext(fn *ssa.Function) bool {
+	if a.findIntrinsic(fn) != nil {
+		return true // treat intrinsics context-sensitively
+	}
+	if len(fn.Blocks) != 1 {
+		return false // too expensive
+	}
+	blk := fn.Blocks[0]
+	if len(blk.Instrs) > 10 {
+		return false // too expensive
+	}
+	if fn.Synthetic != "" && (fn.Pkg == nil || fn != fn.Pkg.Func("init")) {
+		return true // treat synthetic wrappers context-sensitively
+	}
+	for _, instr := range blk.Instrs {
+		switch instr := instr.(type) {
+		case ssa.CallInstruction:
+			// Disallow function calls (except to built-ins)
+			// because of the danger of unbounded recursion.
+			if _, ok := instr.Common().Value.(*ssa.Builtin); !ok {
+				return false
+			}
+		}
+	}
+	return true
+}
+
+// genStaticCall generates constraints for a statically dispatched
+// function call.  It returns a node whose pts() will be the set of
+// possible call targets (in this case, a singleton).
+//
+func (a *analysis) genStaticCall(call *ssa.CallCommon, result nodeid) nodeid {
+	// Ascertain the context (contour/CGNode) for a particular call.
+	var obj nodeid
+	fn := call.StaticCallee()
+	if a.shouldUseContext(fn) {
+		obj = a.makeFunctionObject(fn) // new contour for this call
+	} else {
+		a.valueNode(fn)     // ensure shared contour was created
+		obj = a.funcObj[fn] // ordinary (shared) contour.
+	}
+
+	sig := call.Signature()
+	targets := a.addOneNode(sig, "call.targets", nil)
+	a.addressOf(targets, obj) // (a singleton)
+
+	// Copy receiver, if any.
+	params := a.funcParams(obj)
+	args := call.Args
+	if sig.Recv() != nil {
+		sz := a.sizeof(sig.Recv().Type())
+		a.copy(params, a.valueNode(args[0]), sz)
+		params += nodeid(sz)
+		args = args[1:]
+	}
+
+	// Copy actual parameters into formal params block.
+	// Must loop, since the actuals aren't contiguous.
+	for i, arg := range args {
+		sz := a.sizeof(sig.Params().At(i).Type())
+		a.copy(params, a.valueNode(arg), sz)
+		params += nodeid(sz)
+	}
+
+	// Copy formal results block to actual result.
+	if result != 0 {
+		a.copy(result, a.funcResults(obj), a.sizeof(sig.Results()))
+	}
+
+	return targets
+}
+
+// genDynamicCall generates constraints for a dynamic function call.
+// It returns a node whose pts() will be the set of possible call targets.
+//
+func (a *analysis) genDynamicCall(call *ssa.CallCommon, result nodeid) nodeid {
+	fn := a.valueNode(call.Value)
+	sig := call.Signature()
+
+	// We add dynamic closure rules that store the arguments into,
+	// and load the results from, the P/R block of each function
+	// discovered in pts(fn).
+
+	var offset uint32 = 1 // P/R block starts at offset 1
+	for i, arg := range call.Args {
+		sz := a.sizeof(sig.Params().At(i).Type())
+		a.storeOffset(fn, a.valueNode(arg), offset, sz)
+		offset += sz
+	}
+	if result != 0 {
+		a.loadOffset(result, fn, offset, a.sizeof(sig.Results()))
+	}
+	return fn
+}
+
+// genInvoke generates constraints for a dynamic method invocation.
+// It returns a node whose pts() will be the set of possible call targets.
+//
+func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid {
+	sig := call.Signature()
+
+	// Allocate a contiguous targets/params/results block for this call.
+	block := a.nextNode()
+	targets := a.addOneNode(sig, "invoke.targets", nil)
+	p := a.addNodes(sig.Params(), "invoke.params")
+	r := a.addNodes(sig.Results(), "invoke.results")
+
+	// Copy the actual parameters into the call's params block.
+	for i, n := 0, sig.Params().Len(); i < n; i++ {
+		sz := a.sizeof(sig.Params().At(i).Type())
+		a.copy(p, a.valueNode(call.Args[i]), sz)
+		p += nodeid(sz)
+	}
+	// Copy the call's results block to the actual results.
+	if result != 0 {
+		a.copy(result, r, a.sizeof(sig.Results()))
+	}
+
+	// We add a dynamic invoke constraint that will add
+	// edges from the caller's P/R block to the callee's
+	// P/R block for each discovered call target.
+	a.addConstraint(&invokeConstraint{call.Method, a.valueNode(call.Value), block})
+
+	return targets
+}
+
+// genCall generates contraints for call instruction instr.
+func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) {
+	call := instr.Common()
+
+	// Intrinsic implementations of built-in functions.
+	if _, ok := call.Value.(*ssa.Builtin); ok {
+		a.genBuiltinCall(instr)
+		return
+	}
+
+	var result nodeid
+	if v := instr.Value(); v != nil {
+		result = a.valueNode(v)
+	}
+
+	// The node whose pts(·) will contain all targets of the call.
+	var targets nodeid
+	switch {
+	case call.StaticCallee() != nil:
+		targets = a.genStaticCall(call, result)
+	case call.IsInvoke():
+		targets = a.genInvoke(call, result)
+	default:
+		targets = a.genDynamicCall(call, result)
+	}
+
+	site := &callsite{
+		caller:  caller,
+		targets: targets,
+		instr:   instr,
+		pos:     instr.Pos(),
+	}
+	a.callsites = append(a.callsites, site)
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\t%s to targets %s from %s\n",
+			site.Description(), site.targets, site.caller)
+	}
+}
+
+// genInstr generates contraints for instruction instr in context cgn.
+func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) {
+	if a.log != nil {
+		var prefix string
+		if val, ok := instr.(ssa.Value); ok {
+			prefix = val.Name() + " = "
+		}
+		fmt.Fprintf(a.log, "; %s%s\n", prefix, instr)
+	}
+
+	switch instr := instr.(type) {
+	case *ssa.DebugRef:
+		// no-op.
+
+	case *ssa.UnOp:
+		switch instr.Op {
+		case token.ARROW: // <-x
+			// We can ignore instr.CommaOk because the node we're
+			// altering is always at zero offset relative to instr.
+			a.load(a.valueNode(instr), a.valueNode(instr.X), a.sizeof(instr.Type()))
+
+		case token.MUL: // *x
+			a.load(a.valueNode(instr), a.valueNode(instr.X), a.sizeof(instr.Type()))
+
+		default:
+			// NOT, SUB, XOR: no-op.
+		}
+
+	case *ssa.BinOp:
+		// All no-ops.
+
+	case ssa.CallInstruction: // *ssa.Call, *ssa.Go, *ssa.Defer
+		a.genCall(cgn, instr)
+
+	case *ssa.ChangeType:
+		a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
+
+	case *ssa.Convert:
+		a.genConv(instr)
+
+	case *ssa.Extract:
+		a.copy(a.valueNode(instr),
+			a.valueOffsetNode(instr.Tuple, instr.Index),
+			a.sizeof(instr.Type()))
+
+	case *ssa.FieldAddr:
+		a.offsetAddr(a.valueNode(instr), a.valueNode(instr.X),
+			a.offsetOf(mustDeref(instr.X.Type()), instr.Field))
+
+	case *ssa.IndexAddr:
+		a.offsetAddr(a.valueNode(instr), a.valueNode(instr.X), 1)
+
+	case *ssa.Field:
+		a.copy(a.valueNode(instr),
+			a.valueOffsetNode(instr.X, instr.Field),
+			a.sizeof(instr.Type()))
+
+	case *ssa.Index:
+		a.copy(a.valueNode(instr), 1+a.valueNode(instr.X), a.sizeof(instr.Type()))
+
+	case *ssa.Select:
+		recv := a.valueOffsetNode(instr, 2) // instr : (index, recvOk, recv0, ... recv_n-1)
+		for _, st := range instr.States {
+			elemSize := a.sizeof(st.Chan.Type().Underlying().(*types.Chan).Elem())
+			switch st.Dir {
+			case ast.RECV:
+				a.load(recv, a.valueNode(st.Chan), elemSize)
+				recv++
+
+			case ast.SEND:
+				a.store(a.valueNode(st.Chan), a.valueNode(st.Send), elemSize)
+			}
+		}
+
+	case *ssa.Ret:
+		results := a.funcResults(cgn.obj)
+		for _, r := range instr.Results {
+			sz := a.sizeof(r.Type())
+			a.copy(results, a.valueNode(r), sz)
+			results += nodeid(sz)
+		}
+
+	case *ssa.Send:
+		a.store(a.valueNode(instr.Chan), a.valueNode(instr.X), a.sizeof(instr.X.Type()))
+
+	case *ssa.Store:
+		a.store(a.valueNode(instr.Addr), a.valueNode(instr.Val), a.sizeof(instr.Val.Type()))
+
+	case *ssa.Alloc:
+		obj := a.nextNode()
+		a.addNodes(mustDeref(instr.Type()), "alloc")
+		a.endObject(obj, instr)
+		a.addressOf(a.valueNode(instr), obj)
+
+	case *ssa.MakeSlice:
+		obj := a.nextNode()
+		a.addNodes(sliceToArray(instr.Type()), "makeslice")
+		a.endObject(obj, instr)
+		a.addressOf(a.valueNode(instr), obj)
+
+	case *ssa.MakeChan:
+		obj := a.nextNode()
+		a.addNodes(instr.Type().Underlying().(*types.Chan).Elem(), "makechan")
+		a.endObject(obj, instr)
+		a.addressOf(a.valueNode(instr), obj)
+
+	case *ssa.MakeMap:
+		obj := a.nextNode()
+		tmap := instr.Type().Underlying().(*types.Map)
+		a.addNodes(tmap.Key(), "makemap.key")
+		a.addNodes(tmap.Elem(), "makemap.value")
+		a.endObject(obj, instr)
+		a.addressOf(a.valueNode(instr), obj)
+
+	case *ssa.MakeInterface:
+		tConc := instr.X.Type()
+		// Create nodes and constraints for all methods of the type.
+		// Ascertaining which will be needed is undecidable in general.
+		mset := tConc.MethodSet()
+		for i, n := 0, mset.Len(); i < n; i++ {
+			a.valueNode(a.prog.Method(mset.At(i)))
+		}
+		obj := a.addOneNode(tConc, "iface.conctype", nil) // NB: type may be non-scalar!
+		vnode := a.addNodes(tConc, "iface.value")
+		a.endObject(obj, instr)
+		a.nodes[obj].flags |= ntInterface
+		// Copy the value into it, if nontrivial.
+		if x := a.valueNode(instr.X); x != 0 {
+			a.copy(vnode, x, a.sizeof(tConc))
+		}
+		a.addressOf(a.valueNode(instr), obj)
+
+	case *ssa.ChangeInterface:
+		a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
+
+	case *ssa.TypeAssert:
+		dst, src := a.valueNode(instr), a.valueNode(instr.X)
+		a.addConstraint(&typeAssertConstraint{instr.AssertedType, dst, src})
+
+	case *ssa.Slice:
+		a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
+
+	case *ssa.If, *ssa.Jump:
+		// no-op.
+
+	case *ssa.Phi:
+		sz := a.sizeof(instr.Type())
+		for _, e := range instr.Edges {
+			a.copy(a.valueNode(instr), a.valueNode(e), sz)
+		}
+
+	case *ssa.MakeClosure:
+		fn := instr.Fn.(*ssa.Function)
+		a.copy(a.valueNode(instr), a.valueNode(fn), 1)
+		// Free variables are treated like global variables.
+		for i, b := range instr.Bindings {
+			a.copy(a.valueNode(fn.FreeVars[i]), a.valueNode(b), a.sizeof(b.Type()))
+		}
+
+	case *ssa.RunDefers:
+		// The analysis is flow insensitive, so we just "call"
+		// defers as we encounter them.
+
+	case *ssa.Range:
+		// Do nothing.  Next{Iter: *ssa.Range} handles this case.
+
+	case *ssa.Next:
+		if !instr.IsString { // map
+			// Assumes that Next is always directly applied to a Range result.
+			theMap := instr.Iter.(*ssa.Range).X
+			tMap := theMap.Type().Underlying().(*types.Map)
+			ksize := a.sizeof(tMap.Key())
+			vsize := a.sizeof(tMap.Elem())
+
+			// Load from the map's (k,v) into the tuple's (ok, k, v).
+			a.load(a.valueNode(instr)+1, a.valueNode(theMap), ksize+vsize)
+		}
+
+	case *ssa.Lookup:
+		if tMap, ok := instr.X.Type().Underlying().(*types.Map); ok {
+			// CommaOk can be ignored: field 0 is a no-op.
+			ksize := a.sizeof(tMap.Key())
+			vsize := a.sizeof(tMap.Elem())
+			a.loadOffset(a.valueNode(instr), a.valueNode(instr.X), ksize, vsize)
+		}
+
+	case *ssa.MapUpdate:
+		tmap := instr.Map.Type().Underlying().(*types.Map)
+		ksize := a.sizeof(tmap.Key())
+		vsize := a.sizeof(tmap.Elem())
+		m := a.valueNode(instr.Map)
+		a.store(m, a.valueNode(instr.Key), ksize)
+		a.storeOffset(m, a.valueNode(instr.Value), ksize, vsize)
+
+	case *ssa.Panic:
+		a.copy(a.panicNode, a.valueNode(instr.X), 1)
+
+	default:
+		panic(fmt.Sprintf("unimplemented: %T", instr))
+	}
+}
+
+// genRootCalls generates the synthetic root of the callgraph and the
+// initial calls from it to the analysis scope, such as main, a test
+// or a library.
+//
+func (a *analysis) genRootCalls() *cgnode {
+	r := ssa.NewFunction("<root>", new(types.Signature), "root of callgraph")
+	r.Prog = a.prog // hack.
+	r.Enclosing = r // hack, so Function.String() doesn't crash
+	r.String()      // (asserts that it doesn't crash)
+	root := &cgnode{fn: r}
+
+	// For each main package, call main.init(), main.main().
+	for _, mainPkg := range a.config.Mains {
+		main := mainPkg.Func("main")
+		if main == nil {
+			panic(fmt.Sprintf("%s has no main function", mainPkg))
+		}
+
+		targets := a.addOneNode(main.Signature, "root.targets", nil)
+		site := &callsite{
+			caller:  root,
+			targets: targets,
+		}
+		a.callsites = append(a.callsites, site)
+		for _, fn := range [2]*ssa.Function{mainPkg.Func("init"), main} {
+			if a.log != nil {
+				fmt.Fprintf(a.log, "\troot call to %s:\n", fn)
+			}
+			a.copy(targets, a.valueNode(fn), 1)
+		}
+	}
+
+	return root
+}
+
+// genFunc generates constraints for function fn.
+func (a *analysis) genFunc(cgn *cgnode) {
+	fn := cgn.fn
+	if a.log != nil {
+		fmt.Fprintln(a.log)
+		fmt.Fprintln(a.log)
+		cgn.fn.DumpTo(a.log)
+	}
+
+	if impl := a.findIntrinsic(fn); impl != nil {
+		impl(a, cgn)
+		return
+	}
+
+	if fn.Blocks == nil {
+		// External function with no intrinsic treatment.
+		// We'll warn about calls to such functions at the end.
+		return
+	}
+
+	// The value nodes for the params are in the func object block.
+	params := a.funcParams(cgn.obj)
+	for _, p := range fn.Params {
+		// TODO(adonovan): record the context (cgn) too.
+		a.setValueNode(p, params)
+		params += nodeid(a.sizeof(p.Type()))
+	}
+
+	// Free variables are treated like global variables:
+	// the outer function sets them with MakeClosure;
+	// the inner function accesses them with Capture.
+
+	// Create value nodes for all value instructions.
+	// (Clobbers any previous nodes from same fn in different context.)
+	if a.log != nil {
+		fmt.Fprintln(a.log, "; Creating instruction values")
+	}
+	for _, b := range fn.Blocks {
+		for _, instr := range b.Instrs {
+			switch instr := instr.(type) {
+			case *ssa.Range:
+				// do nothing: it has a funky type.
+
+			case ssa.Value:
+				var comment string
+				if a.log != nil {
+					comment = instr.Name()
+				}
+				id := a.addNodes(instr.Type(), comment)
+				// TODO(adonovan): record the context (cgn) too.
+				a.setValueNode(instr, id)
+			}
+		}
+	}
+
+	// Generate constraints for instructions.
+	for _, b := range fn.Blocks {
+		for _, instr := range b.Instrs {
+			a.genInstr(cgn, instr)
+		}
+	}
+
+	// (Instruction Values will hang around in the environment.)
+}
+
+// generate generates offline constraints for the entire program.
+// It returns the synthetic root of the callgraph.
+//
+func (a *analysis) generate() *cgnode {
+	// Create a dummy node since we use the nodeid 0 for
+	// non-pointerlike variables.
+	a.addNodes(tInvalid, "(zero)")
+
+	// Create the global node for panic values.
+	a.panicNode = a.addNodes(tEface, "panic")
+
+	root := a.genRootCalls()
+
+	// Generate constraints for entire program.
+	// (Actually just the RTA-reachable portion of the program.
+	// See Bacon & Sweeney, OOPSLA'96).
+	for len(a.genq) > 0 {
+		cgn := a.genq[0]
+		a.genq = a.genq[1:]
+		a.genFunc(cgn)
+	}
+
+	// Create a dummy node to avoid out-of-range indexing in case
+	// the last allocated type was of zero length.
+	a.addNodes(tInvalid, "(max)")
+
+	return root
+}
diff --git a/pointer/intrinsics.go b/pointer/intrinsics.go
new file mode 100644
index 0000000..6323023
--- /dev/null
+++ b/pointer/intrinsics.go
@@ -0,0 +1,276 @@
+package pointer
+
+// This package defines the treatment of intrinsics, i.e. library
+// functions requiring special analytical treatment.
+//
+// Most of these are C or assembly functions, but even some Go
+// functions require may special treatment if the analysis completely
+// replaces the implementation of an API such as reflection.
+
+// TODO(adonovan): support a means of writing analytic summaries in
+// the target code, so that users can summarise the effects of their
+// own C functions using a snippet of Go.
+
+import (
+	"code.google.com/p/go.tools/ssa"
+)
+
+// Instances of 'intrinsic' generate analysis constraints for calls to
+// intrinsic functions.
+type intrinsic func(a *analysis, cgn *cgnode)
+
+// Initialized in explicit init() to defeat (spurious) initialization
+// cycle error.
+var intrinsicsByName map[string]intrinsic
+
+func init() {
+	// Key strings are from Function.String().
+	// That little dot ۰ is an Arabic zero numeral (U+06F0),
+	// categories [Nd].
+	intrinsicsByName = map[string]intrinsic{
+		// reflect.Value methods.
+		// "(reflect.Value).Addr":      ext۰reflect۰Value۰Addr,
+		"(reflect.Value).Bool": ext۰NoEffect,
+		// "(reflect.Value).Bytes":     ext۰reflect۰Value۰Bytes,
+		// "(reflect.Value).Call":      ext۰reflect۰Value۰Call,
+		// "(reflect.Value).CallSlice": ext۰reflect۰Value۰CallSlice,
+		"(reflect.Value).CanAddr":      ext۰NoEffect,
+		"(reflect.Value).CanInterface": ext۰NoEffect,
+		"(reflect.Value).CanSet":       ext۰NoEffect,
+		"(reflect.Value).Cap":          ext۰NoEffect,
+		"(reflect.Value).Close":        ext۰NoEffect,
+		"(reflect.Value).Complex":      ext۰NoEffect,
+		// "(reflect.Value).Convert":   ext۰reflect۰Value۰Convert,
+		// "(reflect.Value).Elem":      ext۰reflect۰Value۰Elem,
+		// "(reflect.Value).Field":     ext۰reflect۰Value۰Field,
+		// "(reflect.Value).FieldByIndex":    ext۰reflect۰Value۰FieldByIndex,
+		// "(reflect.Value).FieldByName":     ext۰reflect۰Value۰FieldByName,
+		// "(reflect.Value).FieldByNameFunc": ext۰reflect۰Value۰FieldByNameFunc,
+		"(reflect.Value).Float": ext۰NoEffect,
+		// "(reflect.Value).Index":     ext۰reflect۰Value۰Index,
+		"(reflect.Value).Int": ext۰NoEffect,
+		// "(reflect.Value).Interface":  ext۰reflect۰Value۰Interface,
+		"(reflect.Value).InterfaceData": ext۰NoEffect,
+		"(reflect.Value).IsNil":         ext۰NoEffect,
+		"(reflect.Value).IsValid":       ext۰NoEffect,
+		"(reflect.Value).Kind":          ext۰NoEffect,
+		"(reflect.Value).Len":           ext۰NoEffect,
+		// "(reflect.Value).MapIndex":   ext۰reflect۰Value۰MapIndex,
+		// "(reflect.Value).MapKeys":    ext۰reflect۰Value۰MapKeys,
+		// "(reflect.Value).Method":     ext۰reflect۰Value۰Method,
+		// "(reflect.Value).MethodByName": ext۰reflect۰Value۰MethodByName,
+		"(reflect.Value).NumField":        ext۰NoEffect,
+		"(reflect.Value).NumMethod":       ext۰NoEffect,
+		"(reflect.Value).OverflowComplex": ext۰NoEffect,
+		"(reflect.Value).OverflowFloat":   ext۰NoEffect,
+		"(reflect.Value).OverflowInt":     ext۰NoEffect,
+		"(reflect.Value).OverflowUint":    ext۰NoEffect,
+		"(reflect.Value).Pointer":         ext۰NoEffect,
+		// "(reflect.Value).Set":          ext۰reflect۰Value۰Set,
+		"(reflect.Value).SetBool": ext۰NoEffect,
+		// "(reflect.Value).SetBytes":     ext۰reflect۰Value۰SetBytes,
+		"(reflect.Value).SetComplex": ext۰NoEffect,
+		"(reflect.Value).SetFloat":   ext۰NoEffect,
+		"(reflect.Value).SetInt":     ext۰NoEffect,
+		"(reflect.Value).SetLen":     ext۰NoEffect,
+		// "(reflect.Value).SetMapIndex":  ext۰reflect۰Value۰SetMapIndex,
+		// "(reflect.Value).SetPointer":   ext۰reflect۰Value۰SetPointer,
+		"(reflect.Value).SetString": ext۰NoEffect,
+		"(reflect.Value).SetUint":   ext۰NoEffect,
+		// "(reflect.Value).Slice":        ext۰reflect۰Value۰Slice,
+		"(reflect.Value).String":     ext۰NoEffect,
+		"(reflect.Value).Type":       ext۰NoEffect,
+		"(reflect.Value).Uint":       ext۰NoEffect,
+		"(reflect.Value).UnsafeAddr": ext۰NoEffect,
+
+		// Standalone reflect.* functions.
+		"reflect.Append":      ext۰NotYetImplemented,
+		"reflect.AppendSlice": ext۰NotYetImplemented,
+		"reflect.Copy":        ext۰NotYetImplemented,
+		// "reflect.ChanOf":    ext۰reflect۰ChanOf,
+		"reflect.DeepEqual": ext۰NoEffect,
+		// "reflect.Indirect":  ext۰reflect۰Indirect,
+		// "reflect.MakeChan":  ext۰reflect۰MakeChan,
+		"reflect.MakeFunc":  ext۰NotYetImplemented,
+		"reflect.MakeMap":   ext۰NotYetImplemented,
+		"reflect.MakeSlice": ext۰NotYetImplemented,
+		"reflect.MapOf":     ext۰NotYetImplemented,
+		// "reflect.New":   ext۰reflect۰New,
+		// "reflect.NewAt": ext۰reflect۰NewAt,
+		"reflect.PtrTo":   ext۰NotYetImplemented,
+		"reflect.Select":  ext۰NotYetImplemented,
+		"reflect.SliceOf": ext۰NotYetImplemented,
+		// "reflect.TypeOf":  ext۰reflect۰TypeOf,
+		// "reflect.ValueOf": ext۰reflect۰ValueOf,
+		// "reflect.Zero":    ext۰reflect۰Zero,
+		"reflect.init": ext۰NoEffect,
+
+		// *reflect.rtype methods
+		"(*reflect.rtype).Align":         ext۰NoEffect,
+		"(*reflect.rtype).AssignableTo":  ext۰NoEffect,
+		"(*reflect.rtype).Bits":          ext۰NoEffect,
+		"(*reflect.rtype).ChanDir":       ext۰NoEffect,
+		"(*reflect.rtype).ConvertibleTo": ext۰NoEffect,
+		// "(*reflect.rtype).Elem":       ext۰reflect۰rtype۰Elem,
+		"(*reflect.rtype).Field":      ext۰NotYetImplemented,
+		"(*reflect.rtype).FieldAlign": ext۰NoEffect,
+		// "(*reflect.rtype).FieldByIndex":    ext۰reflect۰rtype۰FieldByIndex,
+		// "(*reflect.rtype).FieldByName":     ext۰reflect۰rtype۰FieldByName,
+		// "(*reflect.rtype).FieldByNameFunc": ext۰reflect۰rtype۰FieldByNameFunc,
+		"(*reflect.rtype).Implements": ext۰NoEffect,
+		// "(*reflect.rtype).In":      ext۰reflect۰rtype۰In,
+		"(*reflect.rtype).IsVariadic": ext۰NoEffect,
+		// "(*reflect.rtype).Key":     ext۰reflect۰rtype۰Key,
+		"(*reflect.rtype).Kind":         ext۰NoEffect,
+		"(*reflect.rtype).Len":          ext۰NoEffect,
+		"(*reflect.rtype).Method":       ext۰NotYetImplemented,
+		"(*reflect.rtype).MethodByName": ext۰NotYetImplemented,
+		"(*reflect.rtype).Name":         ext۰NoEffect,
+		"(*reflect.rtype).NumField":     ext۰NoEffect,
+		"(*reflect.rtype).NumIn":        ext۰NoEffect,
+		"(*reflect.rtype).NumMethod":    ext۰NoEffect,
+		"(*reflect.rtype).NumOut":       ext۰NoEffect,
+		// "(*reflect.rtype).Out":       ext۰reflect۰rtype۰Out,
+		"(*reflect.rtype).PkgPath": ext۰NoEffect,
+		"(*reflect.rtype).Size":    ext۰NoEffect,
+		"(*reflect.rtype).String":  ext۰NoEffect,
+
+		// Other packages.
+		"bytes.Equal":                           ext۰NoEffect,
+		"bytes.IndexByte":                       ext۰NoEffect,
+		"math.Abs":                              ext۰NoEffect,
+		"math.Acos":                             ext۰NoEffect,
+		"math.Asin":                             ext۰NoEffect,
+		"math.Atan":                             ext۰NoEffect,
+		"math.Atan2":                            ext۰NoEffect,
+		"math.Ceil":                             ext۰NoEffect,
+		"math.Cos":                              ext۰NoEffect,
+		"math.Dim":                              ext۰NoEffect,
+		"math.Exp":                              ext۰NoEffect,
+		"math.Exp2":                             ext۰NoEffect,
+		"math.Expm1":                            ext۰NoEffect,
+		"math.Float32bits":                      ext۰NoEffect,
+		"math.Float32frombits":                  ext۰NoEffect,
+		"math.Float64bits":                      ext۰NoEffect,
+		"math.Float64frombits":                  ext۰NoEffect,
+		"math.Floor":                            ext۰NoEffect,
+		"math.Frexp":                            ext۰NoEffect,
+		"math.Hypot":                            ext۰NoEffect,
+		"math.Ldexp":                            ext۰NoEffect,
+		"math.Log":                              ext۰NoEffect,
+		"math.Log10":                            ext۰NoEffect,
+		"math.Log1p":                            ext۰NoEffect,
+		"math.Log2":                             ext۰NoEffect,
+		"math.Max":                              ext۰NoEffect,
+		"math.Min":                              ext۰NoEffect,
+		"math.Mod":                              ext۰NoEffect,
+		"math.Modf":                             ext۰NoEffect,
+		"math.Remainder":                        ext۰NoEffect,
+		"math.Sin":                              ext۰NoEffect,
+		"math.Sincos":                           ext۰NoEffect,
+		"math.Sqrt":                             ext۰NoEffect,
+		"math.Tan":                              ext۰NoEffect,
+		"math.Trunc":                            ext۰NoEffect,
+		"math/big.addMulVVW":                    ext۰NoEffect,
+		"math/big.addVV":                        ext۰NoEffect,
+		"math/big.addVW":                        ext۰NoEffect,
+		"math/big.bitLen":                       ext۰NoEffect,
+		"math/big.divWVW":                       ext۰NoEffect,
+		"math/big.divWW":                        ext۰NoEffect,
+		"math/big.mulAddVWW":                    ext۰NoEffect,
+		"math/big.mulWW":                        ext۰NoEffect,
+		"math/big.shlVU":                        ext۰NoEffect,
+		"math/big.shrVU":                        ext۰NoEffect,
+		"math/big.subVV":                        ext۰NoEffect,
+		"math/big.subVW":                        ext۰NoEffect,
+		"os.epipecheck":                         ext۰NoEffect,
+		"runtime.BlockProfile":                  ext۰NoEffect,
+		"runtime.Breakpoint":                    ext۰NoEffect,
+		"runtime.CPUProfile":                    ext۰NotYetImplemented,
+		"runtime.Caller":                        ext۰NoEffect,
+		"runtime.FuncForPC":                     ext۰NotYetImplemented,
+		"runtime.GC":                            ext۰NoEffect,
+		"runtime.GOMAXPROCS":                    ext۰NoEffect,
+		"runtime.Goexit":                        ext۰NoEffect,
+		"runtime.GoroutineProfile":              ext۰NoEffect,
+		"runtime.Gosched":                       ext۰NoEffect,
+		"runtime.MemProfile":                    ext۰NoEffect,
+		"runtime.NumCPU":                        ext۰NoEffect,
+		"runtime.NumGoroutine":                  ext۰NoEffect,
+		"runtime.ReadMemStats":                  ext۰NoEffect,
+		"runtime.SetBlockProfileRate":           ext۰NoEffect,
+		"runtime.SetCPUProfileRate":             ext۰NoEffect,
+		"runtime.SetFinalizer":                  ext۰NotYetImplemented,
+		"runtime.Stack":                         ext۰NoEffect,
+		"runtime.ThreadCreateProfile":           ext۰NoEffect,
+		"runtime.funcentry_go":                  ext۰NoEffect,
+		"runtime.funcline_go":                   ext۰NoEffect,
+		"runtime.funcname_go":                   ext۰NoEffect,
+		"runtime.getgoroot":                     ext۰NoEffect,
+		"runtime/pprof.runtime_cyclesPerSecond": ext۰NoEffect,
+		"strings.IndexByte":                     ext۰NoEffect,
+		"sync.runtime_Semacquire":               ext۰NoEffect,
+		"sync.runtime_Semrelease":               ext۰NoEffect,
+		"sync.runtime_Syncsemcheck":             ext۰NoEffect,
+		"sync/atomic.AddInt32":                  ext۰NoEffect,
+		"sync/atomic.CompareAndSwapInt32":       ext۰NoEffect,
+		"sync/atomic.LoadInt32":                 ext۰NoEffect,
+		"sync/atomic.LoadUint32":                ext۰NoEffect,
+		"sync/atomic.StoreInt32":                ext۰NoEffect,
+		"sync/atomic.StoreUint32":               ext۰NoEffect,
+		"syscall.Close":                         ext۰NoEffect,
+		"syscall.Exit":                          ext۰NoEffect,
+		"syscall.Getpid":                        ext۰NoEffect,
+		"syscall.Getwd":                         ext۰NoEffect,
+		"syscall.Kill":                          ext۰NoEffect,
+		"syscall.RawSyscall":                    ext۰NoEffect,
+		"syscall.Syscall":                       ext۰NoEffect,
+		"syscall.Syscall6":                      ext۰NoEffect,
+		"time.Sleep":                            ext۰NoEffect,
+		"time.now":                              ext۰NoEffect,
+		"time.startTimer":                       ext۰NoEffect,
+		"time.stopTimer":                        ext۰NoEffect,
+	}
+}
+
+// findIntrinsic returns the constraint generation function for an
+// intrinsic function fn, or nil if the function should be handled normally.
+//
+func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic {
+	// Consult the *Function-keyed cache.
+	// A cached nil indicates a normal non-intrinsic function.
+	impl, ok := a.intrinsics[fn]
+	if !ok {
+		impl = intrinsicsByName[fn.String()] // may be nil
+
+		// Ensure all "reflect" code is treated intrinsically.
+		if impl == nil && fn.Pkg != nil && a.reflectValueObj != nil && a.reflectValueObj.Pkg() == fn.Pkg.Object {
+			impl = ext۰NotYetImplemented
+		}
+
+		a.intrinsics[fn] = impl
+	}
+	return impl
+}
+
+// A trivial intrinsic suitable for any function that does not:
+// 1) induce aliases between its arguments or any global variables;
+// 2) call any functions; or
+// 3) create any labels.
+//
+// We sometimes violate condition #3 if the function creates only
+// non-function labels, as the control-flow graph is still sound.
+//
+func ext۰NoEffect(a *analysis, cgn *cgnode) {}
+
+func ext۰NotYetImplemented(a *analysis, cgn *cgnode) {
+	// TODO(adonovan): enable this warning when we've implemented
+	// enough that it's not unbearably annoying.
+	// a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
+}
+
+// We'll model reflect.Value as an interface{} containing pointers.
+// We must use pointers since some reflect.Values (those derived by
+// Field, Elem, etc) are abstractions of lvalues, not rvalues, and
+// mutations via Set are reflected in the underlying value.  (We could
+// represent it as a union of lvalue and rvalue but that's more work.)
diff --git a/pointer/labels.go b/pointer/labels.go
new file mode 100644
index 0000000..d24f9fd
--- /dev/null
+++ b/pointer/labels.go
@@ -0,0 +1,67 @@
+package pointer
+
+import (
+	"fmt"
+	"go/token"
+	"strings"
+
+	"code.google.com/p/go.tools/ssa"
+)
+
+// A Label is an abstract location or an instruction that allocates memory.
+// A points-to set is (conceptually) a set of labels.
+//
+// (This is basically a pair of a Value that allocates an object and a
+// subelement indicator within that object.)
+//
+// TODO(adonovan): local labels should include their context (CallGraphNode).
+//
+type Label struct {
+	Value      ssa.Value
+	subelement *fieldInfo // e.g. ".a.b[*].c"
+}
+
+func (l *Label) Pos() token.Pos {
+	if l.Value != nil {
+		return l.Value.Pos()
+	}
+	return token.NoPos
+}
+
+func (l *Label) String() string {
+	var s string
+	switch v := l.Value.(type) {
+	case *ssa.Function, *ssa.Global:
+		s = v.String()
+
+	case *ssa.Const:
+		s = v.Name()
+
+	case *ssa.Alloc:
+		s = v.Comment
+		if s == "" {
+			s = "alloc"
+		}
+
+	case *ssa.Call:
+		// Currently only calls to append can allocate objects.
+		if v.Call.Value.(*ssa.Builtin).Object().Name() != "append" {
+			panic("unhandled *ssa.Call label: " + v.Name())
+		}
+		s = "append"
+
+	case *ssa.MakeMap, *ssa.MakeChan, *ssa.MakeSlice, *ssa.Convert:
+		s = strings.ToLower(strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa."))
+
+	case *ssa.MakeInterface:
+		// MakeInterface is usually implicit in Go source (so
+		// Pos()==0), and interfaces objects may be allocated
+		// synthetically (so no *MakeInterface data).
+		s = "makeinterface:" + v.X.Type().String()
+
+	default:
+		panic(fmt.Sprintf("unhandled Label.Value type: %T", v))
+	}
+
+	return s + l.subelement.path()
+}
diff --git a/pointer/pointer_test.go b/pointer/pointer_test.go
new file mode 100644
index 0000000..b86abe6
--- /dev/null
+++ b/pointer/pointer_test.go
@@ -0,0 +1,567 @@
+package pointer_test
+
+// This test uses 'expectation' comments embedded within testdata/*.go
+// files to specify the expected pointer analysis behaviour.
+// See below for grammar.
+
+import (
+	"bytes"
+	"fmt"
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"io/ioutil"
+	"os"
+	"regexp"
+	"strconv"
+	"strings"
+	"testing"
+
+	"code.google.com/p/go.tools/go/types"
+	"code.google.com/p/go.tools/go/types/typemap"
+	"code.google.com/p/go.tools/importer"
+	"code.google.com/p/go.tools/pointer"
+	"code.google.com/p/go.tools/ssa"
+)
+
+var inputs = []string{
+	// Currently debugging:
+	// "testdata/tmp.go",
+
+	// Working:
+	"testdata/another.go",
+	"testdata/arrays.go",
+	"testdata/channels.go",
+	"testdata/context.go",
+	"testdata/conv.go",
+	"testdata/flow.go",
+	"testdata/fmtexcerpt.go",
+	"testdata/func.go",
+	"testdata/hello.go",
+	"testdata/interfaces.go",
+	"testdata/maps.go",
+	"testdata/panic.go",
+	"testdata/recur.go",
+	"testdata/structs.go",
+	"testdata/a_test.go",
+
+	// TODO(adonovan): get these tests (of reflection) passing.
+	// (The tests are mostly sound since they were used for a
+	// previous implementation.)
+	// "testdata/funcreflect.go",
+	// "testdata/arrayreflect.go",
+	// "testdata/chanreflect.go",
+	// "testdata/finalizer.go",
+	// "testdata/reflect.go",
+	// "testdata/mapreflect.go",
+	// "testdata/structreflect.go",
+}
+
+// Expectation grammar:
+//
+// @calls f -> g
+//
+//   A 'calls' expectation asserts that edge (f, g) appears in the
+//   callgraph.  f and g are notated as per Function.String(), which
+//   may contain spaces (e.g. promoted method in anon struct).
+//
+// @pointsto a | b | c
+//
+//   A 'pointsto' expectation asserts that the points-to set of its
+//   operand contains exactly the set of labels {a,b,c} notated as per
+//   labelString.
+//
+//   A 'pointsto' expectation must appear on the same line as a
+//   print(x) statement; the expectation's operand is x.
+//
+//   If one of the strings is "...", the expectation asserts that the
+//   points-to set at least the other labels.
+//
+//   We use '|' because label names may contain spaces, e.g.  methods
+//   of anonymous structs.
+//
+//   From a theoretical perspective, concrete types in interfaces are
+//   labels too, but they are represented differently and so have a
+//   different expectation, @concrete, below.
+//
+// @concrete t | u | v
+//
+//   A 'concrete' expectation asserts that the set of possible dynamic
+//   types of its interface operand is exactly {t,u,v}, notated per
+//   go/types.Type.String(). In other words, it asserts that the type
+//   component of the interface may point to that set of concrete type
+//   literals.
+//
+//   A 'concrete' expectation must appear on the same line as a
+//   print(x) statement; the expectation's operand is x.
+//
+//   If one of the strings is "...", the expectation asserts that the
+//   interface's type may point to at least the  other concrete types.
+//
+//   We use '|' because type names may contain spaces.
+//
+// @warning "regexp"
+//
+//   A 'warning' expectation asserts that the analysis issues a
+//   warning that matches the regular expression within the string
+//   literal.
+//
+// @line id
+//
+//   A line directive associates the name "id" with the current
+//   file:line.  The string form of labels will use this id instead of
+//   a file:line, making @pointsto expectations more robust against
+//   perturbations in the source file.
+//   (NB, anon functions still include line numbers.)
+//
+type expectation struct {
+	kind     string // "pointsto" | "concrete" | "calls" | "warning"
+	filename string
+	linenum  int // source line number, 1-based
+	args     []string
+	types    []types.Type // for concrete
+}
+
+func (e *expectation) String() string {
+	return fmt.Sprintf("@%s[%s]", e.kind, strings.Join(e.args, " | "))
+}
+
+func (e *expectation) errorf(format string, args ...interface{}) {
+	fmt.Printf("%s:%d: ", e.filename, e.linenum)
+	fmt.Printf(format, args...)
+	fmt.Println()
+}
+
+func (e *expectation) needsProbe() bool {
+	return e.kind == "pointsto" || e.kind == "concrete"
+}
+
+// A record of a call to the built-in print() function.  Used for testing.
+type probe struct {
+	instr *ssa.CallCommon
+	arg0  pointer.Pointer // first argument to print
+}
+
+// Find probe (call to print(x)) of same source
+// file/line as expectation.
+func findProbe(prog *ssa.Program, probes []probe, e *expectation) *probe {
+	for _, p := range probes {
+		pos := prog.Fset.Position(p.instr.Pos())
+		if pos.Line == e.linenum && pos.Filename == e.filename {
+			// TODO(adonovan): send this to test log (display only on failure).
+			// fmt.Printf("%s:%d: info: found probe for %s: %s\n",
+			// 	e.filename, e.linenum, e, p.arg0) // debugging
+			return &p
+		}
+	}
+	return nil // e.g. analysis didn't reach this call
+}
+
+func doOneInput(input, filename string) bool {
+	impctx := &importer.Config{Loader: importer.MakeGoBuildLoader(nil)}
+	imp := importer.New(impctx)
+
+	// Parsing.
+	f, err := parser.ParseFile(imp.Fset, filename, input, parser.DeclarationErrors)
+	if err != nil {
+		// TODO(adonovan): err is a scanner error list;
+		// display all errors not just first?
+		fmt.Println(err.Error())
+		return false
+	}
+
+	// Type checking.
+	info := imp.CreateSourcePackage("main", []*ast.File{f})
+	if info.Err != nil {
+		fmt.Println(info.Err.Error())
+		return false
+	}
+
+	// SSA creation + building.
+	prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
+	for _, info := range imp.Packages {
+		prog.CreatePackage(info)
+	}
+	prog.BuildAll()
+
+	mainpkg := prog.Package(info.Pkg)
+	ptrmain := mainpkg // main package for the pointer analysis
+	if mainpkg.Func("main") == nil {
+		// No main function; assume it's a test.
+		ptrmain = prog.CreateTestMainPackage([]*ssa.Package{mainpkg})
+		fmt.Printf("%s: synthesized testmain package for test.\n", imp.Fset.Position(f.Package))
+	}
+
+	ok := true
+
+	lineMapping := make(map[string]string) // maps "file:line" to @line tag
+
+	// Parse expectations in this input.
+	var exps []*expectation
+	re := regexp.MustCompile("// *@([a-z]*) *(.*)$")
+	lines := strings.Split(input, "\n")
+	for linenum, line := range lines {
+		linenum++ // make it 1-based
+		if matches := re.FindAllStringSubmatch(line, -1); matches != nil {
+			match := matches[0]
+			kind, rest := match[1], match[2]
+			e := &expectation{kind: kind, filename: filename, linenum: linenum}
+
+			if kind == "line" {
+				if rest == "" {
+					ok = false
+					e.errorf("@%s expectation requires identifier", kind)
+				} else {
+					lineMapping[fmt.Sprintf("%s:%d", filename, linenum)] = rest
+				}
+				continue
+			}
+
+			if e.needsProbe() && !strings.Contains(line, "print(") {
+				ok = false
+				e.errorf("@%s expectation must follow call to print(x)", kind)
+				continue
+			}
+
+			switch kind {
+			case "pointsto":
+				e.args = split(rest, "|")
+
+			case "concrete":
+				for _, typstr := range split(rest, "|") {
+					var t types.Type = types.Typ[types.Invalid] // means "..."
+					if typstr != "..." {
+						texpr, err := parser.ParseExpr(typstr)
+						if err != nil {
+							ok = false
+							// Don't print err since its location is bad.
+							e.errorf("'%s' is not a valid type", typstr)
+							continue
+						}
+						t, _, err = types.EvalNode(imp.Fset, texpr, mainpkg.Object, mainpkg.Object.Scope())
+						if err != nil {
+							ok = false
+							// TODO Don't print err since its location is bad.
+							e.errorf("'%s' is not a valid type: %s", typstr, err)
+							continue
+						}
+					}
+					e.types = append(e.types, t)
+				}
+
+			case "calls":
+				e.args = split(rest, "->")
+				// TODO(adonovan): eagerly reject the
+				// expectation if fn doesn't denote
+				// existing function, rather than fail
+				// the expectation after analysis.
+				if len(e.args) != 2 {
+					ok = false
+					e.errorf("@calls expectation wants 'caller -> callee' arguments")
+					continue
+				}
+
+			case "warning":
+				lit, err := strconv.Unquote(strings.TrimSpace(rest))
+				if err != nil {
+					ok = false
+					e.errorf("couldn't parse @warning operand: %s", err.Error())
+					continue
+				}
+				e.args = append(e.args, lit)
+
+			default:
+				ok = false
+				e.errorf("unknown expectation kind: %s", e)
+				continue
+			}
+			exps = append(exps, e)
+		}
+	}
+
+	var probes []probe
+	var warnings []string
+	var log bytes.Buffer
+
+	callgraph := make(pointer.CallGraph)
+
+	// Run the analysis.
+	config := &pointer.Config{
+		Mains: []*ssa.Package{ptrmain},
+		Log:   &log,
+		Print: func(site *ssa.CallCommon, p pointer.Pointer) {
+			probes = append(probes, probe{site, p})
+		},
+		Call: callgraph.AddEdge,
+		Warn: func(pos token.Pos, format string, args ...interface{}) {
+			msg := fmt.Sprintf(format, args...)
+			fmt.Printf("%s: warning: %s\n", prog.Fset.Position(pos), msg)
+			warnings = append(warnings, msg)
+		},
+	}
+	pointer.Analyze(config)
+
+	// Print the log is there was an error or a panic.
+	complete := false
+	defer func() {
+		if !complete || !ok {
+			log.WriteTo(os.Stderr)
+		}
+	}()
+
+	// Check the expectations.
+	for _, e := range exps {
+		var pr *probe
+		if e.needsProbe() {
+			if pr = findProbe(prog, probes, e); pr == nil {
+				ok = false
+				e.errorf("unreachable print() statement has expectation %s", e)
+				continue
+			}
+			if pr.arg0 == nil {
+				ok = false
+				e.errorf("expectation on non-pointerlike operand: %s", pr.instr.Args[0].Type())
+				continue
+			}
+		}
+
+		switch e.kind {
+		case "pointsto":
+			if !checkPointsToExpectation(e, pr, lineMapping, prog) {
+				ok = false
+			}
+
+		case "concrete":
+			if !checkConcreteExpectation(e, pr) {
+				ok = false
+			}
+
+		case "calls":
+			if !checkCallsExpectation(prog, e, callgraph) {
+				ok = false
+			}
+
+		case "warning":
+			if !checkWarningExpectation(prog, e, warnings) {
+				ok = false
+			}
+		}
+	}
+
+	complete = true
+
+	// ok = false // debugging: uncomment to always see log
+
+	return ok
+}
+
+func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Program) string {
+	// Functions and Globals need no pos suffix.
+	switch l.Value.(type) {
+	case *ssa.Function, *ssa.Global:
+		return l.String()
+	}
+
+	str := l.String()
+	if pos := l.Value.Pos(); pos != 0 {
+		// Append the position, using a @line tag instead of a line number, if defined.
+		posn := prog.Fset.Position(l.Value.Pos())
+		s := fmt.Sprintf("%s:%d", posn.Filename, posn.Line)
+		if tag, ok := lineMapping[s]; ok {
+			return fmt.Sprintf("%s@%s:%d", str, tag, posn.Column)
+		}
+		str = fmt.Sprintf("%s@%s", str, posn)
+	}
+	return str
+}
+
+func checkPointsToExpectation(e *expectation, pr *probe, lineMapping map[string]string, prog *ssa.Program) bool {
+	expected := make(map[string]struct{})
+	surplus := make(map[string]struct{})
+	exact := true
+	for _, g := range e.args {
+		if g == "..." {
+			exact = false
+			continue
+		}
+		expected[g] = struct{}{}
+	}
+	// Find the set of labels that the probe's
+	// argument (x in print(x)) may point to.
+	for _, label := range pr.arg0.PointsTo().Labels() {
+		name := labelString(label, lineMapping, prog)
+		if _, ok := expected[name]; ok {
+			delete(expected, name)
+		} else if exact {
+			surplus[name] = struct{}{}
+		}
+	}
+	// Report set difference:
+	ok := true
+	if len(expected) > 0 {
+		ok = false
+		e.errorf("value does not alias these expected labels: %s", join(expected))
+	}
+	if len(surplus) > 0 {
+		ok = false
+		e.errorf("value may additionally alias these labels: %s", join(surplus))
+	}
+	return ok
+}
+
+// underlying returns the underlying type of typ.  Copied from go/types.
+func underlyingType(typ types.Type) types.Type {
+	if typ, ok := typ.(*types.Named); ok {
+		return typ.Underlying() // underlying types are never NamedTypes
+	}
+	if typ == nil {
+		panic("underlying(nil)")
+	}
+	return typ
+}
+
+func checkConcreteExpectation(e *expectation, pr *probe) bool {
+	var expected typemap.M
+	var surplus typemap.M
+	exact := true
+	for _, g := range e.types {
+		if g == types.Typ[types.Invalid] {
+			exact = false
+			continue
+		}
+		expected.Set(g, struct{}{})
+	}
+
+	switch t := underlyingType(pr.instr.Args[0].Type()).(type) {
+	case *types.Interface:
+		// ok
+	default:
+		e.errorf("@concrete expectation requires an interface-typed operand, got %s", t)
+		return false
+	}
+
+	// Find the set of concrete types that the probe's
+	// argument (x in print(x)) may contain.
+	for _, conc := range pr.arg0.PointsTo().ConcreteTypes().Keys() {
+		if expected.At(conc) != nil {
+			expected.Delete(conc)
+		} else if exact {
+			surplus.Set(conc, struct{}{})
+		}
+	}
+	// Report set difference:
+	ok := true
+	if expected.Len() > 0 {
+		ok = false
+		e.errorf("interface cannot contain these concrete types: %s", expected.KeysString())
+	}
+	if surplus.Len() > 0 {
+		ok = false
+		e.errorf("interface may additionally contain these concrete types: %s", surplus.KeysString())
+	}
+	return ok
+	return false
+}
+
+func checkCallsExpectation(prog *ssa.Program, e *expectation, callgraph pointer.CallGraph) bool {
+	// TODO(adonovan): this is inefficient and not robust against
+	// typos.  Better to convert strings to *Functions during
+	// expectation parsing (somehow).
+	for caller, callees := range callgraph {
+		if caller.Func().String() == e.args[0] {
+			found := make(map[string]struct{})
+			for callee := range callees {
+				s := callee.Func().String()
+				found[s] = struct{}{}
+				if s == e.args[1] {
+					return true // expectation satisfied
+				}
+			}
+			e.errorf("found no call from %s to %s, but only to %s",
+				e.args[0], e.args[1], join(found))
+			return false
+		}
+	}
+	e.errorf("didn't find any calls from %s", e.args[0])
+	return false
+}
+
+func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []string) bool {
+	// TODO(adonovan): check the position part of the warning too?
+	re, err := regexp.Compile(e.args[0])
+	if err != nil {
+		e.errorf("invalid regular expression in @warning expectation: %s", err.Error())
+		return false
+	}
+
+	if len(warnings) == 0 {
+		e.errorf("@warning %s expectation, but no warnings", strconv.Quote(e.args[0]))
+		return false
+	}
+
+	for _, warning := range warnings {
+		if re.MatchString(warning) {
+			return true
+		}
+	}
+
+	e.errorf("@warning %s expectation not satised; found these warnings though:", strconv.Quote(e.args[0]))
+	for _, warning := range warnings {
+		fmt.Println("\t", warning)
+	}
+	return false
+}
+
+func TestInput(t *testing.T) {
+	ok := true
+
+	wd, err := os.Getwd()
+	if err != nil {
+		t.Errorf("os.Getwd: %s", err.Error())
+		return
+	}
+
+	// 'go test' does a chdir so that relative paths in
+	// diagnostics no longer make sense relative to the invoking
+	// shell's cwd.  We print a special marker so that Emacs can
+	// make sense of them.
+	fmt.Fprintf(os.Stderr, "Entering directory `%s'\n", wd)
+
+	for _, filename := range inputs {
+		content, err := ioutil.ReadFile(filename)
+		if err != nil {
+			t.Errorf("couldn't read file '%s': %s", filename, err.Error())
+			continue
+		}
+
+		if !doOneInput(string(content), filename) {
+			ok = false
+		}
+	}
+	if !ok {
+		t.Fail()
+	}
+}
+
+// join joins the elements of set with " | "s.
+func join(set map[string]struct{}) string {
+	var buf bytes.Buffer
+	sep := ""
+	for name := range set {
+		buf.WriteString(sep)
+		sep = " | "
+		buf.WriteString(name)
+	}
+	return buf.String()
+}
+
+// split returns the list of sep-delimited non-empty strings in s.
+func split(s, sep string) (r []string) {
+	for _, elem := range strings.Split(s, sep) {
+		elem = strings.TrimSpace(elem)
+		if elem != "" {
+			r = append(r, elem)
+		}
+	}
+	return
+}
diff --git a/pointer/print.go b/pointer/print.go
new file mode 100644
index 0000000..78d45ca
--- /dev/null
+++ b/pointer/print.go
@@ -0,0 +1,35 @@
+package pointer
+
+import "fmt"
+
+func (c *addrConstraint) String() string {
+	return fmt.Sprintf("addr n%d <- {&n%d}", c.dst, c.src)
+}
+
+func (c *copyConstraint) String() string {
+	return fmt.Sprintf("copy n%d <- n%d", c.dst, c.src)
+}
+
+func (c *loadConstraint) String() string {
+	return fmt.Sprintf("load n%d <- n%d[%d]", c.dst, c.src, c.offset)
+}
+
+func (c *storeConstraint) String() string {
+	return fmt.Sprintf("store n%d[%d] <- n%d", c.dst, c.offset, c.src)
+}
+
+func (c *offsetAddrConstraint) String() string {
+	return fmt.Sprintf("offsetAddr n%d <- n%d.#%d", c.dst, c.src, c.offset)
+}
+
+func (c *typeAssertConstraint) String() string {
+	return fmt.Sprintf("typeAssert n%d <- n%d.(%s)", c.dst, c.src, c.typ)
+}
+
+func (c *invokeConstraint) String() string {
+	return fmt.Sprintf("invoke n%d.%s(n%d ...)", c.iface, c.method.Name(), c.params+1)
+}
+
+func (n nodeid) String() string {
+	return fmt.Sprintf("n%d", n)
+}
diff --git a/pointer/solve.go b/pointer/solve.go
new file mode 100644
index 0000000..34916ca
--- /dev/null
+++ b/pointer/solve.go
@@ -0,0 +1,262 @@
+package pointer
+
+// This file defines a naive Andersen-style solver for the inclusion
+// constraint system.
+
+import (
+	"fmt"
+
+	"code.google.com/p/go.tools/go/types"
+)
+
+func (a *analysis) solve() {
+	// Initialize points-to sets and complex constraint sets.
+	for _, c := range a.constraints {
+		c.init(a)
+	}
+	a.constraints = nil // aid GC
+
+	work := a.work
+
+	// Now we've initialized all constraints, we populate the
+	// worklist with nodes that point to something initially (due
+	// to addrConstraints) and have other constraints attached.
+	for id, n := range a.nodes {
+		if len(n.pts) > 0 && (n.copyTo != nil || n.complex != nil) {
+			if a.log != nil {
+				fmt.Fprintf(a.log, "Adding to worklist n%d\n", id)
+			}
+			a.addWork(nodeid(id))
+		}
+	}
+	work.swap()
+
+	// Solver main loop.
+	for round := 1; ; round++ {
+		if work.swap() {
+			if a.log != nil {
+				fmt.Fprintf(a.log, "Solving, round %d\n", round)
+			}
+
+			// Next iteration.
+			if work.empty() {
+				break // done
+			}
+		}
+
+		id := work.take()
+		n := a.nodes[id]
+
+		if a.log != nil {
+			fmt.Fprintf(a.log, "\tnode n%d\n", id)
+		}
+
+		// Difference propagation.
+		delta := n.pts.diff(n.prevPts)
+		if delta == nil {
+			continue
+		}
+		n.prevPts = n.pts.clone()
+
+		// Process complex constraints dependent on n.
+		for c := range n.complex {
+			if a.log != nil {
+				fmt.Fprintf(a.log, "\t\tconstraint %s\n", c)
+			}
+			c.solve(a, n, delta)
+		}
+
+		// Process copy constraints.
+		var copySeen nodeset
+		for mid := range n.copyTo {
+			if copySeen.add(mid) {
+				if a.nodes[mid].pts.addAll(delta) {
+					a.addWork(mid)
+				}
+			}
+		}
+
+		if a.log != nil {
+			fmt.Fprintf(a.log, "\t\tpts(n%d) = %s\n", id, n.pts)
+		}
+	}
+
+	if a.log != nil {
+		fmt.Fprintf(a.log, "Solver done\n")
+	}
+}
+
+func (a *analysis) addWork(id nodeid) {
+	a.work.add(id)
+	if a.log != nil {
+		fmt.Fprintf(a.log, "\t\twork: n%d\n", id)
+	}
+}
+
+func (c *addrConstraint) init(a *analysis) {
+	a.nodes[c.dst].pts.add(c.src)
+}
+func (c *copyConstraint) init(a *analysis) {
+	a.nodes[c.src].copyTo.add(c.dst)
+}
+
+// Complex constraints attach themselves to the relevant pointer node.
+
+func (c *storeConstraint) init(a *analysis) {
+	a.nodes[c.dst].complex.add(c)
+}
+func (c *loadConstraint) init(a *analysis) {
+	a.nodes[c.src].complex.add(c)
+}
+func (c *offsetAddrConstraint) init(a *analysis) {
+	a.nodes[c.src].complex.add(c)
+}
+func (c *typeAssertConstraint) init(a *analysis) {
+	a.nodes[c.src].complex.add(c)
+}
+func (c *invokeConstraint) init(a *analysis) {
+	a.nodes[c.iface].complex.add(c)
+}
+
+// onlineCopy adds a copy edge.  It is called online, i.e. during
+// solving, so it adds edges and pts members directly rather than by
+// instantiating a 'constraint'.
+//
+// The size of the copy is implicitly 1.
+// It returns true if pts(dst) changed.
+//
+func (a *analysis) onlineCopy(dst, src nodeid) bool {
+	if dst != src {
+		if nsrc := a.nodes[src]; nsrc.copyTo.add(dst) {
+			if a.log != nil {
+				fmt.Fprintf(a.log, "\t\t\tdynamic copy n%d <- n%d\n", dst, src)
+			}
+			return a.nodes[dst].pts.addAll(nsrc.pts)
+		}
+	}
+	return false
+}
+
+// Returns sizeof.
+// Implicitly adds nodes to worklist.
+func (a *analysis) onlineCopyN(dst, src nodeid, sizeof uint32) uint32 {
+	for i := uint32(0); i < sizeof; i++ {
+		if a.onlineCopy(dst, src) {
+			a.addWork(dst)
+		}
+		src++
+		dst++
+	}
+	return sizeof
+}
+
+func (c *loadConstraint) solve(a *analysis, n *node, delta nodeset) {
+	var changed bool
+	for k := range delta {
+		koff := k + nodeid(c.offset)
+		if a.onlineCopy(c.dst, koff) {
+			changed = true
+		}
+	}
+	if changed {
+		a.addWork(c.dst)
+	}
+}
+
+func (c *storeConstraint) solve(a *analysis, n *node, delta nodeset) {
+	for k := range delta {
+		koff := k + nodeid(c.offset)
+		if a.onlineCopy(koff, c.src) {
+			a.addWork(koff)
+		}
+	}
+}
+
+func (c *offsetAddrConstraint) solve(a *analysis, n *node, delta nodeset) {
+	dst := a.nodes[c.dst]
+	for k := range delta {
+		if dst.pts.add(k + nodeid(c.offset)) {
+			a.addWork(c.dst)
+		}
+	}
+}
+
+func (c *typeAssertConstraint) solve(a *analysis, n *node, delta nodeset) {
+	tIface, _ := c.typ.Underlying().(*types.Interface)
+
+	for ifaceObj := range delta {
+		ifaceValue, tConc := a.interfaceValue(ifaceObj)
+
+		if tIface != nil {
+			if types.IsAssignableTo(tConc, tIface) {
+				if a.nodes[c.dst].pts.add(ifaceObj) {
+					a.addWork(c.dst)
+				}
+			}
+		} else {
+			if types.IsIdentical(tConc, c.typ) {
+				// Copy entire payload to dst.
+				//
+				// TODO(adonovan): opt: if tConc is
+				// nonpointerlike we can skip this
+				// entire constraint, perhaps.  We
+				// only care about pointers among the
+				// fields.
+				a.onlineCopyN(c.dst, ifaceValue, a.sizeof(tConc))
+			}
+		}
+	}
+}
+
+func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) {
+	for ifaceObj := range delta {
+		ifaceValue, tConc := a.interfaceValue(ifaceObj)
+
+		// Look up the concrete method.
+		meth := tConc.MethodSet().Lookup(c.method.Pkg(), c.method.Name())
+		if meth == nil {
+			panic(fmt.Sprintf("n%d: type %s has no method %s (iface=n%d)",
+				c.iface, tConc, c.method, ifaceObj))
+		}
+		fn := a.prog.Method(meth)
+		if fn == nil {
+			panic(fmt.Sprintf("n%d: no ssa.Function for %s", c.iface, meth))
+		}
+		sig := fn.Signature
+
+		fnObj := a.funcObj[fn]
+
+		// Make callsite's fn variable point to identity of
+		// concrete method.  (There's no need to add it to
+		// worklist since it never has attached constraints.)
+		a.nodes[c.params].pts.add(fnObj)
+
+		// Extract value and connect to method's receiver.
+		// Copy payload to method's receiver param (arg0).
+		arg0 := a.funcParams(fnObj)
+		recvSize := a.sizeof(sig.Recv().Type())
+		a.onlineCopyN(arg0, ifaceValue, recvSize)
+
+		// Copy iface object payload to method receiver.
+		src := c.params + 1 // skip past identity
+		dst := arg0 + nodeid(recvSize)
+
+		// Copy caller's argument block to method formal parameters.
+		paramsSize := a.sizeof(sig.Params())
+		a.onlineCopyN(dst, src, paramsSize)
+		src += nodeid(paramsSize)
+		dst += nodeid(paramsSize)
+
+		// Copy method results to caller's result block.
+		resultsSize := a.sizeof(sig.Results())
+		a.onlineCopyN(src, dst, resultsSize)
+	}
+}
+
+func (c *addrConstraint) solve(a *analysis, n *node, delta nodeset) {
+	panic("addr is not a complex constraint")
+}
+
+func (c *copyConstraint) solve(a *analysis, n *node, delta nodeset) {
+	panic("copy is not a complex constraint")
+}
diff --git a/pointer/testdata/a_test.go b/pointer/testdata/a_test.go
new file mode 100644
index 0000000..3baa9ac
--- /dev/null
+++ b/pointer/testdata/a_test.go
@@ -0,0 +1,42 @@
+// +build ignore
+
+package a
+
+// This test exercises the synthesis of testmain packages for tests.
+// The test framework doesn't directly let us perform negative
+// assertions (i.e. that TestingQuux isn't called, or that its
+// parameter's PTS is empty) so this test is rather roundabout.
+
+import "testing"
+
+func log(f func(*testing.T)) {
+	// The PTS of f is the set of called tests.  TestingQuux is not present.
+	print(f) // @pointsto main.Test | main.TestFoo
+}
+
+func Test(t *testing.T) {
+	// Don't assert @pointsto(t) since its label contains a fragile line number.
+	log(Test)
+}
+
+func TestFoo(t *testing.T) {
+	// Don't assert @pointsto(t) since its label contains a fragile line number.
+	log(TestFoo)
+}
+
+func TestingQuux(t *testing.T) {
+	// We can't assert @pointsto(t) since this is dead code.
+	log(TestingQuux)
+}
+
+func BenchmarkFoo(b *testing.B) {
+}
+
+func ExampleBar() {
+}
+
+// Excludes TestingQuux.
+// @calls testing.tRunner -> main.Test
+// @calls testing.tRunner -> main.TestFoo
+// @calls testing.runExample -> main.ExampleBar
+// @calls (*testing.B).runN -> main.BenchmarkFoo
diff --git a/pointer/testdata/another.go b/pointer/testdata/another.go
new file mode 100644
index 0000000..90d4cd2
--- /dev/null
+++ b/pointer/testdata/another.go
@@ -0,0 +1,35 @@
+// +build ignore
+
+package main
+
+var unknown bool
+
+type S string
+
+func incr(x int) int { return x + 1 }
+
+func main() {
+	var i interface{}
+	i = 1
+	if unknown {
+		i = S("foo")
+	}
+	if unknown {
+		i = (func(int, int))(nil) // NB type compares equal to that below.
+	}
+	// Look, the test harness can handle equal-but-not-String-equal
+	// types because we parse types and using a typemap.
+	if unknown {
+		i = (func(x int, y int))(nil)
+	}
+	if unknown {
+		i = incr
+	}
+	print(i) // @concrete int | S | func(int, int) | func(int) int
+
+	// NB, an interface may never directly alias any global
+	// labels, even though it may contain pointers that do.
+	print(i)                 // @pointsto makeinterface:func(x int) int | makeinterface:func(x int, y int) | makeinterface:func(int, int) | makeinterface:int | makeinterface:main.S
+	print(i.(func(int) int)) // @pointsto main.incr
+	print(i.(S))             // @pointsto
+}
diff --git a/pointer/testdata/arrayreflect.go b/pointer/testdata/arrayreflect.go
new file mode 100644
index 0000000..06849d9
--- /dev/null
+++ b/pointer/testdata/arrayreflect.go
@@ -0,0 +1,108 @@
+// +build ignore
+
+package main
+
+import "reflect"
+
+// Test of arrays & slices with reflection.
+
+var a int
+
+func arrayreflect1() {
+	sl := make([]*int, 10) // @line ar1make
+	sl[0] = &a
+
+	srv := reflect.ValueOf(sl).Slice(0, 0)
+	print(srv.Interface())              // @concrete []*int
+	print(srv.Interface().([]*int))     // @pointsto makeslice@ar1make:12
+	print(srv.Interface().([]*int)[42]) // @pointsto main.a
+}
+
+func arrayreflect2() {
+	var arr [10]*int
+	sl := arr[:]
+	sl[0] = &a
+
+	srv := reflect.ValueOf(sl).Slice(0, 0)
+	print(srv.Interface())              // @concrete []*int
+	print(srv.Interface().([]*int))     // pointsto TODO
+	print(srv.Interface().([]*int)[42]) // @pointsto main.a
+}
+
+func arrayreflect3() {
+	srv := reflect.ValueOf("hi").Slice(0, 0)
+	print(srv.Interface()) // @concrete string
+
+	type S string
+	srv2 := reflect.ValueOf(S("hi")).Slice(0, 0)
+	print(srv2.Interface()) // @concrete main.S
+}
+
+func arrayreflect4() {
+	rv1 := reflect.ValueOf("hi")
+	rv2 := rv1 // backflow!
+	if unknown {
+		rv2 = reflect.ValueOf(123)
+	}
+	// We see backflow through the assignment above causing an
+	// imprecise result for rv1.  This is because the SSA builder
+	// doesn't yet lift structs (like reflect.Value) into
+	// registers so these are all loads/stores to the stack.
+	// Under Das's algorithm, the extra indirection results in
+	// (undirected) unification not (directed) flow edges.
+	// TODO(adonovan): precision: lift aggregates.
+	print(rv1.Interface()) // @concrete string | int
+	print(rv2.Interface()) // @concrete string | int
+}
+
+func arrayreflect5() {
+	sl1 := make([]byte, 0)
+	sl2 := make([]byte, 0)
+
+	srv := reflect.ValueOf(sl1)
+
+	print(srv.Interface())          // @concrete []byte
+	print(srv.Interface().([]byte)) // @pointsto makeslice@testdata/arrayreflect.go:62:13
+	print(srv.Bytes())              // @pointsto makeslice@testdata/arrayreflect.go:62:13
+
+	srv2 := reflect.ValueOf(123)
+	srv2.SetBytes(sl2)
+	print(srv2.Interface())          // @concrete []byte | int
+	print(srv2.Interface().([]byte)) // @pointsto makeslice@testdata/arrayreflect.go:63:13
+	print(srv2.Bytes())              // @pointsto makeslice@testdata/arrayreflect.go:63:13
+}
+
+func arrayreflect6() {
+	sl1 := []*bool{new(bool)}
+	sl2 := []*int{&a}
+
+	srv1 := reflect.ValueOf(sl1)
+	print(srv1.Index(42).Interface())         // @concrete *bool
+	print(srv1.Index(42).Interface().(*bool)) // @pointsto alloc@testdata/arrayreflect.go:79:20
+
+	srv2 := reflect.ValueOf(sl2)
+	print(srv2.Index(42).Interface())        // @concrete *int
+	print(srv2.Index(42).Interface().(*int)) // @pointsto main.a
+
+	p1 := &sl1[0]
+	p2 := &sl2[0]
+
+	prv1 := reflect.ValueOf(p1)
+	print(prv1.Elem().Interface())         // @concrete *bool
+	print(prv1.Elem().Interface().(*bool)) // @pointsto alloc@testdata/arrayreflect.go:79:20
+
+	prv2 := reflect.ValueOf(p2)
+	print(prv2.Elem().Interface())        // @concrete *int
+	print(prv2.Elem().Interface().(*int)) // @pointsto main.a
+}
+
+func main() {
+	arrayreflect1()
+	arrayreflect2()
+	arrayreflect3()
+	arrayreflect4()
+	arrayreflect5()
+	arrayreflect6()
+}
+
+var unknown bool
diff --git a/pointer/testdata/arrays.go b/pointer/testdata/arrays.go
new file mode 100644
index 0000000..ae0ae39
--- /dev/null
+++ b/pointer/testdata/arrays.go
@@ -0,0 +1,97 @@
+// +build ignore
+
+package main
+
+var unknown bool // defeat dead-code elimination
+
+var a, b int
+
+func array1() {
+	sliceA := make([]*int, 10) // @line a1make
+	sliceA[0] = &a
+
+	var sliceB []*int
+	sliceB = append(sliceB, &b) // @line a1append
+
+	print(sliceA)    // @pointsto makeslice@a1make:16
+	print(sliceA[0]) // @pointsto main.a
+
+	print(sliceB)      // @pointsto append@a1append:17
+	print(sliceB[100]) // @pointsto main.b
+}
+
+func array2() {
+	sliceA := make([]*int, 10) // @line a2make
+	sliceA[0] = &a
+
+	sliceB := sliceA[:]
+
+	print(sliceA)    // @pointsto makeslice@a2make:16
+	print(sliceA[0]) // @pointsto main.a
+
+	print(sliceB)    // @pointsto makeslice@a2make:16
+	print(sliceB[0]) // @pointsto main.a
+}
+
+func array3() {
+	a := []interface{}{"", 1}
+	b := []interface{}{true, func() {}}
+	print(a[0]) // @concrete string | int
+	print(b[0]) // @concrete bool | func()
+}
+
+// Test of append, copy, slice.
+func array4() {
+	var s2 struct { // @line a4L0
+		a [3]int
+		b struct{ c, d int }
+	}
+	var sl1 = make([]*int, 10) // @line a4make
+	var someint int            // @line a4L1
+	sl1[1] = &someint
+	sl2 := append(sl1, &s2.a[1]) // @line a4append1
+	print(sl1)                   // @pointsto makeslice@a4make:16
+	print(sl2)                   // @pointsto append@a4append1:15 | makeslice@a4make:16
+	print(sl1[0])                // @pointsto someint@a4L1:6 | s2.a[*]@a4L0:6
+	print(sl2[0])                // @pointsto someint@a4L1:6 | s2.a[*]@a4L0:6
+
+	// In z=append(x,y) we should observe flow from y[*] to x[*].
+	var sl3 = make([]*int, 10) // @line a4L2
+	_ = append(sl3, &s2.a[1])
+	print(sl3)    // @pointsto makeslice@a4L2:16
+	print(sl3[0]) // @pointsto s2.a[*]@a4L0:6
+
+	var sl4 = []*int{&a} // @line a4L3
+	sl4a := append(sl4)  // @line a4L4
+	print(sl4a)          // @pointsto slicelit@a4L3:18 | append@a4L4:16
+	print(&sl4a[0])      // @pointsto slicelit[*]@a4L3:18 | append[*]@a4L4:16
+	print(sl4a[0])       // @pointsto main.a
+
+	var sl5 = []*int{&b} // @line a4L5
+	copy(sl5, sl4)
+	print(sl5)     // @pointsto slicelit@a4L5:18
+	print(&sl5[0]) // @pointsto slicelit[*]@a4L5:18
+	print(sl5[0])  // @pointsto main.b | main.a
+
+	var sl6 = sl5[:0]
+	print(sl6)     // @pointsto slicelit@a4L5:18
+	print(&sl6[0]) // @pointsto slicelit[*]@a4L5:18
+	print(sl6[0])  // @pointsto main.b | main.a
+}
+
+func array5() {
+	var arr [2]*int
+	arr[0] = &a
+	arr[1] = &b
+
+	var n int
+	print(arr[n]) // @pointsto main.a | main.b
+}
+
+func main() {
+	array1()
+	array2()
+	array3()
+	array4()
+	array5()
+}
diff --git a/pointer/testdata/channels.go b/pointer/testdata/channels.go
new file mode 100644
index 0000000..b796255
--- /dev/null
+++ b/pointer/testdata/channels.go
@@ -0,0 +1,91 @@
+// +build ignore
+
+package main
+
+func incr(x int) int { return x + 1 }
+
+func decr(x int) int { return x - 1 }
+
+var unknown bool // defeat dead-code elimination
+
+func chan1() {
+	chA := make(chan func(int) int, 0) // @line c1makeA
+	chB := make(chan func(int) int, 0) // @line c1makeB
+	chA <- incr
+	chB <- decr
+	chB <- func(int) int { return 1 }
+
+	print(chA)   // @pointsto makechan@c1makeA:13
+	print(<-chA) // @pointsto main.incr
+
+	print(chB)   // @pointsto makechan@c1makeB:13
+	print(<-chB) // @pointsto main.decr | func@16.9
+}
+
+func chan2() {
+	chA := make(chan func(int) int, 0) // @line c2makeA
+	chB := make(chan func(int) int, 0) // @line c2makeB
+	chA <- incr
+	chB <- decr
+	chB <- func(int) int { return 1 }
+
+	// Channels flow together.
+	// Labelsets remain distinct but elements are merged.
+	chAB := chA
+	if unknown {
+		chAB = chB
+	}
+
+	print(chA)   // @pointsto makechan@c2makeA:13
+	print(<-chA) // @pointsto main.incr
+
+	print(chB)   // @pointsto makechan@c2makeB:13
+	print(<-chB) // @pointsto main.decr | func@30.9
+
+	print(chAB)   // @pointsto makechan@c2makeA:13 | makechan@c2makeB:13
+	print(<-chAB) // @pointsto main.incr | main.decr | func@30.9
+
+	(<-chA)(3)
+}
+
+// @calls main.chan2 -> main.incr
+
+func chan3() {
+	chA := make(chan func(int) int, 0) // @line c3makeA
+	chB := make(chan func(int) int, 0) // @line c3makeB
+	chA <- incr
+	chB <- decr
+	chB <- func(int) int { return 1 }
+	print(chA)   // @pointsto makechan@c3makeA:13
+	print(<-chA) // @pointsto main.incr
+	print(chB)   // @pointsto makechan@c3makeB:13
+	print(<-chB) // @pointsto main.decr | func@58.9
+
+	(<-chA)(3)
+}
+
+// @calls main.chan3 -> main.incr
+
+func chan4() {
+	chA := make(chan func(int) int, 0) // @line c4makeA
+	chB := make(chan func(int) int, 0) // @line c4makeB
+
+	select {
+	case chA <- incr:
+	case chB <- decr:
+	case a := <-chA:
+		print(a) // @pointsto main.incr
+	case b := <-chB:
+		print(b) // @pointsto main.decr
+	default:
+		print(chA) // @pointsto makechan@c4makeA:13
+		print(chB) // @pointsto makechan@c4makeB:13
+	}
+}
+
+func main() {
+	chan1()
+	chan2()
+	chan3()
+	chan4()
+}
diff --git a/pointer/testdata/chanreflect.go b/pointer/testdata/chanreflect.go
new file mode 100644
index 0000000..8e662cd
--- /dev/null
+++ b/pointer/testdata/chanreflect.go
@@ -0,0 +1,75 @@
+// +build ignore
+
+package main
+
+import "reflect"
+
+//
+// This test is very sensitive to line-number perturbations!
+
+// Test of channels with reflection.
+
+var a, b int
+
+func chanreflect1() {
+	ch := make(chan *int, 0)
+	crv := reflect.ValueOf(ch)
+	crv.Send(reflect.ValueOf(&a))
+	print(crv.Interface())             // @concrete chan *int
+	print(crv.Interface().(chan *int)) // @pointsto makechan@testdata/chanreflect.go:15:12
+	print(<-ch)                        // @pointsto main.a
+}
+
+func chanreflect2() {
+	ch := make(chan *int, 0)
+	ch <- &b
+	crv := reflect.ValueOf(ch)
+	r, _ := crv.Recv()
+	print(r.Interface())        // @concrete *int
+	print(r.Interface().(*int)) // @pointsto main.b
+}
+
+func chanOfRecv() {
+	// MakeChan(<-chan) is a no-op.
+	t := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(&a))
+	print(reflect.Zero(t).Interface())                      // @concrete <-chan *int
+	print(reflect.MakeChan(t, 0).Interface().(<-chan *int)) // @pointsto
+}
+
+func chanOfSend() {
+	// MakeChan(chan<-) is a no-op.
+	t := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(&a))
+	print(reflect.Zero(t).Interface())                      // @concrete chan<- *int
+	print(reflect.MakeChan(t, 0).Interface().(chan<- *int)) // @pointsto
+}
+
+func chanOfBoth() {
+	t := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(&a))
+	print(reflect.Zero(t).Interface()) // @concrete chan *int
+	ch := reflect.MakeChan(t, 0)
+	print(ch.Interface().(chan *int)) // @pointsto reflectMakechan@testdata/chanreflect.go:49:24
+	ch.Send(reflect.ValueOf(&b))
+	ch.Interface().(chan *int) <- &a
+	r, _ := ch.Recv()
+	print(r.Interface().(*int))         // @pointsto main.a | main.b
+	print(<-ch.Interface().(chan *int)) // @pointsto main.a | main.b
+}
+
+var unknownDir reflect.ChanDir // not a constant
+
+func chanOfUnknown() {
+	// Unknown channel direction: assume all three.
+	// MakeChan only works on the bi-di channel type.
+	t := reflect.ChanOf(unknownDir, reflect.TypeOf(&a))
+	print(reflect.Zero(t).Interface())        // @concrete <-chan *int | chan<- *int | chan *int
+	print(reflect.MakeChan(t, 0).Interface()) // @concrete chan *int
+}
+
+func main() {
+	chanreflect1()
+	chanreflect2()
+	chanOfRecv()
+	chanOfSend()
+	chanOfBoth()
+	chanOfUnknown()
+}
diff --git a/pointer/testdata/chanreflect1.go b/pointer/testdata/chanreflect1.go
new file mode 100644
index 0000000..4be8524
--- /dev/null
+++ b/pointer/testdata/chanreflect1.go
@@ -0,0 +1,35 @@
+// +build ignore
+
+package main
+
+import "reflect"
+
+//
+// This test is very sensitive to line-number perturbations!
+
+// Test of channels with reflection.
+
+var a, b int
+
+func chanreflect1() {
+	ch := make(chan *int, 0)
+	crv := reflect.ValueOf(ch)
+	crv.Send(reflect.ValueOf(&a))
+	print(crv.Interface())             // @concrete chan *int
+	print(crv.Interface().(chan *int)) // @pointsto makechan@testdata/chanreflect.go:15:12
+	print(<-ch)                        // @pointsto main.a
+}
+
+func chanreflect2() {
+	ch := make(chan *int, 0)
+	ch <- &b
+	crv := reflect.ValueOf(ch)
+	r, _ := crv.Recv()
+	print(r.Interface())        // @concrete *int
+	print(r.Interface().(*int)) // @pointsto main.b
+}
+
+func main() {
+	chanreflect1()
+	chanreflect2()
+}
diff --git a/pointer/testdata/context.go b/pointer/testdata/context.go
new file mode 100644
index 0000000..ed616e7
--- /dev/null
+++ b/pointer/testdata/context.go
@@ -0,0 +1,48 @@
+// +build ignore
+
+package main
+
+// Test of context-sensitive treatment of certain function calls,
+// e.g. static calls to simple accessor methods.
+
+var a, b int
+
+type T struct{ x *int }
+
+func (t *T) SetX(x *int) { t.x = x }
+func (t *T) GetX() *int  { return t.x }
+
+func context1() {
+	var t1, t2 T
+	t1.SetX(&a)
+	t2.SetX(&b)
+	print(t1.GetX()) // @pointsto main.a
+	print(t2.GetX()) // @pointsto main.b
+}
+
+func context2() {
+	id := func(x *int) *int {
+		print(x) // @pointsto main.a | main.b
+		return x
+	}
+	print(id(&a)) // @pointsto main.a
+	print(id(&b)) // @pointsto main.b
+
+	// Same again, but anon func has free vars.
+	var c int // @line context2c
+	id2 := func(x *int) (*int, *int) {
+		print(x) // @pointsto main.a | main.b
+		return x, &c
+	}
+	p, q := id2(&a)
+	print(p) // @pointsto main.a
+	print(q) // @pointsto c@context2c:6
+	r, s := id2(&b)
+	print(r) // @pointsto main.b
+	print(s) // @pointsto c@context2c:6
+}
+
+func main() {
+	context1()
+	context2()
+}
diff --git a/pointer/testdata/conv.go b/pointer/testdata/conv.go
new file mode 100644
index 0000000..49cc8ca
--- /dev/null
+++ b/pointer/testdata/conv.go
@@ -0,0 +1,61 @@
+// +build ignore
+
+package main
+
+import "unsafe"
+
+var a int
+
+func conv1() {
+	// Conversions of channel direction.
+	ch := make(chan int)    // @line c1make
+	print((<-chan int)(ch)) // @pointsto makechan@c1make:12
+	print((chan<- int)(ch)) // @pointsto makechan@c1make:12
+}
+
+func conv2() {
+	// []byte/[]rune literal
+	print([]byte("foo")) // @pointsto "foo":[]byte
+	print([]rune("bar")) // @pointsto "bar":[]rune
+
+	// string -> []byte/[]rune conversion
+	s := "foo"
+	ba := []byte(s) // @line c2ba
+	ra := []rune(s) // @line c2ra
+	print(ba)       // @pointsto convert@c2ba:14
+	print(ra)       // @pointsto convert@c2ra:14
+}
+
+func conv3() {
+	// Conversion of same underlying types.
+	type PI *int
+	pi := PI(&a)
+	print(pi) // @pointsto main.a
+
+	pint := (*int)(pi)
+	print(pint) // @pointsto main.a
+
+	// Conversions between pointers to identical base types.
+	var y *PI = &pi
+	var x **int = (**int)(y)
+	print(*x) // @pointsto main.a
+	print(*y) // @pointsto main.a
+	y = (*PI)(x)
+	print(*y) // @pointsto main.a
+}
+
+// @warning "main.conv4 contains an unsafe.Pointer conversion"
+func conv4() {
+	// Handling of unsafe.Pointer conversion is unsound:
+	// we lose the alias to main.a and get something like new(int) instead.
+	// We require users to provide aliasing summaries.
+	p := (*int)(unsafe.Pointer(&a)) // @line c2p
+	print(p)                        // @pointsto convert@c2p:13
+}
+
+func main() {
+	conv1()
+	conv2()
+	conv3()
+	conv4()
+}
diff --git a/pointer/testdata/finalizer.go b/pointer/testdata/finalizer.go
new file mode 100644
index 0000000..c2709b5
--- /dev/null
+++ b/pointer/testdata/finalizer.go
@@ -0,0 +1,70 @@
+package main
+
+import "runtime"
+
+func final1a(x *int) int {
+	print(x) // @pointsto alloc@newint:10
+	return *x
+}
+
+func final1b(x *bool) {
+	print(x) // @pointsto
+}
+
+func setfinalizer1() {
+	x := new(int)                    // @line newint
+	runtime.SetFinalizer(x, final1a) // ok: final1a's result is ignored
+	runtime.SetFinalizer(x, final1b) // param type mismatch: no effect
+}
+
+// @calls runtime.SetFinalizer -> main.final1a
+// @calls main.setfinalizer1 -> runtime.SetFinalizer
+
+func final2a(x *bool) {
+	print(x) // @pointsto alloc@newbool1:10 | alloc@newbool2:10
+}
+
+func final2b(x *bool) {
+	print(x) // @pointsto alloc@newbool1:10 | alloc@newbool2:10
+}
+
+func setfinalizer2() {
+	x := new(bool) // @line newbool1
+	f := final2a
+	if unknown {
+		x = new(bool) // @line newbool2
+		f = final2b
+	}
+	runtime.SetFinalizer(x, f)
+}
+
+// @calls runtime.SetFinalizer -> main.final2a
+// @calls runtime.SetFinalizer -> main.final2b
+// @calls main.setfinalizer2 -> runtime.SetFinalizer
+
+// type T int
+
+// func (t *T) finalize() {
+// 	print(t) // #@pointsto x
+// }
+
+// func setfinalizer3() {
+// 	x := new(T)
+// 	runtime.SetFinalizer(x, (*T).finalize) // go/types gives wrong type to f.
+// }
+
+// #@calls runtime.SetFinalizer -> (*T) finalize
+
+func funcForPC() {
+	f := runtime.FuncForPC(0) // @line funcforpc
+	print(f)                  // @pointsto reflectAlloc@funcforpc:25
+}
+
+func main() {
+	setfinalizer1()
+	setfinalizer2()
+	// setfinalizer3()
+	funcForPC()
+}
+
+var unknown bool // defeat dead-code elimination
diff --git a/pointer/testdata/flow.go b/pointer/testdata/flow.go
new file mode 100644
index 0000000..06faff5
--- /dev/null
+++ b/pointer/testdata/flow.go
@@ -0,0 +1,63 @@
+// +build ignore
+
+package main
+
+// Demonstration of directionality of flow edges.
+
+func f1() {}
+func f2() {}
+
+var somepred bool
+
+// Tracking functions.
+func flow1() {
+	s := f1
+	p := f2
+	q := p
+	r := q
+	if somepred {
+		r = s
+	}
+	print(s) // @pointsto main.f1
+	print(p) // @pointsto main.f2
+	print(q) // @pointsto main.f2
+	print(r) // @pointsto main.f1 | main.f2
+}
+
+// Tracking concrete types in interfaces.
+func flow2() {
+	var s interface{} = 1
+	var p interface{} = "foo"
+	q := p
+	r := q
+	if somepred {
+		r = s
+	}
+	print(s) // @concrete int
+	print(p) // @concrete string
+	print(q) // @concrete string
+	print(r) // @concrete int | string
+}
+
+var g1, g2 int
+
+// Tracking addresses of globals.
+func flow3() {
+	s := &g1
+	p := &g2
+	q := p
+	r := q
+	if somepred {
+		r = s
+	}
+	print(s) // @pointsto main.g1
+	print(p) // @pointsto main.g2
+	print(q) // @pointsto main.g2
+	print(r) // @pointsto main.g2 | main.g1
+}
+
+func main() {
+	flow1()
+	flow2()
+	flow3()
+}
diff --git a/pointer/testdata/fmtexcerpt.go b/pointer/testdata/fmtexcerpt.go
new file mode 100644
index 0000000..d738ffb
--- /dev/null
+++ b/pointer/testdata/fmtexcerpt.go
@@ -0,0 +1,42 @@
+// +build ignore
+
+// This is a slice of the fmt package.
+
+package main
+
+type pp struct {
+	field interface{}
+}
+
+func newPrinter() *pp {
+	return new(pp)
+}
+
+func Fprintln(a ...interface{}) {
+	p := newPrinter()
+	p.doPrint(a, true, true)
+}
+
+func Println(a ...interface{}) {
+	Fprintln(a...)
+}
+
+func (p *pp) doPrint(a []interface{}, addspace, addnewline bool) {
+	print(a[0]) // @concrete S | string
+	stringer := a[0].(interface {
+		String() string
+	})
+
+	stringer.String()
+	print(stringer) // @concrete S
+}
+
+type S int
+
+func (S) String() string { return "" }
+
+func main() {
+	Println("Hello, World!", S(0))
+}
+
+// @calls (*main.pp).doPrint -> (main.S).String
diff --git a/pointer/testdata/func.go b/pointer/testdata/func.go
new file mode 100644
index 0000000..ec8883b
--- /dev/null
+++ b/pointer/testdata/func.go
@@ -0,0 +1,165 @@
+// +build ignore
+
+package main
+
+var a, b, c int
+
+var unknown bool // defeat dead-code elimination
+
+func func1() {
+	var h int // @line f1h
+	f := func(x *int) *int {
+		if unknown {
+			return &b
+		}
+		return x
+	}
+
+	// FV(g) = {f, h}
+	g := func(x *int) *int {
+		if unknown {
+			return &h
+		}
+		return f(x)
+	}
+
+	print(g(&a)) // @pointsto main.a | main.b | h@f1h:6
+	print(f(&a)) // @pointsto main.a | main.b
+	print(&a)    // @pointsto main.a
+}
+
+// @calls main.func1 -> func@19.7
+// @calls main.func1 -> func@11.7
+// @calls func@19.7 ->  func@11.7
+
+func func2() {
+	var x, y *int
+	defer func() {
+		x = &a
+	}()
+	go func() {
+		y = &b
+	}()
+	print(x) // @pointsto main.a
+	print(y) // @pointsto main.b
+}
+
+func func3() {
+	x, y := func() (x, y *int) {
+		x = &a
+		y = &b
+		if unknown {
+			return nil, &c
+		}
+		return
+	}()
+	print(x) // @pointsto main.a
+	print(y) // @pointsto main.b | main.c
+}
+
+func swap(x, y *int) (*int, *int) { // @line swap
+	print(&x) // @pointsto x@swap:11
+	print(x)  // @pointsto makeslice[*]@func4make:11
+	print(&y) // @pointsto y@swap:14
+	print(y)  // @pointsto j@f4j:5
+	return y, x
+}
+
+func func4() {
+	a := make([]int, 10) // @line func4make
+	i, j := 123, 456     // @line f4j
+	p, q := swap(&a[3], &j)
+	print(p) // @pointsto j@f4j:5
+	print(q) // @pointsto makeslice[*]@func4make:11
+
+	f := &b
+	print(f) // @pointsto main.b
+}
+
+type T int
+
+func (t *T) f(x *int) *int {
+	print(t) // @pointsto main.a
+	print(x) // @pointsto main.c
+	return &b
+}
+
+func (t *T) g(x *int) *int {
+	print(t) // @pointsto main.a
+	print(x) // @pointsto main.b
+	return &c
+}
+
+func (t *T) h(x *int) *int {
+	print(t) // @pointsto main.a
+	print(x) // @pointsto main.b
+	return &c
+}
+
+var h func(*T, *int) *int
+
+func func5() {
+	// Static call of method.
+	t := (*T)(&a)
+	print(t.f(&c)) // @pointsto main.b
+
+	// Static call of method as function
+	print((*T).g(t, &b)) // @pointsto main.c
+
+	// Dynamic call (not invoke) of method.
+	h = (*T).h
+	print(h(t, &b)) // @pointsto main.c
+}
+
+// @calls main.func5 -> (*main.T).f
+// @calls main.func5 -> (*main.T).g
+// @calls main.func5 -> (*main.T).h
+
+func func6() {
+	A := &a
+	f := func() *int {
+		return A // (free variable)
+	}
+	print(f()) // @pointsto main.a
+}
+
+// @calls main.func6 -> func@120.7
+
+type I interface {
+	f()
+}
+
+type D struct{}
+
+func (D) f() {}
+
+func func7() {
+	var i I = D{}
+	imethodClosure := i.f
+	imethodClosure()
+	// @calls main.func7 -> bound$(main.I).f
+	// @calls bound$(main.I).f -> (main.D).f
+
+	var d D
+	cmethodClosure := d.f
+	cmethodClosure()
+	// @calls main.func7 -> bound$(main.D).f
+	// @calls bound$(main.D).f ->(main.D).f
+
+	methodExpr := D.f
+	methodExpr(d)
+	// @calls main.func7 -> (main.D).f
+}
+
+func main() {
+	func1()
+	func2()
+	func3()
+	func4()
+	func5()
+	func6()
+	func7()
+}
+
+// @calls <root> -> main.main
+// @calls <root> -> main.init
diff --git a/pointer/testdata/funcreflect.go b/pointer/testdata/funcreflect.go
new file mode 100644
index 0000000..e7a8c45
--- /dev/null
+++ b/pointer/testdata/funcreflect.go
@@ -0,0 +1,30 @@
+// +build ignore
+
+package main
+
+//
+
+import "reflect"
+
+var a, b int
+
+func f(p *int) *int {
+	print(p) // @pointsto
+	return &b
+}
+
+func g(p *bool) {
+}
+
+func funcreflect1() {
+	rvf := reflect.ValueOf(f)
+	res := rvf.Call([]reflect.Value{reflect.ValueOf(&a)})
+	print(res[0].Interface())        // @concrete
+	print(res[0].Interface().(*int)) // @pointsto
+}
+
+// @calls main.funcreflect1 -> main.f
+
+func main() {
+	funcreflect1()
+}
diff --git a/pointer/testdata/hello.go b/pointer/testdata/hello.go
new file mode 100644
index 0000000..0cb08ff
--- /dev/null
+++ b/pointer/testdata/hello.go
@@ -0,0 +1,21 @@
+// +build ignore
+
+package main
+
+import "fmt"
+
+type S int
+
+var theS S
+
+func (s *S) String() string {
+	print(s) // @pointsto main.theS
+	return ""
+}
+
+func main() {
+	fmt.Println("Hello, World!", &theS)
+}
+
+// @calls main.main               -> fmt.Println
+// @calls (*fmt.pp).handleMethods -> (*main.S).String
diff --git a/pointer/testdata/interfaces.go b/pointer/testdata/interfaces.go
new file mode 100644
index 0000000..ed08a16
--- /dev/null
+++ b/pointer/testdata/interfaces.go
@@ -0,0 +1,152 @@
+// +build ignore
+
+package main
+
+type I interface {
+	f()
+}
+
+type C int
+
+func (*C) f() {}
+
+type D struct{ ptr *int }
+
+func (D) f() {}
+
+type E struct{}
+
+func (*E) f() {}
+
+var a, b int
+
+var unknown bool // defeat dead-code elimination
+
+func interface1() {
+	var i interface{} = &a
+	var j interface{} = D{&b}
+	k := j
+	if unknown {
+		k = i
+	}
+
+	print(i) // @concrete *int
+	print(j) // @concrete D
+	print(k) // @concrete *int | D
+
+	print(i.(*int)) // @pointsto main.a
+	print(j.(*int)) // @pointsto
+	print(k.(*int)) // @pointsto main.a
+
+	print(i.(D).ptr) // @pointsto
+	print(j.(D).ptr) // @pointsto main.b
+	print(k.(D).ptr) // @pointsto main.b
+}
+
+func interface2() {
+	var i I = (*C)(&a)
+	var j I = D{&a}
+	k := j
+	if unknown {
+		k = i
+	}
+
+	print(i) // @concrete *C
+	print(j) // @concrete D
+	print(k) // @concrete *C | D
+	print(k) // @pointsto makeinterface:main.D | makeinterface:*main.C
+
+	k.f()
+	// @calls main.interface2 -> (*main.C).f
+	// @calls main.interface2 -> (main.D).f
+
+	print(i.(*C))    // @pointsto main.a
+	print(j.(D).ptr) // @pointsto main.a
+	print(k.(*C))    // @pointsto main.a
+
+	switch x := k.(type) {
+	case *C:
+		print(x) // @pointsto main.a
+	case D:
+		print(x.ptr) // @pointsto main.a
+	case *E:
+		print(x) // @pointsto
+	}
+}
+
+func interface3() {
+	// There should be no backflow of concrete types from the type-switch to x.
+	var x interface{} = 0
+	print(x) // @concrete int
+	switch y := x.(type) {
+	case int:
+	case string:
+	}
+}
+
+func interface4() {
+	var i interface{} = D{&a}
+	if unknown {
+		i = 123
+	}
+
+	print(i) // @concrete int | D
+
+	j := i.(I)       // interface narrowing type-assertion
+	print(j)         // @concrete D
+	print(j.(D).ptr) // @pointsto main.a
+
+	var l interface{} = j // interface widening assignment.
+	print(l)              // @concrete D
+	print(l.(D).ptr)      // @pointsto main.a
+
+	m := j.(interface{}) // interface widening type-assertion.
+	print(m)             // @concrete D
+	print(m.(D).ptr)     // @pointsto main.a
+}
+
+// Interface method calls and value flow:
+
+type J interface {
+	f(*int) *int
+}
+
+type P struct {
+	x int
+}
+
+func (p *P) f(pi *int) *int {
+	print(p)  // @pointsto p@i5p:6
+	print(pi) // @pointsto i@i5i:6
+	return &p.x
+}
+
+func interface5() {
+	var p P // @line i5p
+	var j J = &p
+	var i int      // @line i5i
+	print(j.f(&i)) // @pointsto p.x@i5p:6
+	print(&i)      // @pointsto i@i5i:6
+
+	print(j) // @pointsto makeinterface:*main.P
+}
+
+// @calls main.interface5 -> (*main.P).f
+
+func interface6() {
+	f := I.f
+	print(f) // @pointsto (main.I).f
+	f(new(struct{ D }))
+}
+
+// @calls main.interface6 -> (main.I).f
+// @calls (main.I).f -> (*struct{main.D}).f
+
+func main() {
+	interface1()
+	interface2()
+	interface3()
+	interface4()
+	interface5()
+	interface6()
+}
diff --git a/pointer/testdata/mapreflect.go b/pointer/testdata/mapreflect.go
new file mode 100644
index 0000000..5662bad
--- /dev/null
+++ b/pointer/testdata/mapreflect.go
@@ -0,0 +1,52 @@
+// +build ignore
+
+package main
+
+import "reflect"
+
+//
+// This test is very sensitive to line-number perturbations!
+
+// Test of maps with reflection.
+
+var a int
+var b bool
+
+func mapreflect1() {
+	m := make(map[*int]*bool)
+	m[&a] = &b
+
+	mrv := reflect.ValueOf(m)
+	print(mrv.Interface())                  // @concrete map[*int]*bool
+	print(mrv.Interface().(map[*int]*bool)) // @pointsto makemap@testdata/mapreflect.go:16:11
+
+	for _, k := range mrv.MapKeys() {
+		print(k.Interface())        // @concrete *int
+		print(k.Interface().(*int)) // @pointsto main.a
+
+		v := mrv.MapIndex(k)
+		print(v.Interface())         // @concrete *bool
+		print(v.Interface().(*bool)) // @pointsto main.b
+	}
+}
+
+func mapreflect2() {
+	m := make(map[*int]*bool)
+	mrv := reflect.ValueOf(m)
+	mrv.SetMapIndex(reflect.ValueOf(&a), reflect.ValueOf(&b))
+
+	print(m[nil]) // @pointsto main.b
+
+	for _, k := range mrv.MapKeys() {
+		print(k.Interface())        // @concrete *int
+		print(k.Interface().(*int)) // @pointsto main.a
+	}
+
+	print(reflect.Zero(reflect.TypeOf(m).Key()).Interface())  // @concrete *int
+	print(reflect.Zero(reflect.TypeOf(m).Elem()).Interface()) // @concrete *bool
+}
+
+func main() {
+	mapreflect1()
+	mapreflect2()
+}
diff --git a/pointer/testdata/maps.go b/pointer/testdata/maps.go
new file mode 100644
index 0000000..695b698
--- /dev/null
+++ b/pointer/testdata/maps.go
@@ -0,0 +1,51 @@
+// +build ignore
+
+package main
+
+// Test of maps.
+
+var a, b, c int
+
+func maps1() {
+	m1 := map[*int]*int{&a: &b} // @line m1m1
+	m2 := make(map[*int]*int)   // @line m1m2
+	m2[&b] = &a
+
+	print(m1[nil]) // @pointsto main.b | main.c
+	print(m2[nil]) // @pointsto main.a
+
+	print(m1) // @pointsto makemap@m1m1:21
+	print(m2) // @pointsto makemap@m1m2:12
+
+	m1[&b] = &c
+
+	for k, v := range m1 {
+		print(k) // @pointsto main.a | main.b
+		print(v) // @pointsto main.b | main.c
+	}
+
+	for k, v := range m2 {
+		print(k) // @pointsto main.b
+		print(v) // @pointsto main.a
+	}
+
+	// Lookup doesn't create any aliases.
+	print(m2[&c]) // @pointsto main.a
+	if m2v, ok := m2[&a]; ok {
+		print(m2[&c]) // @pointsto main.a
+	}
+}
+
+func maps2() {
+	m1 := map[*int]*int{&a: &b}
+	m2 := map[*int]*int{&b: &c}
+	m3 := []map[*int]*int{m1, m2} // (no spurious merging of m1, m2)
+
+	print(m1[nil]) // @pointsto main.b
+	print(m2[nil]) // @pointsto main.c
+}
+
+func main() {
+	maps1()
+	maps2()
+}
diff --git a/pointer/testdata/panic.go b/pointer/testdata/panic.go
new file mode 100644
index 0000000..dd0bc6c
--- /dev/null
+++ b/pointer/testdata/panic.go
@@ -0,0 +1,36 @@
+// +build ignore
+
+package main
+
+// Test of value flow from panic() to recover().
+// We model them as stores/loads of a global location.
+// We ignore concrete panic types originating from the runtime.
+
+var someval int
+
+type myPanic struct{}
+
+func f(int) {}
+
+func g() string { return "" }
+
+func deadcode() {
+	panic(123) // not reached
+}
+
+func main() {
+	switch someval {
+	case 0:
+		panic("oops")
+	case 1:
+		panic(myPanic{})
+	case 2:
+		panic(f)
+	case 3:
+		panic(g)
+	}
+	ex := recover()
+	print(ex)                 // @concrete myPanic | string | func(int) | func() string
+	print(ex.(func(int)))     // @pointsto main.f
+	print(ex.(func() string)) // @pointsto main.g
+}
diff --git a/pointer/testdata/recur.go b/pointer/testdata/recur.go
new file mode 100644
index 0000000..4c7229d
--- /dev/null
+++ b/pointer/testdata/recur.go
@@ -0,0 +1,11 @@
+// +build ignore
+
+package main
+
+// Analysis abstraction of recursive calls is finite.
+
+func main() {
+	main()
+}
+
+// @calls main.main -> main.main
diff --git a/pointer/testdata/reflect.go b/pointer/testdata/reflect.go
new file mode 100644
index 0000000..986a649
--- /dev/null
+++ b/pointer/testdata/reflect.go
@@ -0,0 +1,69 @@
+// +build ignore
+
+package main
+
+import "reflect"
+import "unsafe"
+
+var a, b int
+
+func reflectIndirect() {
+	ptr := &a
+	// Pointer:
+	print(reflect.Indirect(reflect.ValueOf(&ptr)).Interface().(*int)) // @pointsto main.a
+	// Non-pointer:
+	print(reflect.Indirect(reflect.ValueOf([]*int{ptr})).Interface().([]*int)[0]) // @pointsto main.a
+}
+
+func reflectNewAt() {
+	var x [8]byte
+	print(reflect.NewAt(reflect.TypeOf(3), unsafe.Pointer(&x)).Interface()) // @concrete *int
+}
+
+// @warning "unsound: main.reflectNewAt contains a reflect.NewAt.. call"
+
+func reflectTypeOf() {
+	t := reflect.TypeOf(3)
+	if unknown {
+		t = reflect.TypeOf("foo")
+	}
+	print(t)                             // @concrete *reflect.rtype
+	print(reflect.Zero(t).Interface())   // @concrete int | string
+	newint := reflect.New(t).Interface() // @line rtonew
+	print(newint)                        // @concrete *int | *string
+	print(newint.(*int))                 // @pointsto reflectAlloc@rtonew:23
+	print(newint.(*string))              // @pointsto reflectAlloc@rtonew:23
+}
+
+func reflectTypeElem() {
+	print(reflect.Zero(reflect.TypeOf(&a).Elem()).Interface())                       // @concrete int
+	print(reflect.Zero(reflect.TypeOf([]string{}).Elem()).Interface())               // @concrete string
+	print(reflect.Zero(reflect.TypeOf(make(chan bool)).Elem()).Interface())          // @concrete bool
+	print(reflect.Zero(reflect.TypeOf(make(map[string]float64)).Elem()).Interface()) // @concrete float64
+	print(reflect.Zero(reflect.TypeOf([3]complex64{}).Elem()).Interface())           // @concrete complex64
+	print(reflect.Zero(reflect.TypeOf(3).Elem()).Interface())                        // @concrete
+}
+
+func reflectTypeInOut() {
+	var f func(float64, bool) (string, int)
+	print(reflect.Zero(reflect.TypeOf(f).In(0)).Interface())    // @concrete float64
+	print(reflect.Zero(reflect.TypeOf(f).In(1)).Interface())    // @concrete bool
+	print(reflect.Zero(reflect.TypeOf(f).In(-1)).Interface())   // @concrete float64 | bool
+	print(reflect.Zero(reflect.TypeOf(f).In(zero)).Interface()) // @concrete float64 | bool
+
+	print(reflect.Zero(reflect.TypeOf(f).Out(0)).Interface()) // @concrete string
+	print(reflect.Zero(reflect.TypeOf(f).Out(1)).Interface()) // @concrete int
+	print(reflect.Zero(reflect.TypeOf(f).Out(2)).Interface()) // @concrete string | int
+	print(reflect.Zero(reflect.TypeOf(3).Out(0)).Interface()) // @concrete
+}
+
+func main() {
+	reflectIndirect()
+	reflectNewAt()
+	reflectTypeOf()
+	reflectTypeElem()
+	reflectTypeInOut()
+}
+
+var unknown bool
+var zero int
diff --git a/pointer/testdata/structreflect.go b/pointer/testdata/structreflect.go
new file mode 100644
index 0000000..510ff09
--- /dev/null
+++ b/pointer/testdata/structreflect.go
@@ -0,0 +1,25 @@
+// +build ignore
+
+package main
+
+import "reflect"
+
+var a, b int
+
+type A struct {
+	f *int
+	g interface{}
+	h bool
+}
+
+func structReflect1() {
+	var a A
+	fld, _ := reflect.TypeOf(a).FieldByName("f") // "f" is ignored
+	// TODO(adonovan): what does interface{} even mean here?
+	print(reflect.Zero(fld.Type).Interface()) // @concrete *int | bool | interface{}
+	// TODO(adonovan): test promotion/embedding.
+}
+
+func main() {
+	structReflect1()
+}
diff --git a/pointer/testdata/structs.go b/pointer/testdata/structs.go
new file mode 100644
index 0000000..c9fbc2a
--- /dev/null
+++ b/pointer/testdata/structs.go
@@ -0,0 +1,101 @@
+// +build ignore
+
+package main
+
+var unknown bool // defeat dead-code elimination
+
+var p, q int
+
+type A struct {
+	f *int
+	g interface{}
+}
+
+func (a A) m1() {
+	print(a.f) // @pointsto main.p
+}
+
+func (a *A) m2() {
+	print(a)   // @pointsto complit.A@struct1s:9
+	print(a.f) // @pointsto main.p
+}
+
+type B struct {
+	h *int
+	A
+}
+
+func structs1() {
+	b := &B{ // @line struct1s
+		h: &q,
+	}
+	b.f = &p
+	b.g = b
+
+	print(b.h) // @pointsto main.q
+	print(b.f) // @pointsto main.p
+	print(b.g) // @concrete *B
+
+	ptr := &b.f
+	print(*ptr) // @pointsto main.p
+
+	b.m1()
+	b.m2()
+}
+
+// @calls main.structs1 -> (main.A).m1
+// @calls main.structs1 -> (*main.A).m2
+// @calls (*main.B).m1 -> (main.A).m1
+// @calls (*main.B).m2 -> (*main.A).m2
+
+type T struct {
+	x int
+	y int
+}
+
+type S struct {
+	a [3]T
+	b *[3]T
+	c [3]*T
+}
+
+func structs2() {
+	var s S          // @line s2s
+	print(&s)        // @pointsto s@s2s:6
+	print(&s.a)      // @pointsto s.a@s2s:6
+	print(&s.a[0])   // @pointsto s.a[*]@s2s:6
+	print(&s.a[0].x) // @pointsto s.a[*].x@s2s:6
+	print(&s.a[0].y) // @pointsto s.a[*].y@s2s:6
+	print(&s.b)      // @pointsto s.b@s2s:6
+	print(&s.b[0])   // @pointsto
+	print(&s.b[0].x) // @pointsto
+	print(&s.b[0].y) // @pointsto
+	print(&s.c)      // @pointsto s.c@s2s:6
+	print(&s.c[0])   // @pointsto s.c[*]@s2s:6
+	print(&s.c[0].x) // @pointsto
+	print(&s.c[0].y) // @pointsto
+
+	var s2 S          // @line s2s2
+	s2.b = new([3]T)  // @line s2s2b
+	print(s2.b)       // @pointsto new@s2s2b:12
+	print(&s2.b)      // @pointsto s2.b@s2s2:6
+	print(&s2.b[0])   // @pointsto new[*]@s2s2b:12
+	print(&s2.b[0].x) // @pointsto new[*].x@s2s2b:12
+	print(&s2.b[0].y) // @pointsto new[*].y@s2s2b:12
+	print(&s2.c[0].x) // @pointsto
+	print(&s2.c[0].y) // @pointsto
+
+	var s3 S          // @line s2s3
+	s3.c[2] = new(T)  // @line s2s3c
+	print(s3.c)       // @pointsto
+	print(&s3.c)      // @pointsto s3.c@s2s3:6
+	print(s3.c[1])    // @pointsto new@s2s3c:15
+	print(&s3.c[1])   // @pointsto s3.c[*]@s2s3:6
+	print(&s3.c[1].x) // @pointsto new.x@s2s3c:15
+	print(&s3.c[1].y) // @pointsto new.y@s2s3c:15
+}
+
+func main() {
+	structs1()
+	structs2()
+}
diff --git a/pointer/util.go b/pointer/util.go
new file mode 100644
index 0000000..41bcc24
--- /dev/null
+++ b/pointer/util.go
@@ -0,0 +1,289 @@
+package pointer
+
+import (
+	"bytes"
+	"fmt"
+
+	"code.google.com/p/go.tools/go/types"
+)
+
+// CanPoint reports whether the type T is pointerlike,
+// for the purposes of this analysis.
+func CanPoint(T types.Type) bool {
+	switch T := T.(type) {
+	case *types.Named:
+		return CanPoint(T.Underlying())
+
+	case *types.Pointer, *types.Interface, *types.Map, *types.Chan, *types.Signature:
+		return true
+	}
+
+	return false // array struct tuple builtin basic
+}
+
+// mustDeref returns the element type of its argument, which must be a
+// pointer; panic ensues otherwise.
+func mustDeref(typ types.Type) types.Type {
+	return typ.Underlying().(*types.Pointer).Elem()
+}
+
+// A fieldInfo describes one subelement (node) of the flattening-out
+// of a type T: the subelement's type and its path from the root of T.
+//
+// For example, for this type:
+//     type line struct{ points []struct{x, y int} }
+// flatten() of the inner struct yields the following []fieldInfo:
+//    struct{ x, y int }                      ""
+//    int                                     ".x"
+//    int                                     ".y"
+// and flatten(line) yields:
+//    struct{ points []struct{x, y int} }     ""
+//    struct{ x, y int }                      ".points[*]"
+//    int                                     ".points[*].x
+//    int                                     ".points[*].y"
+//
+type fieldInfo struct {
+	typ types.Type
+
+	// op and tail describe the path to the element (e.g. ".a#2.b[*].c").
+	op   interface{} // *Array: true; *Tuple: int; *Struct: *types.Var; *Named: nil
+	tail *fieldInfo
+}
+
+// path returns a user-friendly string describing the subelement path.
+//
+func (fi *fieldInfo) path() string {
+	var buf bytes.Buffer
+	for p := fi; p != nil; p = p.tail {
+		switch op := p.op.(type) {
+		case bool:
+			fmt.Fprintf(&buf, "[*]")
+		case int:
+			fmt.Fprintf(&buf, "#%d", op)
+		case *types.Var:
+			fmt.Fprintf(&buf, ".%s", op.Name())
+		}
+	}
+	return buf.String()
+}
+
+// flatten returns a list of directly contained fields in the preorder
+// traversal of the type tree of t.  The resulting elements are all
+// scalars (basic types or pointerlike types), except for struct/array
+// "identity" nodes, whose type is that of the aggregate.
+//
+// Callers must not mutate the result.
+//
+func (a *analysis) flatten(t types.Type) []*fieldInfo {
+	fl, ok := a.flattenMemo[t]
+	if !ok {
+		switch t := t.(type) {
+		case *types.Named:
+			u := t.Underlying()
+			if _, ok := u.(*types.Interface); ok {
+				// Debuggability hack: don't remove
+				// the named type from interfaces as
+				// they're very verbose.
+				fl = append(fl, &fieldInfo{typ: t})
+			} else {
+				fl = a.flatten(u)
+			}
+
+		case *types.Basic,
+			*types.Signature,
+			*types.Chan,
+			*types.Map,
+			*types.Interface,
+			*types.Slice,
+			*types.Pointer:
+			fl = append(fl, &fieldInfo{typ: t})
+
+		case *types.Array:
+			fl = append(fl, &fieldInfo{typ: t}) // identity node
+			for _, fi := range a.flatten(t.Elem()) {
+				fl = append(fl, &fieldInfo{typ: fi.typ, op: true, tail: fi})
+			}
+
+		case *types.Struct:
+			fl = append(fl, &fieldInfo{typ: t}) // identity node
+			for i, n := 0, t.NumFields(); i < n; i++ {
+				f := t.Field(i)
+				for _, fi := range a.flatten(f.Type()) {
+					fl = append(fl, &fieldInfo{typ: fi.typ, op: f, tail: fi})
+				}
+			}
+
+		case *types.Tuple:
+			// No identity node: tuples are never address-taken.
+			for i, n := 0, t.Len(); i < n; i++ {
+				f := t.At(i)
+				for _, fi := range a.flatten(f.Type()) {
+					fl = append(fl, &fieldInfo{typ: fi.typ, op: i, tail: fi})
+				}
+			}
+
+		case *types.Builtin:
+			panic("flatten(*types.Builtin)") // not the type of any value
+
+		default:
+			panic(t)
+		}
+
+		a.flattenMemo[t] = fl
+	}
+
+	return fl
+}
+
+// sizeof returns the number of pointerlike abstractions (nodes) in the type t.
+func (a *analysis) sizeof(t types.Type) uint32 {
+	return uint32(len(a.flatten(t)))
+}
+
+// offsetOf returns the (abstract) offset of field index within struct
+// or tuple typ.
+func (a *analysis) offsetOf(typ types.Type, index int) uint32 {
+	var offset uint32
+	switch t := typ.Underlying().(type) {
+	case *types.Tuple:
+		for i := 0; i < index; i++ {
+			offset += a.sizeof(t.At(i).Type())
+		}
+	case *types.Struct:
+		offset++ // the node for the struct itself
+		for i := 0; i < index; i++ {
+			offset += a.sizeof(t.Field(i).Type())
+		}
+	default:
+		panic(fmt.Sprintf("offsetOf(%s : %T)", typ, typ))
+	}
+	return offset
+}
+
+// sliceToArray returns the type representing the arrays to which
+// slice type slice points.
+func sliceToArray(slice types.Type) *types.Array {
+	return types.NewArray(slice.Underlying().(*types.Slice).Elem(), 1)
+}
+
+// Node set -------------------------------------------------------------------
+
+type nodeset map[nodeid]struct{}
+
+// ---- Accessors ----
+
+func (ns nodeset) String() string {
+	var buf bytes.Buffer
+	buf.WriteRune('{')
+	var sep string
+	for n := range ns {
+		fmt.Fprintf(&buf, "%sn%d", sep, n)
+		sep = ", "
+	}
+	buf.WriteRune('}')
+	return buf.String()
+}
+
+// diff returns the set-difference x - y.  nil => empty.
+//
+// TODO(adonovan): opt: extremely inefficient.  BDDs do this in
+// constant time.  Sparse bitvectors are linear but very fast.
+func (x nodeset) diff(y nodeset) nodeset {
+	var z nodeset
+	for k := range x {
+		if _, ok := y[k]; !ok {
+			z.add(k)
+		}
+	}
+	return z
+}
+
+// clone() returns an unaliased copy of x.
+func (x nodeset) clone() nodeset {
+	return x.diff(nil)
+}
+
+// ---- Mutators ----
+
+func (ns *nodeset) add(n nodeid) bool {
+	sz := len(*ns)
+	if *ns == nil {
+		*ns = make(nodeset)
+	}
+	(*ns)[n] = struct{}{}
+	return len(*ns) > sz
+}
+
+func (x *nodeset) addAll(y nodeset) bool {
+	if y == nil {
+		return false
+	}
+	sz := len(*x)
+	if *x == nil {
+		*x = make(nodeset)
+	}
+	for n := range y {
+		(*x)[n] = struct{}{}
+	}
+	return len(*x) > sz
+}
+
+// Constraint set -------------------------------------------------------------
+
+type constraintset map[constraint]struct{}
+
+func (cs *constraintset) add(c constraint) bool {
+	sz := len(*cs)
+	if *cs == nil {
+		*cs = make(constraintset)
+	}
+	(*cs)[c] = struct{}{}
+	return len(*cs) > sz
+}
+
+// Worklist -------------------------------------------------------------------
+
+// TODO(adonovan): interface may not be general enough for certain
+// implementations, e.g. priority queue
+//
+// Uses double-buffering so nodes can be added during iteration.
+type worklist interface {
+	empty() bool  // Reports whether active buffer is empty.
+	swap() bool   // Switches to the shadow buffer if empty().
+	add(nodeid)   // Adds a node to the shadow buffer.
+	take() nodeid // Takes a node from the active buffer.  Precondition: !empty().
+}
+
+// Horribly naive (and nondeterministic) worklist
+// based on two hash-sets.
+type mapWorklist struct {
+	active, shadow nodeset
+}
+
+func (w *mapWorklist) empty() bool {
+	return len(w.active) == 0
+}
+
+func (w *mapWorklist) swap() bool {
+	if w.empty() {
+		w.shadow, w.active = w.active, w.shadow
+		return true
+	}
+	return false
+}
+
+func (w *mapWorklist) add(n nodeid) {
+	w.shadow[n] = struct{}{}
+}
+
+func (w *mapWorklist) take() nodeid {
+	for k := range w.active {
+		delete(w.active, k)
+		return k
+	}
+	panic("worklist.take(): empty active buffer")
+}
+
+func makeMapWorklist() worklist {
+	return &mapWorklist{make(nodeset), make(nodeset)}
+}