go.tools/ssa: extend debug information to arbitrary ast.Exprs.

CanonicalPos was inadequate since many pairs of instruction share the same pos (e.g. Allocs and Phis).  Instead, we generalize the DebugRef instruction to associate not just Idents but Exprs with ssa.Values.

We no longer store any DebugRefs for constant expressions, to save space.  (The type and value of such expressions can be obtained by other means, at a cost in complexity.)

Function.ValueForExpr queries the DebugRef info to return the ssa.Value of a given Expr.

Added tests.

Also:
- the DebugInfo flag is now per package, not global.
   It must be set between Create and Build phases if desired.
- {Value,Instruction}.Pos() documentation updated: we still maintain
  this information in the instruction stream even in non-debug mode,
  but we make fewer claims about its invariants.
- Go and Defer instructions can now use their respective go/defer
   token positions (not the call's lparen), so they do.
- SelectState:
     Posn token.Pos indicates the <- position
     DebugNode ast.Expr is the send stmt or receive expr.
- In building SelectStmt, we introduce extra temporaries in debug
   mode to hold the result of the receive in 'case <-ch' even though
   this value isn't ordinarily needed.
- Use *SelectState (indirectly) since the struct is getting bigger.
- Document some missing instructions in doc.go.

R=gri
CC=golang-dev
https://golang.org/cl/12147043
This commit is contained in:
Alan Donovan 2013-07-31 13:13:05 -04:00
parent e5cfd92deb
commit c28bf6e069
14 changed files with 271 additions and 143 deletions

View File

@ -38,7 +38,7 @@ func main() {
f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors) f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors)
if err != nil { if err != nil {
t.Errorf("parse error: %s", err) t.Error(err)
return return
} }

View File

@ -23,7 +23,6 @@ const (
SanityCheckFunctions // Perform sanity checking of function bodies SanityCheckFunctions // Perform sanity checking of function bodies
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
BuildSerially // Build packages serially, not in parallel. BuildSerially // Build packages serially, not in parallel.
DebugInfo // Include DebugRef instructions [TODO(adonovan): finer grain?]
) )
// NewProgram returns a new SSA Program initially containing no // NewProgram returns a new SSA Program initially containing no

View File

@ -59,6 +59,7 @@
// *ChangeType ✔ ✔ // *ChangeType ✔ ✔
// *Const ✔ // *Const ✔
// *Convert ✔ ✔ // *Convert ✔ ✔
// *DebugRef ✔
// *Defer ✔ // *Defer ✔
// *Extract ✔ ✔ // *Extract ✔ ✔
// *Field ✔ ✔ // *Field ✔ ✔
@ -86,7 +87,9 @@
// *Ret ✔ // *Ret ✔
// *RunDefers ✔ // *RunDefers ✔
// *Select ✔ ✔ // *Select ✔ ✔
// *Send ✔
// *Slice ✔ ✔ // *Slice ✔ ✔
// *Store ✔
// *Type ✔ (type) // *Type ✔ (type)
// *TypeAssert ✔ ✔ // *TypeAssert ✔ ✔
// *UnOp ✔ ✔ // *UnOp ✔ ✔
@ -110,16 +113,14 @@
// TODO(adonovan): Consider the exceptional control-flow implications // TODO(adonovan): Consider the exceptional control-flow implications
// of defer and recover(). // of defer and recover().
// //
// TODO(adonovan): Consider how token.Pos source location information
// should be made available generally. Currently it is only present
// in package Members and selected Instructions for which there is a
// direct source correspondence. We'll need to work harder to tie all
// defs/uses of named variables together, esp. because SSA splits them
// into separate webs.
//
// TODO(adonovan): write an example showing how to visit all functions // TODO(adonovan): write an example showing how to visit all functions
// in a Program, including package init functions, methods of named // in a Program, including package init functions, methods of named
// and anon types, and functions used as values but never called // and anon types, and functions used as values but never called
// directly. // directly. See AllFunctions().
//
// TODO(adonovan): write a how-to document for all the various cases
// of trying to determine corresponding elements across the four
// domains of source locations, ast.Nodes, types.Objects,
// ssa.Values/Instructions.
// //
package ssa package ssa

View File

@ -31,22 +31,28 @@ func emitLoad(f *Function, addr Value) *UnOp {
} }
// emitDebugRef emits to f a DebugRef pseudo-instruction associating // emitDebugRef emits to f a DebugRef pseudo-instruction associating
// reference id with local var/const value v. // expression e with value v.
// //
func emitDebugRef(f *Function, id *ast.Ident, v Value) { func emitDebugRef(f *Function, e ast.Expr, v Value) {
if !f.debugInfo() { if !f.debugInfo() {
return // debugging not enabled return // debugging not enabled
} }
if v == nil || e == nil {
panic("nil")
}
var obj types.Object
if id, ok := e.(*ast.Ident); ok {
if isBlankIdent(id) { if isBlankIdent(id) {
return return
} }
obj := f.Pkg.objectOf(id) obj = f.Pkg.objectOf(id)
if obj.Parent() == types.Universe { if _, ok := obj.(*types.Const); ok {
return // skip nil/true/false return
}
} }
f.emit(&DebugRef{ f.emit(&DebugRef{
X: v, X: v,
pos: id.Pos(), Expr: unparen(e),
object: obj, object: obj,
}) })
} }

View File

@ -42,14 +42,14 @@ func main() {
// Parse the input file. // Parse the input file.
file, err := parser.ParseFile(imp.Fset, "hello.go", hello, parser.DeclarationErrors) file, err := parser.ParseFile(imp.Fset, "hello.go", hello, parser.DeclarationErrors)
if err != nil { if err != nil {
fmt.Printf(err.Error()) // parse error fmt.Print(err.Error()) // parse error
return return
} }
// Create a "main" package containing one file. // Create a "main" package containing one file.
info := imp.CreateSourcePackage("main", []*ast.File{file}) info := imp.CreateSourcePackage("main", []*ast.File{file})
if info.Err != nil { if info.Err != nil {
fmt.Printf(info.Err.Error()) // type error fmt.Print(info.Err.Error()) // type error
return return
} }

View File

@ -374,10 +374,18 @@ func (f *Function) removeNilBlocks() {
f.Blocks = f.Blocks[:j] f.Blocks = f.Blocks[:j]
} }
// SetDebugMode sets the debug mode for package pkg. If true, all its
// functions will include full debug info. This greatly increases
// the size of the instruction stream.
//
func (pkg *Package) SetDebugMode(debug bool) {
// TODO(adonovan): do we want ast.File granularity?
pkg.debug = debug
}
// debugInfo reports whether debug info is wanted for this function. // debugInfo reports whether debug info is wanted for this function.
func (f *Function) debugInfo() bool { func (f *Function) debugInfo() bool {
// TODO(adonovan): make the policy finer grained. return f.Pkg.debug
return f.Prog.mode&DebugInfo != 0
} }
// addNamedLocal creates a local variable, adds it to function f and // addNamedLocal creates a local variable, adds it to function f and

View File

@ -25,7 +25,7 @@ type lvalue interface {
type address struct { type address struct {
addr Value addr Value
starPos token.Pos // source position, if from explicit *addr starPos token.Pos // source position, if from explicit *addr
id *ast.Ident // source syntax, if from *ast.Ident expr ast.Expr // source syntax [debug mode]
object types.Object // source var, if from *ast.Ident object types.Object // source var, if from *ast.Ident
} }
@ -38,16 +38,16 @@ func (a *address) load(fn *Function) Value {
func (a *address) store(fn *Function, v Value) { func (a *address) store(fn *Function, v Value) {
store := emitStore(fn, a.addr, v) store := emitStore(fn, a.addr, v)
store.pos = a.starPos store.pos = a.starPos
if a.id != nil { if a.expr != nil {
// store.Val is v converted for assignability. // store.Val is v converted for assignability.
emitDebugRef(fn, a.id, store.Val) emitDebugRef(fn, a.expr, store.Val)
} }
} }
func (a *address) address(fn *Function) Value { func (a *address) address(fn *Function) Value {
if a.id != nil { if a.expr != nil {
// NB: this kind of DebugRef yields the object's address. // NB: this kind of DebugRef yields the object's address.
emitDebugRef(fn, a.id, a.addr) emitDebugRef(fn, a.expr, a.addr)
} }
return a.addr return a.addr
} }

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"io" "io"
"reflect"
"sort" "sort"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
@ -348,8 +349,14 @@ func (s *MapUpdate) String() string {
} }
func (s *DebugRef) String() string { func (s *DebugRef) String() string {
p := s.Parent().Prog.Fset.Position(s.pos) p := s.Parent().Prog.Fset.Position(s.Pos())
return fmt.Sprintf("; %s is %s @ %d:%d", s.X.Name(), s.object, p.Line, p.Column) var descr interface{}
if s.object != nil {
descr = s.object // e.g. "var x int"
} else {
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr"
}
return fmt.Sprintf("; %s is %s @ %d:%d", s.X.Name(), descr, p.Line, p.Column)
} }
func (p *Package) String() string { func (p *Package) String() string {

View File

@ -3,6 +3,9 @@ package ssa
// This file defines utilities for working with source positions // This file defines utilities for working with source positions
// or source-level named entities ("objects"). // or source-level named entities ("objects").
// TODO(adonovan): test that {Value,Instruction}.Pos() positions match
// the originating syntax, as specified.
import ( import (
"go/ast" "go/ast"
"go/token" "go/token"
@ -114,78 +117,39 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
return nil return nil
} }
// CanonicalPos returns the canonical position of the AST node n, // ValueForExpr returns the SSA Value that corresponds to non-constant
// expression e.
// //
// For each Node kind that may generate an SSA Value or Instruction, // It returns nil if no value was found, e.g.
// exactly one token within it is designated as "canonical". The // - the expression is not lexically contained within f;
// position of that token is returned by {Value,Instruction}.Pos(). // - f was not built with debug information; or
// The specifications of those methods determine the implementation of // - e is a constant expression. (For efficiency, no debug
// this function. // information is stored for constants. Use
// importer.PackageInfo.ValueOf(e) instead.)
// - the value was optimised away.
// //
// TODO(adonovan): test coverage. // The types of e and the result are equal (modulo "untyped" bools
// resulting from comparisons) and they have equal "pointerness".
// //
func CanonicalPos(n ast.Node) token.Pos { // (Tip: to find the ssa.Value given a source position, use
// Comments show the Value/Instruction kinds v that may be // importer.PathEnclosingInterval to locate the ast.Node, then
// created by n such that CanonicalPos(n) == v.Pos(). // EnclosingFunction to locate the Function, then ValueForExpr to find
switch n := n.(type) { // the ssa.Value.)
case *ast.ParenExpr: //
return CanonicalPos(n.X) func (f *Function) ValueForExpr(e ast.Expr) Value {
if f.debugInfo() { // (opt)
case *ast.CallExpr: e = unparen(e)
// f(x): *Call, *Go, *Defer. for _, b := range f.Blocks {
// T(x): *ChangeType, *Convert, *MakeInterface, *ChangeInterface. for _, instr := range b.Instrs {
// make(): *MakeMap, *MakeChan, *MakeSlice. if ref, ok := instr.(*DebugRef); ok {
// new(): *Alloc. if ref.Expr == e {
// panic(): *Panic. return ref.X
return n.Lparen
case *ast.Ident:
return n.NamePos // *Parameter, *Alloc, *Capture
case *ast.TypeAssertExpr:
return n.Lparen // *ChangeInterface or *TypeAssertExpr
case *ast.SelectorExpr:
return n.Sel.NamePos // *MakeClosure, *Field, *FieldAddr
case *ast.FuncLit:
return n.Type.Func // *Function or *MakeClosure
case *ast.CompositeLit:
return n.Lbrace // *Alloc or *Slice
case *ast.BinaryExpr:
return n.OpPos // *Phi or *BinOp
case *ast.UnaryExpr:
return n.OpPos // *Phi or *UnOp
case *ast.IndexExpr:
return n.Lbrack // *Index or *IndexAddr
case *ast.SliceExpr:
return n.Lbrack // *Slice
case *ast.SelectStmt:
return n.Select // *Select
case *ast.RangeStmt:
return n.For // *Range
case *ast.ReturnStmt:
return n.Return // *Ret
case *ast.SendStmt:
return n.Arrow // *Send
case *ast.StarExpr:
return n.Star // *Store
case *ast.KeyValueExpr:
return n.Colon // *MapUpdate
} }
}
return token.NoPos }
}
}
return nil
} }
// --- Lookup functions for source-level named entities (types.Objects) --- // --- Lookup functions for source-level named entities (types.Objects) ---
@ -233,7 +197,7 @@ func (prog *Program) FuncValue(obj *types.Func) Value {
// //
func (prog *Program) ConstValue(obj *types.Const) *Const { func (prog *Program) ConstValue(obj *types.Const) *Const {
// TODO(adonovan): opt: share (don't reallocate) // TODO(adonovan): opt: share (don't reallocate)
// Consts for const objects. // Consts for const objects and constant ast.Exprs.
// Universal constant? {true,false,nil} // Universal constant? {true,false,nil}
if obj.Parent() == types.Universe { if obj.Parent() == types.Universe {
@ -250,10 +214,8 @@ func (prog *Program) ConstValue(obj *types.Const) *Const {
// identifier denoting the source-level named variable obj. // identifier denoting the source-level named variable obj.
// //
// VarValue returns nil if a local variable was not found, perhaps // VarValue returns nil if a local variable was not found, perhaps
// because its package was not built, the DebugInfo flag was not set // because its package was not built, the debug information was not
// during SSA construction, or the value was optimized away. // requested during SSA construction, or the value was optimized away.
//
// TODO(adonovan): test on x.f where x is a field.
// //
// ref must be the path to an ast.Ident (e.g. from // ref must be the path to an ast.Ident (e.g. from
// PathEnclosingInterval), and that ident must resolve to obj. // PathEnclosingInterval), and that ident must resolve to obj.
@ -315,5 +277,5 @@ func (prog *Program) VarValue(obj *types.Var, ref []ast.Node) Value {
} }
} }
return nil // e.g. DebugInfo unset, or var optimized away return nil // e.g. debug info not requested, or var optimized away
} }

View File

@ -7,7 +7,9 @@ import (
"go/ast" "go/ast"
"go/parser" "go/parser"
"go/token" "go/token"
"os"
"regexp" "regexp"
"strings"
"testing" "testing"
"code.google.com/p/go.tools/go/exact" "code.google.com/p/go.tools/go/exact"
@ -20,7 +22,7 @@ func TestObjValueLookup(t *testing.T) {
imp := importer.New(new(importer.Config)) // (uses GCImporter) imp := importer.New(new(importer.Config)) // (uses GCImporter)
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.DeclarationErrors|parser.ParseComments) f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.DeclarationErrors|parser.ParseComments)
if err != nil { if err != nil {
t.Errorf("parse error: %s", err) t.Error(err)
return return
} }
@ -46,12 +48,13 @@ func TestObjValueLookup(t *testing.T) {
return return
} }
prog := ssa.NewProgram(imp.Fset, ssa.DebugInfo /*|ssa.LogFunctions*/) prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
for _, info := range imp.Packages { for _, info := range imp.Packages {
prog.CreatePackage(info) prog.CreatePackage(info)
} }
pkg := prog.Package(info.Pkg) mainPkg := prog.Package(info.Pkg)
pkg.Build() mainPkg.SetDebugMode(true)
mainPkg.Build()
// Gather all idents and objects in file. // Gather all idents and objects in file.
objs := make(map[types.Object]bool) objs := make(map[types.Object]bool)
@ -178,3 +181,75 @@ func checkVarValue(t *testing.T, prog *ssa.Program, ref []ast.Node, obj *types.V
} }
} }
} }
// Ensure that, in debug mode, we can determine the ssa.Value
// corresponding to every ast.Expr.
func TestValueForExpr(t *testing.T) {
imp := importer.New(new(importer.Config)) // (uses GCImporter)
f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil,
parser.DeclarationErrors|parser.ParseComments)
if err != nil {
t.Error(err)
return
}
info := imp.CreateSourcePackage("main", []*ast.File{f})
if info.Err != nil {
t.Error(info.Err.Error())
return
}
prog := ssa.NewProgram(imp.Fset, 0)
for _, info := range imp.Packages {
prog.CreatePackage(info)
}
mainPkg := prog.Package(info.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
fn := mainPkg.Func("f")
if false {
fn.DumpTo(os.Stderr) // debugging
}
// Find the actual AST node for each canonical position.
parenExprByPos := make(map[token.Pos]*ast.ParenExpr)
ast.Inspect(f, func(n ast.Node) bool {
if n != nil {
if e, ok := n.(*ast.ParenExpr); ok {
parenExprByPos[e.Pos()] = e
}
}
return true
})
// Find all annotations of form /*@kind*/.
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if text == "" || text[0] != '@' {
continue
}
text = text[1:]
pos := c.End() + 1
position := imp.Fset.Position(pos)
var e ast.Expr
if target := parenExprByPos[pos]; target == nil {
t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text)
continue
} else {
e = target.X
}
v := fn.ValueForExpr(e) // (may be nil)
got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.")
if want := text; got != want {
t.Errorf("%s: got value %q, want %q", position, got, want)
}
if v != nil {
if !types.IsIdentical(v.Type(), info.TypeOf(e)) {
t.Errorf("%s: got type %s, want %s", position, info.TypeOf(e), v.Type())
}
}
}
}

View File

@ -41,6 +41,7 @@ type Package struct {
Members map[string]Member // all package members keyed by name Members map[string]Member // all package members keyed by name
values map[types.Object]Value // package-level vars & funcs (incl. methods), keyed by object values map[types.Object]Value // package-level vars & funcs (incl. methods), keyed by object
init *Function // Func("init"); the package's (concatenated) init function init *Function // Func("init"); the package's (concatenated) init function
debug bool // include full debug info in this package.
// The following fields are set transiently, then cleared // The following fields are set transiently, then cleared
// after building. // after building.
@ -125,14 +126,20 @@ type Value interface {
// Instruction.Operands contains the inverse of this relation. // Instruction.Operands contains the inverse of this relation.
Referrers() *[]Instruction Referrers() *[]Instruction
// Pos returns the location of the source construct that // Pos returns the location of the AST token most closely
// gave rise to this value, or token.NoPos if it was not // associated with the operation that gave rise to this value,
// explicit in the source. // or token.NoPos if it was not explicit in the source.
// //
// For each ast.Expr type, a particular field is designated as // For each ast.Node type, a particular token is designated as
// the canonical location for the expression, e.g. the Lparen // the closest location for the expression, e.g. the Lparen
// for an *ast.CallExpr. This enables us to find the value // for an *ast.CallExpr. This permits a compact but
// corresponding to a given piece of source syntax. // approximate mapping from Values to source positions for use
// in diagnostic messages, for example.
//
// (Do not use this position to determine which Value
// corresponds to an ast.Expr; use Function.ValueForExpr
// instead. NB: it requires that the function was built with
// debug information.)
// //
Pos() token.Pos Pos() token.Pos
} }
@ -191,15 +198,22 @@ type Instruction interface {
// Values.) // Values.)
Operands(rands []*Value) []*Value Operands(rands []*Value) []*Value
// Pos returns the location of the source construct that // Pos returns the location of the AST token most closely
// gave rise to this instruction, or token.NoPos if it was not // associated with the operation that gave rise to this
// explicit in the source. // instruction, or token.NoPos if it was not explicit in the
// source.
// //
// For each ast.Expr type, a particular field is designated as // For each ast.Node type, a particular token is designated as
// the canonical location for the expression, e.g. the Lparen // the closest location for the expression, e.g. the Go token
// for an *ast.CallExpr. This enables us to find the // for an *ast.GoStmt. This permits a compact but approximate
// instruction corresponding to a given piece of source // mapping from Instructions to source positions for use in
// syntax. // diagnostic messages, for example.
//
// (Do not use this position to determine which Instruction
// corresponds to an ast.Expr; see the notes for Value.Pos.
// This position may be used to determine which non-Value
// Instruction corresponds to some ast.Stmts, but not all: If
// and Jump instructions have no Pos(), for example.)
// //
Pos() token.Pos Pos() token.Pos
} }
@ -795,6 +809,7 @@ type SelectState struct {
Chan Value // channel to use (for send or receive) Chan Value // channel to use (for send or receive)
Send Value // value to send (for send) Send Value // value to send (for send)
Pos token.Pos // position of token.ARROW Pos token.Pos // position of token.ARROW
DebugNode ast.Node // ast.SendStmt or ast.UnaryExpr(<-) [debug mode]
} }
// The Select instruction tests whether (or blocks until) one or more // The Select instruction tests whether (or blocks until) one or more
@ -834,7 +849,7 @@ type SelectState struct {
// //
type Select struct { type Select struct {
Register Register
States []SelectState States []*SelectState
Blocking bool Blocking bool
} }
@ -906,8 +921,9 @@ type Next struct {
// //
// Pos() returns the ast.CallExpr.Lparen if the instruction arose from // Pos() returns the ast.CallExpr.Lparen if the instruction arose from
// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the // an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the
// instruction arose from an explicit e.(T) operation; or token.NoPos // instruction arose from an explicit e.(T) operation; or the
// otherwise. // ast.CaseClause.Case if the instruction arose from a case of a
// type-switch statement.
// //
// Example printed form: // Example printed form:
// t1 = typeassert t0.(int) // t1 = typeassert t0.(int)
@ -1037,6 +1053,8 @@ type Panic struct {
// //
// See CallCommon for generic function call documentation. // See CallCommon for generic function call documentation.
// //
// Pos() returns the ast.GoStmt.Go.
//
// Example printed form: // Example printed form:
// go println(t0, t1) // go println(t0, t1)
// go t3() // go t3()
@ -1045,6 +1063,7 @@ type Panic struct {
type Go struct { type Go struct {
anInstruction anInstruction
Call CallCommon Call CallCommon
pos token.Pos
} }
// The Defer instruction pushes the specified call onto a stack of // The Defer instruction pushes the specified call onto a stack of
@ -1052,6 +1071,8 @@ type Go struct {
// //
// See CallCommon for generic function call documentation. // See CallCommon for generic function call documentation.
// //
// Pos() returns the ast.DeferStmt.Defer.
//
// Example printed form: // Example printed form:
// defer println(t0, t1) // defer println(t0, t1)
// defer t3() // defer t3()
@ -1060,6 +1081,7 @@ type Go struct {
type Defer struct { type Defer struct {
anInstruction anInstruction
Call CallCommon Call CallCommon
pos token.Pos
} }
// The Send instruction sends X on channel Chan. // The Send instruction sends X on channel Chan.
@ -1107,15 +1129,15 @@ type MapUpdate struct {
} }
// A DebugRef instruction provides the position information for a // A DebugRef instruction provides the position information for a
// specific source-level reference that denotes the SSA value X. // specific source-level expression that compiles to the SSA value X.
// //
// DebugRef is a pseudo-instruction: it has no dynamic effect. // DebugRef is a pseudo-instruction: it has no dynamic effect.
// //
// Pos() returns the ast.Ident or ast.Selector.Sel of the source-level // Pos() returns Expr.Pos(), the position of the source-level
// reference. // expression.
// //
// Object() returns the source-level (var/const) object denoted by // Object() returns the source-level (var/const/func) object denoted
// that reference. // by Expr if it is an *ast.Ident; otherwise it is nil.
// //
// (By representing these as instructions, rather than out-of-band, // (By representing these as instructions, rather than out-of-band,
// consistency is maintained during transformation passes by the // consistency is maintained during transformation passes by the
@ -1124,8 +1146,8 @@ type MapUpdate struct {
type DebugRef struct { type DebugRef struct {
anInstruction anInstruction
X Value // the value whose position we're declaring X Value // the value whose position we're declaring
pos token.Pos // location of the reference Expr ast.Expr // the referring expression
object types.Object // the identity of the source var/const object types.Object // the identity of the source var/const/func
} }
// Embeddable mix-ins and helpers for common parts of other structs. ----------- // Embeddable mix-ins and helpers for common parts of other structs. -----------
@ -1385,8 +1407,8 @@ func (p *Package) Type(name string) (t *Type) {
} }
func (v *Call) Pos() token.Pos { return v.Call.pos } func (v *Call) Pos() token.Pos { return v.Call.pos }
func (s *Defer) Pos() token.Pos { return s.Call.pos } func (s *Defer) Pos() token.Pos { return s.pos }
func (s *Go) Pos() token.Pos { return s.Call.pos } func (s *Go) Pos() token.Pos { return s.pos }
func (s *MapUpdate) Pos() token.Pos { return s.pos } func (s *MapUpdate) Pos() token.Pos { return s.pos }
func (s *Panic) Pos() token.Pos { return s.pos } func (s *Panic) Pos() token.Pos { return s.pos }
func (s *Ret) Pos() token.Pos { return s.pos } func (s *Ret) Pos() token.Pos { return s.pos }
@ -1395,7 +1417,7 @@ func (s *Store) Pos() token.Pos { return s.pos }
func (s *If) Pos() token.Pos { return token.NoPos } func (s *If) Pos() token.Pos { return token.NoPos }
func (s *Jump) Pos() token.Pos { return token.NoPos } func (s *Jump) Pos() token.Pos { return token.NoPos }
func (s *RunDefers) Pos() token.Pos { return token.NoPos } func (s *RunDefers) Pos() token.Pos { return token.NoPos }
func (s *DebugRef) Pos() token.Pos { return s.pos } func (s *DebugRef) Pos() token.Pos { return s.Expr.Pos() }
// Operands. // Operands.

View File

@ -54,11 +54,12 @@ func main() {
impctx := importer.Config{Loader: importer.MakeGoBuildLoader(nil)} impctx := importer.Config{Loader: importer.MakeGoBuildLoader(nil)}
var debugMode bool
var mode ssa.BuilderMode var mode ssa.BuilderMode
for _, c := range *buildFlag { for _, c := range *buildFlag {
switch c { switch c {
case 'D': case 'D':
mode |= ssa.DebugInfo debugMode = true
case 'P': case 'P':
mode |= ssa.LogPackages | ssa.BuildSerially mode |= ssa.LogPackages | ssa.BuildSerially
case 'F': case 'F':
@ -115,7 +116,7 @@ func main() {
// Create and build SSA-form program representation. // Create and build SSA-form program representation.
prog := ssa.NewProgram(imp.Fset, mode) prog := ssa.NewProgram(imp.Fset, mode)
for _, info := range imp.Packages { for _, info := range imp.Packages {
prog.CreatePackage(info) prog.CreatePackage(info).SetDebugMode(debugMode)
} }
prog.BuildAll() prog.BuildAll()

View File

@ -20,6 +20,8 @@ import (
"code.google.com/p/go.tools/ssa" "code.google.com/p/go.tools/ssa"
) )
const debugMode = false
func allPackages() []string { func allPackages() []string {
var pkgs []string var pkgs []string
root := filepath.Join(runtime.GOROOT(), "src/pkg") + "/" root := filepath.Join(runtime.GOROOT(), "src/pkg") + "/"
@ -70,10 +72,10 @@ func TestStdlib(t *testing.T) {
alloc := memstats.Alloc alloc := memstats.Alloc
// Create SSA packages. // Create SSA packages.
prog := ssa.NewProgram(imp.Fset, ssa.DebugInfo|ssa.SanityCheckFunctions) prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
for _, info := range imp.Packages { for _, info := range imp.Packages {
if info.Err == nil { if info.Err == nil {
prog.CreatePackage(info) prog.CreatePackage(info).SetDebugMode(debugMode)
} }
} }

45
ssa/testdata/valueforexpr.go vendored Normal file
View File

@ -0,0 +1,45 @@
//+build ignore
package main
// This file is the input to TestCanonicalPos in source_test.go, which
// ensures that each expression e immediately following a /*@kind*/(x)
// annotation, when passed to Function.ValueForExpr(e), returns a
// non-nil Value of the same type as e and of kind 'kind'.
func f(param int) {
_ = /*@<nil>*/ (1 + 2) // (constant)
i := 0
/*@Call*/ (print( /*@BinOp*/ (i + 1)))
ch := /*@MakeChan*/ (make(chan int))
/*@UnOp*/ (<-ch)
x := /*@UnOp*/ (<-ch)
select {
case /*@Extract*/ (<-ch):
case x := /*@Extract*/ (<-ch):
}
defer /*@Function*/ (func() {
})()
go /*@Function*/ (func() {
})()
y := 0
if true && /*@BinOp*/ (bool(y > 0)) {
y = 1
}
_ = /*@Phi*/ (y)
map1 := /*@MakeMap*/ (make(map[string]string))
_ = /*@MakeMap*/ (map[string]string{"": ""})
_ = /*@MakeSlice*/ (make([]int, 0))
_ = /*@MakeClosure*/ (func() { print(param) })
sl := /*@Slice*/ ([]int{})
_ = /*@Alloc*/ (&struct{}{})
_ = /*@Slice*/ (sl[:0])
_ = /*@Alloc*/ (new(int))
var iface interface{}
_ = /*@TypeAssert*/ (iface.(int))
_ = /*@UnOp*/ (sl[0])
_ = /*@IndexAddr*/ (&sl[0])
_ = /*@Index*/ ([2]int{}[0])
var p *int
_ = /*@UnOp*/ (*p)
}