1
0
mirror of https://github.com/golang/go synced 2024-10-01 16:38:34 -06:00
go/ssa/source.go
Alan Donovan 55d678e697 go.tools/ssa: add debug information for all ast.Idents.
This CL adds three new functions to determine the SSA Value
for a given syntactic var, func or const object:
  Program.{Const,Func,Var}Value.
Since constants and functions are immutable, the first
two only need a types.Object; but each distinct
reference to a var may return a distinct Value, so the third
requires an ast.Ident parameter too.

Debug information for local vars is encoded in the
instruction stream in the form of DebugRef instructions,
which are a no-op but relate their operand to a particular
ident in the AST.  The beauty of this approach is that it
naturally stays consistent during optimisation passes
(e.g. lifting) without additional bookkeeping.

DebugRef instructions are only generated if the DebugMode
builder flag is set; I plan to make the policy more fine-
grained (per function).

DebugRef instructions are inserted for:
- expr(Ident) for rvalue idents
- address.store() for idents that update an lvalue
- address.address() for idents that take address of lvalue
  (this new method replaces all uses of lval.(address).addr)
- expr() for all constant expressions
- local ValueSpecs with implicit zero initialization (no RHS)
  (this case doesn't call store() or address())

To ensure we don't forget to emit debug info for uses of Idents,
we must use the lvalue mechanism consistently.  (Previously,
many simple cases had effectively inlined these functions.)
Similarly setCallFunc no longer inlines expr(Ident).

Also:
- Program.Value() has been inlined & specialized.
- Program.Package() has moved nearer the new lookup functions.
- refactoring: funcSyntax has lost paramFields, resultFields;
  gained funcType, which provides access to both.
- add package-level constants to Package.values map.
- opt: don't call localValueSpec for constants.
  (The resulting code is always optimised away.)

There are a number of comments asking whether Literals
should have positions.  Will address in a follow-up.

Added tests of all interesting cases.

R=gri
CC=golang-dev
https://golang.org/cl/11259044
2013-07-15 13:56:46 -04:00

370 lines
10 KiB
Go

package ssa
// This file defines utilities for working with source positions.
import (
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
)
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
p := int(pos)
base := f.Base()
return base <= p && p < base+f.Size()
}
// PathEnclosingInterval returns the Package and ast.Node that
// contain source interval [start, end), and all the node's ancestors
// up to the AST root. It searches all files of all packages in the
// program prog. exact is defined as for standalone
// PathEnclosingInterval.
//
// imp provides ASTs for the program's packages.
//
// pkg may be nil if no SSA package has yet been created for the found
// package. Call prog.CreatePackages(imp) to avoid this.
//
// The result is (nil, nil, false) if not found.
//
func (prog *Program) PathEnclosingInterval(imp *importer.Importer, start, end token.Pos) (pkg *Package, path []ast.Node, exact bool) {
for importPath, info := range imp.Packages {
for _, f := range info.Files {
if !tokenFileContainsPos(imp.Fset.File(f.Package), start) {
continue
}
if path, exact := PathEnclosingInterval(f, start, end); path != nil {
// TODO(adonovan): return the
// importPath; remove Prog as a
// parameter.
return prog.PackagesByPath[importPath], path, exact
}
}
}
return nil, nil, false
}
// EnclosingFunction returns the function that contains the syntax
// node denoted by path.
//
// Syntax associated with package-level variable specifications is
// enclosed by the package's init() function.
//
// 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).
//
func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
// Start with package-level function...
fn := findEnclosingPackageLevelFunction(pkg, path)
if fn == nil {
return nil // not in any function
}
// ...then walk down the nested anonymous functions.
n := len(path)
outer:
for i := range path {
if lit, ok := path[n-1-i].(*ast.FuncLit); ok {
for _, anon := range fn.AnonFuncs {
if anon.Pos() == lit.Type.Func {
fn = anon
continue outer
}
}
// SSA function not found:
// - package not yet built, or maybe
// - builder skipped FuncLit in dead block
// (in principle; but currently the Builder
// generates even dead FuncLits).
return nil
}
}
return fn
}
// HasEnclosingFunction returns true if the AST node denoted by path
// is contained within the declaration of some function or
// package-level variable.
//
// Unlike EnclosingFunction, the behaviour of this function does not
// depend on whether SSA code for pkg has been built, so it can be
// used to quickly reject check inputs that will cause
// EnclosingFunction to fail, prior to SSA building.
//
func HasEnclosingFunction(pkg *Package, path []ast.Node) bool {
return findEnclosingPackageLevelFunction(pkg, path) != nil
}
// findEnclosingPackageLevelFunction returns the Function
// corresponding to the package-level function enclosing path.
//
func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function {
if n := len(path); n >= 2 { // [... {Gen,Func}Decl File]
switch decl := path[n-2].(type) {
case *ast.GenDecl:
if decl.Tok == token.VAR && n >= 3 {
// Package-level 'var' initializer.
return pkg.init
}
case *ast.FuncDecl:
if decl.Recv == nil && decl.Name.Name == "init" {
// Explicit init() function.
return pkg.init
}
// Declared function/method.
return findNamedFunc(pkg, decl.Name.NamePos)
}
}
return nil // not in any function
}
// findNamedFunc returns the named function whose FuncDecl.Ident is at
// position pos.
//
func findNamedFunc(pkg *Package, pos token.Pos) *Function {
// Look at all package members and method sets of named types.
// Not very efficient.
for _, mem := range pkg.Members {
switch mem := mem.(type) {
case *Function:
if mem.Pos() == pos {
return mem
}
case *Type:
for _, meth := range pkg.Prog.MethodSet(mem.Type()) {
if meth.Synthetic == "" && meth.Pos() == pos {
return meth
}
}
for _, meth := range pkg.Prog.MethodSet(pointer(mem.Type())) {
if meth.Synthetic == "" && meth.Pos() == pos {
return meth
}
}
}
}
return nil
}
// CanonicalPos returns the canonical position of the AST node n,
//
// For each Node kind that may generate an SSA Value or Instruction,
// exactly one token within it is designated as "canonical". The
// position of that token is returned by {Value,Instruction}.Pos().
// The specifications of those methods determine the implementation of
// this function.
//
// TODO(adonovan): test coverage.
//
func CanonicalPos(n ast.Node) token.Pos {
// Comments show the Value/Instruction kinds v that may be
// created by n such that CanonicalPos(n) == v.Pos().
switch n := n.(type) {
case *ast.ParenExpr:
return CanonicalPos(n.X)
case *ast.CallExpr:
// f(x): *Call, *Go, *Defer, *Literal (e.g. len)
// T(x): *ChangeType, *Convert, *MakeInterface, *ChangeInterface, *Literal.
// make(): *MakeMap, *MakeChan, *MakeSlice.
// new(): *Alloc.
// panic(): *Panic.
return n.Lparen
case *ast.BasicLit:
return n.ValuePos // *Literal
case *ast.Ident:
return n.NamePos // *Parameter, *Alloc, *Capture, *Literal
case *ast.TypeAssertExpr:
return n.Lparen // *ChangeInterface or *TypeAssertExpr
case *ast.SelectorExpr:
return n.Sel.NamePos // *MakeClosure, *Field, *FieldAddr, *Literal
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, *BinOp or *Literal
case *ast.UnaryExpr:
return n.OpPos // *Phi, *UnOp, or *Literal
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
}
// --- Lookup functions for source-level named entities (types.Objects) ---
// Package returns the SSA Package corresponding to the specified
// type-checker package object.
// It returns nil if no such SSA package has been created.
//
func (prog *Program) Package(obj *types.Package) *Package {
return prog.packages[obj]
}
// packageLevelValue returns the package-level value corresponding to
// the specified named object, which may be a package-level const
// (*Literal), var (*Global) or func (*Function) of some package in
// prog. It returns nil if the object is not found.
//
func (prog *Program) packageLevelValue(obj types.Object) Value {
if pkg, ok := prog.packages[obj.Pkg()]; ok {
return pkg.values[obj]
}
return nil
}
// FuncValue returns the SSA Value denoted by the source-level named
// function obj. The result may be a *Function or a *Builtin, or nil
// if not found.
//
func (prog *Program) FuncValue(obj *types.Func) Value {
// Universal built-in?
if v, ok := prog.builtins[obj]; ok {
return v
}
// Package-level function?
if v := prog.packageLevelValue(obj); v != nil {
return v
}
// Concrete method?
if v := prog.concreteMethods[obj]; v != nil {
return v
}
// TODO(adonovan): interface method wrappers? other wrappers?
return nil
}
// ConstValue returns the SSA Value denoted by the source-level named
// constant obj. The result may be a *Literal, or nil if not found.
//
func (prog *Program) ConstValue(obj *types.Const) *Literal {
// Universal constant? {true,false,nil}
if obj.Parent() == types.Universe {
// TODO(adonovan): opt: share, don't reallocate.
return NewLiteral(obj.Val(), obj.Type(), obj.Pos())
}
// Package-level named constant?
if v := prog.packageLevelValue(obj); v != nil {
return v.(*Literal)
}
// TODO(adonovan): need a per-function const object map. For
// now, just return a new literal.
//
// Design question: should literal (constant) values even have
// a position? Is their identity important? Should two
// different references to Math.pi be distinguishable in any
// way? From an analytical perspective, their type and value
// tell you all you need to know; they're interchangeable.
// Experiment with removing Literal.Pos().
return NewLiteral(obj.Val(), obj.Type(), obj.Pos())
}
// VarValue returns the SSA Value that corresponds to a specific
// identifier denoting the source-level named variable obj.
//
// VarValue returns nil if a local variable was not found, perhaps
// because its package was not built, the DebugInfo flag was not set
// during SSA construction, or the value was optimized away.
//
// ref must be the path to an ast.Ident (e.g. from
// PathEnclosingInterval), and that ident must resolve to obj.
//
// The Value of a defining (as opposed to referring) identifier is the
// value assigned to it in its definition.
//
// In many cases where the identifier appears in an lvalue context,
// the resulting Value is the var's address, not its value.
// For example, x in all these examples:
// x.y = 0
// x[0] = 0
// _ = x[:]
// x = X{}
// _ = &x
// x.method() (iff method is on &x)
// and all package-level vars. (This situation can be detected by
// comparing the types of the Var and Value.)
//
func (prog *Program) VarValue(obj *types.Var, ref []ast.Node) Value {
id := ref[0].(*ast.Ident)
// Package-level variable?
if v := prog.packageLevelValue(obj); v != nil {
return v.(*Global)
}
// It's a local variable (or param) of some function.
// The reference may occur inside a lexically nested function,
// so find that first.
pkg := prog.packages[obj.Pkg()]
if pkg == nil {
panic("no package for " + obj.String())
}
fn := EnclosingFunction(pkg, ref)
if fn == nil {
return nil // e.g. SSA not built
}
// Defining ident of a parameter?
if id.Pos() == obj.Pos() {
for _, param := range fn.Params {
if param.Object() == obj {
return param
}
}
}
// Other ident?
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if ref, ok := instr.(*DebugRef); ok {
if ref.Pos() == id.Pos() {
return ref.X
}
}
}
}
return nil // e.g. DebugInfo unset, or var optimized away
}