go.tools/ssa: new Function.Syntax() returns the declaring AST (debug mode) or just the Pos/End of the function's extent (otherwise).

R=gri
CC=golang-dev
https://golang.org/cl/16980043
diff --git a/ssa/builder.go b/ssa/builder.go
index 8675fb6..6474be6 100644
--- a/ssa/builder.go
+++ b/ssa/builder.go
@@ -514,10 +514,7 @@
 			Enclosing: fn,
 			Pkg:       fn.Pkg,
 			Prog:      fn.Prog,
-			syntax: &funcSyntax{
-				functype: e.Type,
-				body:     e.Body,
-			},
+			syntax:    e,
 		}
 		fn.AnonFuncs = append(fn.AnonFuncs, fn2)
 		b.buildFunction(fn2)
@@ -2214,10 +2211,25 @@
 	if fn.Blocks != nil {
 		return // building already started
 	}
-	if fn.syntax == nil {
+
+	var recvField *ast.FieldList
+	var body *ast.BlockStmt
+	var functype *ast.FuncType
+	switch n := fn.syntax.(type) {
+	case nil:
 		return // not a Go source function.  (Synthetic, or from object file.)
+	case *ast.FuncDecl:
+		functype = n.Type
+		recvField = n.Recv
+		body = n.Body
+	case *ast.FuncLit:
+		functype = n.Type
+		body = n.Body
+	default:
+		panic(n)
 	}
-	if fn.syntax.body == nil {
+
+	if body == nil {
 		// External function.
 		if fn.Params == nil {
 			// This condition ensures we add a non-empty
@@ -2241,8 +2253,8 @@
 		defer logStack("build function %s @ %s", fn, fn.Prog.Fset.Position(fn.pos))()
 	}
 	fn.startBody()
-	fn.createSyntacticParams()
-	b.stmt(fn, fn.syntax.body)
+	fn.createSyntacticParams(recvField, functype)
+	b.stmt(fn, body)
 	if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb == fn.Recover || cb.Preds != nil) {
 		// Run function calls deferred in this function when
 		// falling off the end of the body block.
@@ -2269,11 +2281,7 @@
 			pos:       decl.Name.NamePos,
 			Pkg:       pkg,
 			Prog:      pkg.Prog,
-			syntax: &funcSyntax{
-				functype:  decl.Type,
-				recvField: decl.Recv,
-				body:      decl.Body,
-			},
+			syntax:    decl,
 		}
 
 		var v Call
diff --git a/ssa/create.go b/ssa/create.go
index 67e730e..5af1fd9 100644
--- a/ssa/create.go
+++ b/ssa/create.go
@@ -95,26 +95,19 @@
 		pkg.Members[name] = g
 
 	case *types.Func:
-		var fs *funcSyntax
-		synthetic := "loaded from gc object file"
-		if decl, ok := syntax.(*ast.FuncDecl); ok {
-			synthetic = ""
-			fs = &funcSyntax{
-				functype:  decl.Type,
-				recvField: decl.Recv,
-				body:      decl.Body,
-			}
-		}
 		fn := &Function{
 			name:      name,
 			object:    obj,
 			Signature: obj.Type().(*types.Signature),
-			Synthetic: synthetic,
+			syntax:    syntax,
 			pos:       obj.Pos(), // (iff syntax)
 			Pkg:       pkg,
 			Prog:      pkg.Prog,
-			syntax:    fs,
 		}
+		if syntax == nil {
+			fn.Synthetic = "loaded from gc object file"
+		}
+
 		pkg.values[obj] = fn
 		if fn.Signature.Recv() == nil {
 			pkg.Members[name] = fn // package-level function
diff --git a/ssa/func.go b/ssa/func.go
index cf380fa..5294ebf 100644
--- a/ssa/func.go
+++ b/ssa/func.go
@@ -147,13 +147,6 @@
 	_continue *BasicBlock
 }
 
-// funcSyntax holds the syntax tree for the function declaration and body.
-type funcSyntax struct {
-	recvField *ast.FieldList
-	body      *ast.BlockStmt
-	functype  *ast.FuncType
-}
-
 // labelledBlock returns the branch target associated with the
 // specified label, creating it if needed.
 //
@@ -221,15 +214,14 @@
 // syntax.  In addition it populates the f.objects mapping.
 //
 // Preconditions:
-// f.syntax != nil, i.e. this is a Go source function.
 // f.startBody() was called.
 // Postcondition:
 // len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0)
 //
-func (f *Function) createSyntacticParams() {
+func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) {
 	// Receiver (at most one inner iteration).
-	if f.syntax.recvField != nil {
-		for _, field := range f.syntax.recvField.List {
+	if recv != nil {
+		for _, field := range recv.List {
 			for _, n := range field.Names {
 				f.addSpilledParam(f.Pkg.objectOf(n))
 			}
@@ -241,9 +233,9 @@
 	}
 
 	// Parameters.
-	if f.syntax.functype.Params != nil {
+	if functype.Params != nil {
 		n := len(f.Params) // 1 if has recv, 0 otherwise
-		for _, field := range f.syntax.functype.Params.List {
+		for _, field := range functype.Params.List {
 			for _, n := range field.Names {
 				f.addSpilledParam(f.Pkg.objectOf(n))
 			}
@@ -255,8 +247,8 @@
 	}
 
 	// Named results.
-	if f.syntax.functype.Results != nil {
-		for _, field := range f.syntax.functype.Results.List {
+	if functype.Results != nil {
+		for _, field := range functype.Results.List {
 			// Implicit "var" decl of locals for named results.
 			for _, n := range field.Names {
 				f.namedResults = append(f.namedResults, f.addLocalForIdent(n))
@@ -308,7 +300,11 @@
 	f.objects = nil
 	f.currentBlock = nil
 	f.lblocks = nil
-	f.syntax = nil
+
+	// Don't pin the AST in memory (except in debug mode).
+	if n := f.syntax; n != nil && !f.debugInfo() {
+		f.syntax = extentNode{n.Pos(), n.End()}
+	}
 
 	// Remove any f.Locals that are now heap-allocated.
 	j := 0
@@ -380,15 +376,13 @@
 
 // debugInfo reports whether debug info is wanted for this function.
 func (f *Function) debugInfo() bool {
-	return f.Pkg.debug
+	return f.Pkg != nil && f.Pkg.debug
 }
 
 // addNamedLocal creates a local variable, adds it to function f and
 // returns it.  Its name and type are taken from obj.  Subsequent
 // calls to f.lookup(obj) will return the same local.
 //
-// Precondition: f.syntax != nil (i.e. a Go source function).
-//
 func (f *Function) addNamedLocal(obj types.Object) *Alloc {
 	l := f.addLocal(obj.Type(), obj.Pos())
 	l.Comment = obj.Name()
@@ -660,3 +654,19 @@
 func NewFunction(name string, sig *types.Signature, provenance string) *Function {
 	return &Function{name: name, Signature: sig, Synthetic: provenance}
 }
+
+type extentNode [2]token.Pos
+
+func (n extentNode) Pos() token.Pos { return n[0] }
+func (n extentNode) End() token.Pos { return n[1] }
+
+// Syntax returns an ast.Node whose Pos/End methods provide the
+// lexical extent of the function if it was defined by Go source code
+// (f.Synthetic==""), or nil otherwise.
+//
+// If f was built with debug information (see Package.SetDebugRef),
+// the result is the *ast.FuncDecl or *ast.FuncLit that declared the
+// function.  Otherwise, it is an opaque Node providing only position
+// information; this avoids pinning the AST in memory.
+//
+func (f *Function) Syntax() ast.Node { return f.syntax }
diff --git a/ssa/sanity.go b/ssa/sanity.go
index c364d4e..81f3351 100644
--- a/ssa/sanity.go
+++ b/ssa/sanity.go
@@ -339,6 +339,9 @@
 			s.errorf("nil Pkg")
 		}
 	}
+	if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn {
+		s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
+	}
 	for i, l := range fn.Locals {
 		if l.Parent() != fn {
 			s.errorf("Local %s at index %d has wrong parent", l.Name(), i)
diff --git a/ssa/source.go b/ssa/source.go
index 726d80e..051d901 100644
--- a/ssa/source.go
+++ b/ssa/source.go
@@ -26,8 +26,8 @@
 // Returns nil if not found; reasons might include:
 //    - the node is not enclosed by any function.
 //    - the node is within an anonymous function (FuncLit) and
-//      its SSA function has not been created yet (pkg.BuildPackage()
-//      has not yet been called).
+//      its SSA function has not been created yet
+//      (pkg.Build() has not yet been called).
 //
 func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
 	// Start with package-level function...
diff --git a/ssa/ssa.go b/ssa/ssa.go
index e3624d4..d958e3a 100644
--- a/ssa/ssa.go
+++ b/ssa/ssa.go
@@ -268,6 +268,7 @@
 	pos       token.Pos
 
 	Synthetic string       // provenance of synthetic function; "" for true source functions
+	syntax    ast.Node     // *ast.Func{Decl,Lit}; replaced with simple ast.Node after build, unless debug mode
 	Enclosing *Function    // enclosing function if anon; nil if global
 	Pkg       *Package     // enclosing package; nil for shared funcs (wrappers and error.Error)
 	Prog      *Program     // enclosing program
@@ -283,7 +284,6 @@
 	currentBlock *BasicBlock             // where to emit code
 	objects      map[types.Object]Value  // addresses of local variables
 	namedResults []*Alloc                // tuple of named results
-	syntax       *funcSyntax             // abstract syntax trees for Go source functions
 	targets      *targets                // linked stack of branch targets
 	lblocks      map[*ast.Object]*lblock // labelled blocks
 }
@@ -1168,6 +1168,9 @@
 // consistency is maintained during transformation passes by the
 // ordinary SSA renaming machinery.)
 //
+// DebugRefs are generated only for functions built with debugging
+// enabled; see Package.SetDebugMode().
+//
 type DebugRef struct {
 	anInstruction
 	X      Value        // the value whose position we're declaring