1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:14:46 -07:00

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
This commit is contained in:
Alan Donovan 2013-07-15 13:56:46 -04:00
parent f1a889124d
commit 55d678e697
13 changed files with 623 additions and 101 deletions

View File

@ -76,7 +76,7 @@ type builder struct {
// emits initialization code into from.init if not already done.
//
func (b *builder) lookup(from *Package, obj types.Object) Value {
v := from.Prog.Value(obj)
v := from.Prog.packages[obj.Pkg()].values[obj]
switch v := v.(type) {
case *Function:
if from == v.Pkg {
@ -351,7 +351,7 @@ func (b *builder) selectField(fn *Function, e *ast.SelectorExpr, wantAddr, escap
// for !wantAddr, when safe (i.e. e.X is addressible),
// since (FieldAddr;Load) is cheaper than (Load;Field).
// Requires go/types to expose addressibility.
v = b.addr(fn, e.X, escaping).(address).addr
v = b.addr(fn, e.X, escaping).address(fn)
} else {
v = b.expr(fn, e.X)
}
@ -439,12 +439,15 @@ func (b *builder) selectField(fn *Function, e *ast.SelectorExpr, wantAddr, escap
func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
switch e := e.(type) {
case *ast.Ident:
if isBlankIdent(e) {
return blank{}
}
obj := fn.Pkg.objectOf(e)
v := b.lookup(fn.Pkg, obj) // var (address)
if v == nil {
v = fn.lookup(obj, escaping)
}
return address{addr: v}
return address{addr: v, id: e, object: obj}
case *ast.CompositeLit:
t := deref(fn.Pkg.typeOf(e))
@ -477,7 +480,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
var et types.Type
switch t := fn.Pkg.typeOf(e.X).Underlying().(type) {
case *types.Array:
x = b.addr(fn, e.X, escaping).(address).addr
x = b.addr(fn, e.X, escaping).address(fn)
et = pointer(t.Elem())
case *types.Pointer: // *array
x = b.expr(fn, e.X)
@ -502,7 +505,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
return address{addr: fn.emit(v)}
case *ast.StarExpr:
return address{addr: b.expr(fn, e.X), star: e.Star}
return address{addr: b.expr(fn, e.X), starPos: e.Star}
}
panic(fmt.Sprintf("unexpected address expression: %T", e))
@ -516,13 +519,13 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
// in an addressable location.
//
func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) {
if addr, ok := loc.(address); ok {
if _, ok := loc.(address); ok {
if e, ok := e.(*ast.CompositeLit); ok {
typ := addr.typ()
typ := loc.typ()
switch typ.Underlying().(type) {
case *types.Pointer: // implicit & -- possibly escaping
ptr := b.addr(fn, e, true).(address).addr
addr.store(fn, ptr) // copy address
ptr := b.addr(fn, e, true).address(fn)
loc.store(fn, ptr) // copy address
return
case *types.Interface:
@ -531,7 +534,7 @@ func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) {
// Fall back to copying.
default:
b.compLit(fn, addr.addr, e, typ) // in place
b.compLit(fn, loc.address(fn), e, typ) // in place
return
}
}
@ -544,7 +547,16 @@ func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) {
//
func (b *builder) expr(fn *Function, e ast.Expr) Value {
if v := fn.Pkg.info.ValueOf(e); v != nil {
return NewLiteral(v, fn.Pkg.typeOf(e), CanonicalPos(e))
// TODO(adonovan): if e is an ident referring to a named
// Const, should we share the Constant's literal?
// Then it won't have a position within the function.
// Do we want it to have the position of the Ident or
// the definition of the const expression?
lit := NewLiteral(v, fn.Pkg.typeOf(e), CanonicalPos(e))
if id, ok := unparen(e).(*ast.Ident); ok {
emitDebugRef(fn, id, lit)
}
return lit
}
switch e := e.(type) {
@ -561,9 +573,8 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
Pkg: fn.Pkg,
Prog: fn.Prog,
syntax: &funcSyntax{
paramFields: e.Type.Params,
resultFields: e.Type.Results,
body: e.Body,
functype: e.Type,
body: e.Body,
},
}
fn.AnonFuncs = append(fn.AnonFuncs, fn2)
@ -621,7 +632,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
case *ast.UnaryExpr:
switch e.Op {
case token.AND: // &X --- potentially escaping.
return b.addr(fn, e.X, true).(address).addr
return b.addr(fn, e.X, true).address(fn)
case token.ADD:
return b.expr(fn, e.X)
case token.NOT, token.ARROW, token.SUB, token.XOR: // ! <- - ^
@ -657,7 +668,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
switch fn.Pkg.typeOf(e.X).Underlying().(type) {
case *types.Array:
// Potentially escaping.
x = b.addr(fn, e.X, true).(address).addr
x = b.addr(fn, e.X, true).address(fn)
case *types.Basic, *types.Slice, *types.Pointer: // *array
x = b.expr(fn, e.X)
default:
@ -691,8 +702,10 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
}
return v // (func)
}
// Local?
return emitLoad(fn, fn.lookup(obj, false)) // var (address)
// Local var.
v := emitLoad(fn, fn.lookup(obj, false)) // var (address)
emitDebugRef(fn, e, v)
return v
case *ast.SelectorExpr:
// p.M where p is a package.
@ -811,7 +824,7 @@ func (b *builder) findMethod(fn *Function, base ast.Expr, id Id) (*Function, Val
// pointer formal receiver; but the actual
// value is not a pointer.
// Implicit & -- possibly escaping.
return m, b.addr(fn, base, true).(address).addr
return m, b.addr(fn, base, true).address(fn)
}
}
return nil, nil
@ -829,25 +842,12 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
sel, ok := unparen(e.Fun).(*ast.SelectorExpr)
// Case 0: e.Fun evaluates normally to a function.
if !ok {
if !ok || fn.Pkg.info.IsPackageRef(sel) != nil {
c.Func = b.expr(fn, e.Fun)
return
}
// Case 1: call of form x.F() where x is a package name.
if obj := fn.Pkg.info.IsPackageRef(sel); obj != nil {
// This is a specialization of expr(ast.Ident(obj)).
if v := b.lookup(fn.Pkg, obj); v != nil {
if _, ok := v.(*Function); !ok {
v = emitLoad(fn, v) // var (address)
}
c.Func = v
return
}
panic("undefined package-qualified name: " + obj.Name())
}
// Case 2a: X.f() or (*X).f(): a statically dipatched call to
// Case 1: X.f() or (*X).f(): a statically dipatched call to
// the method f in the method-set of X or *X. X may be
// an interface. Treat like case 0.
// TODO(adonovan): opt: inline expr() here, to make the call static
@ -991,7 +991,7 @@ func (b *builder) assignOp(fn *Function, loc lvalue, incr Value, op token.Token)
// buildGlobal emits code to the g.Pkg.init function for the variable
// definition(s) of g. Effects occur out of lexical order; see
// explanation at globalValueSpec.
// Precondition: g == g.Prog.Value(obj)
// Precondition: g == g.Prog.value(obj)
//
func (b *builder) buildGlobal(g *Global, obj types.Object) {
spec := g.spec
@ -1014,7 +1014,7 @@ func (b *builder) buildGlobal(g *Global, obj types.Object) {
// B) with g and obj nil, to initialize all globals in the same ValueSpec.
// This occurs during the left-to-right traversal over the ast.File.
//
// Precondition: g == g.Prog.Value(obj)
// Precondition: g == g.Prog.value(obj)
//
// Package-level var initialization order is quite subtle.
// The side effects of:
@ -1061,7 +1061,7 @@ func (b *builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global
} else {
// Mode B: initialize all globals.
if !isBlankIdent(id) {
g2 := init.Prog.Value(init.Pkg.objectOf(id)).(*Global)
g2 := init.Pkg.values[init.Pkg.objectOf(id)].(*Global)
if g2.spec == nil {
continue // already done
}
@ -1095,7 +1095,7 @@ func (b *builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global
result := tuple.Type().(*types.Tuple)
for i, id := range spec.Names {
if !isBlankIdent(id) {
g := init.Prog.Value(init.Pkg.objectOf(id)).(*Global)
g := init.Pkg.values[init.Pkg.objectOf(id)].(*Global)
g.spec = nil // just an optimization
emitStore(init, g, emitExtract(init, tuple, i, result.At(i).Type()))
}
@ -1117,10 +1117,10 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) {
// e.g. var x, y = 0, 1
// 1:1 assignment
for i, id := range spec.Names {
var lval lvalue = blank{}
if !isBlankIdent(id) {
lval = address{addr: fn.addLocalForIdent(id)}
fn.addLocalForIdent(id)
}
lval := b.addr(fn, id, false) // non-escaping
b.exprInPlace(fn, lval, spec.Values[i])
}
@ -1129,7 +1129,12 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) {
// Locals are implicitly zero-initialized.
for _, id := range spec.Names {
if !isBlankIdent(id) {
fn.addLocalForIdent(id)
lhs := fn.addLocalForIdent(id)
// TODO(adonovan): opt: use zero literal in
// lieu of load, if type permits.
if fn.debugInfo() {
emitDebugRef(fn, id, emitLoad(fn, lhs))
}
}
}
@ -1139,8 +1144,9 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) {
result := tuple.Type().(*types.Tuple)
for i, id := range spec.Names {
if !isBlankIdent(id) {
lhs := fn.addLocalForIdent(id)
emitStore(fn, lhs, emitExtract(fn, tuple, i, result.At(i).Type()))
fn.addLocalForIdent(id)
lhs := b.addr(fn, id, false) // non-escaping
lhs.store(fn, emitExtract(fn, tuple, i, result.At(i).Type()))
}
}
}
@ -1452,7 +1458,10 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl
x = b.expr(fn, unparen(ass.X).(*ast.TypeAssertExpr).X)
case *ast.AssignStmt: // y := x.(type)
x = b.expr(fn, unparen(ass.Rhs[0]).(*ast.TypeAssertExpr).X)
emitStore(fn, fn.addLocalForIdent(ass.Lhs[0].(*ast.Ident)), x)
id := ass.Lhs[0].(*ast.Ident)
fn.addLocalForIdent(id)
lval := b.addr(fn, id, false) // non-escaping
lval.store(fn, x)
}
done := fn.newBasicBlock("typeswitch.done")
@ -1999,9 +2008,11 @@ start:
case *ast.DeclStmt: // Con, Var or Typ
d := s.Decl.(*ast.GenDecl)
for _, spec := range d.Specs {
if vs, ok := spec.(*ast.ValueSpec); ok {
b.localValueSpec(fn, vs)
if d.Tok == token.VAR {
for _, spec := range d.Specs {
if vs, ok := spec.(*ast.ValueSpec); ok {
b.localValueSpec(fn, vs)
}
}
}
@ -2290,7 +2301,7 @@ func (b *builder) buildDecl(pkg *Package, decl ast.Decl) {
} else {
// Package-level function.
b.buildFunction(pkg.Prog.Value(pkg.objectOf(id)).(*Function))
b.buildFunction(pkg.values[pkg.objectOf(id)].(*Function))
}
}

View File

@ -23,6 +23,7 @@ const (
SanityCheckFunctions // Perform sanity checking of function bodies
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
BuildSerially // Build packages serially, not in parallel.
DebugInfo // Include DebugRef instructions [TODO(adonovan): finer grain?]
)
// NewProgram returns a new SSA Program initially containing no
@ -88,10 +89,12 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
pkg.Members[name] = &Type{object: obj}
case *types.Const:
pkg.Members[name] = &Constant{
c := &Constant{
object: obj,
Value: NewLiteral(obj.Val(), obj.Type(), obj.Pos()),
}
pkg.values[obj] = c.Value
pkg.Members[name] = c
case *types.Var:
spec, _ := syntax.(*ast.ValueSpec)
@ -112,10 +115,9 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
if decl, ok := syntax.(*ast.FuncDecl); ok {
synthetic = ""
fs = &funcSyntax{
recvField: decl.Recv,
paramFields: decl.Type.Params,
resultFields: decl.Type.Results,
body: decl.Body,
functype: decl.Type,
recvField: decl.Recv,
body: decl.Body,
}
}
sig := obj.Type().(*types.Signature)

View File

@ -3,6 +3,7 @@ package ssa
// Helpers for emitting SSA instructions.
import (
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/types"
@ -29,6 +30,27 @@ func emitLoad(f *Function, addr Value) *UnOp {
return v
}
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
// reference id with local var/const value v.
//
func emitDebugRef(f *Function, id *ast.Ident, v Value) {
if !f.debugInfo() {
return // debugging not enabled
}
if isBlankIdent(id) {
return
}
obj := f.Pkg.objectOf(id)
if obj.Parent() == types.Universe {
return // skip nil/true/false
}
f.emit(&DebugRef{
X: v,
pos: id.Pos(),
object: obj,
})
}
// emitArith emits to f code to compute the binary operation op(x, y)
// where op is an eager shift, logical or arithmetic operation.
// (Use emitCompare() for comparisons and Builder.logicalBinop() for

View File

@ -145,10 +145,9 @@ type lblock struct {
// funcSyntax holds the syntax tree for the function declaration and body.
type funcSyntax struct {
recvField *ast.FieldList
paramFields *ast.FieldList
resultFields *ast.FieldList
body *ast.BlockStmt
recvField *ast.FieldList
body *ast.BlockStmt
functype *ast.FuncType
}
// labelledBlock returns the branch target associated with the
@ -185,7 +184,9 @@ func (f *Function) addParamObj(obj types.Object) *Parameter {
if name == "" {
name = fmt.Sprintf("arg%d", len(f.Params))
}
return f.addParam(name, obj.Type(), obj.Pos())
param := f.addParam(name, obj.Type(), obj.Pos())
param.object = obj
return param
}
// addSpilledParam declares a parameter that is pre-spilled to the
@ -238,9 +239,9 @@ func (f *Function) createSyntacticParams() {
}
// Parameters.
if f.syntax.paramFields != nil {
if f.syntax.functype.Params != nil {
n := len(f.Params) // 1 if has recv, 0 otherwise
for _, field := range f.syntax.paramFields.List {
for _, field := range f.syntax.functype.Params.List {
for _, n := range field.Names {
f.addSpilledParam(f.Pkg.objectOf(n))
}
@ -252,8 +253,8 @@ func (f *Function) createSyntacticParams() {
}
// Named results.
if f.syntax.resultFields != nil {
for _, field := range f.syntax.resultFields.List {
if f.syntax.functype.Results != nil {
for _, field := range f.syntax.functype.Results.List {
// Implicit "var" decl of locals for named results.
for _, n := range field.Names {
f.namedResults = append(f.namedResults, f.addLocalForIdent(n))
@ -373,6 +374,12 @@ func (f *Function) removeNilBlocks() {
f.Blocks = f.Blocks[:j]
}
// debugInfo reports whether debug info is wanted for this function.
func (f *Function) debugInfo() bool {
// TODO(adonovan): make the policy finer grained.
return f.Prog.mode&DebugInfo != 0
}
// 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.

View File

@ -150,6 +150,9 @@ func findMethodSet(i *interpreter, typ types.Type) ssa.MethodSet {
// read the next instruction from.
func visitInstr(fr *frame, instr ssa.Instruction) continuation {
switch instr := instr.(type) {
case *ssa.DebugRef:
// no-op
case *ssa.UnOp:
fr.env[instr] = unop(instr, fr.get(instr.X))

View File

@ -4,8 +4,10 @@ package ssa
// expressions.
import (
"code.google.com/p/go.tools/go/types"
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/types"
)
// An lvalue represents an assignable location that may appear on the
@ -15,23 +17,39 @@ import (
type lvalue interface {
store(fn *Function, v Value) // stores v into the location
load(fn *Function) Value // loads the contents of the location
address(fn *Function) Value // address of the location
typ() types.Type // returns the type of the location
}
// An address is an lvalue represented by a true pointer.
type address struct {
addr Value
star token.Pos // source position, if explicit *addr
addr Value
starPos token.Pos // source position, if from explicit *addr
id *ast.Ident // source syntax, if from *ast.Ident
object types.Object // source var, if from *ast.Ident
}
func (a address) load(fn *Function) Value {
load := emitLoad(fn, a.addr)
load.pos = a.star
load.pos = a.starPos
return load
}
func (a address) store(fn *Function, v Value) {
emitStore(fn, a.addr, v).pos = a.star
store := emitStore(fn, a.addr, v)
store.pos = a.starPos
if a.id != nil {
// store.Val is v converted for assignability.
emitDebugRef(fn, a.id, store.Val)
}
}
func (a address) address(fn *Function) Value {
if a.id != nil {
// NB: this kind of DebugRef yields the object's address.
emitDebugRef(fn, a.id, a.addr)
}
return a.addr
}
func (a address) typ() types.Type {
@ -65,6 +83,10 @@ func (e *element) store(fn *Function, v Value) {
})
}
func (e *element) address(fn *Function) Value {
panic("map/string elements are not addressable")
}
func (e *element) typ() types.Type {
return e.t
}
@ -82,6 +104,10 @@ func (bl blank) store(fn *Function, v Value) {
// no-op
}
func (bl blank) address(fn *Function) Value {
panic("blank var is not addressable")
}
func (bl blank) typ() types.Type {
// This should be the type of the blank Ident; the typechecker
// doesn't provide this yet, but fortunately, we don't need it

View File

@ -355,6 +355,11 @@ func (s *MapUpdate) String() string {
return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
}
func (s *DebugRef) String() string {
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)
}
func (p *Package) String() string {
return "package " + p.Object.Path()
}

View File

@ -155,6 +155,7 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
case *Store:
case *TypeAssert:
case *UnOp:
case *DebugRef:
// TODO(adonovan): implement checks.
default:
panic(fmt.Sprintf("Unknown instruction type: %T", instr))

View File

@ -2,13 +2,12 @@ package ssa
// This file defines utilities for working with source positions.
// TODO(adonovan): move this and source_ast.go to a new subpackage
// since neither depends on SSA internals.
import (
"code.google.com/p/go.tools/importer"
"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)
@ -34,10 +33,13 @@ func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
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(prog.Fset.File(f.Package), start) {
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
}
}
@ -227,3 +229,141 @@ func CanonicalPos(n ast.Node) token.Pos {
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
}

View File

@ -3,18 +3,22 @@ package ssa_test
// This file defines tests of the source and source_ast utilities.
// TODO(adonovan): exhaustive tests that run over the whole input
// tree, not just andcrafted examples.
// tree, not just handcrafted examples.
import (
"bytes"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/ssa"
"fmt"
"go/ast"
"go/parser"
"go/token"
"regexp"
"strings"
"testing"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/ssa"
)
// -------- Tests of source_ast.go -------------------------------------
@ -277,3 +281,168 @@ func TestEnclosingFunction(t *testing.T) {
}
}
}
func TestObjValueLookup(t *testing.T) {
imp := importer.New(new(importer.Context)) // (uses GCImporter)
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.DeclarationErrors|parser.ParseComments)
if err != nil {
t.Errorf("parse error: %s", err)
return
}
// Maps each var Ident (represented "name:linenum") to the
// kind of ssa.Value we expect (represented "Literal", "&Alloc").
expectations := make(map[string]string)
// Find all annotations of form x::BinOp, &y::Alloc, etc.
re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`)
for _, c := range f.Comments {
text := c.Text()
pos := imp.Fset.Position(c.Pos())
fmt.Println(pos.Line, text)
for _, m := range re.FindAllStringSubmatch(text, -1) {
key := fmt.Sprintf("%s:%d", m[2], pos.Line)
value := m[1] + m[3]
expectations[key] = value
}
}
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
if err != nil {
t.Error(err.Error())
return
}
prog := ssa.NewProgram(imp.Fset, ssa.DebugInfo /*|ssa.LogFunctions*/)
prog.CreatePackages(imp)
pkg := prog.Package(info.Pkg)
pkg.Build()
// Gather all idents and objects in file.
objs := make(map[types.Object]bool)
var ids []*ast.Ident
ast.Inspect(f, func(n ast.Node) bool {
if id, ok := n.(*ast.Ident); ok {
ids = append(ids, id)
if obj := info.ObjectOf(id); obj != nil {
objs[obj] = true
}
}
return true
})
// Check invariants for func and const objects.
for obj := range objs {
switch obj := obj.(type) {
case *types.Func:
if obj.Name() == "interfaceMethod" {
continue // TODO(adonovan): not yet implemented.
}
checkFuncValue(t, prog, obj)
case *types.Const:
checkConstValue(t, prog, obj)
}
}
// Check invariants for var objects.
// The result varies based on the specific Ident.
for _, id := range ids {
if obj, ok := info.ObjectOf(id).(*types.Var); ok {
ref, _ := ssa.PathEnclosingInterval(f, id.Pos(), id.Pos())
pos := imp.Fset.Position(id.Pos())
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
if exp == "" {
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
continue
}
wantAddr := false
if exp[0] == '&' {
wantAddr = true
exp = exp[1:]
}
checkVarValue(t, prog, ref, obj, exp, wantAddr)
}
}
}
func checkFuncValue(t *testing.T, prog *ssa.Program, obj *types.Func) {
v := prog.FuncValue(obj)
// fmt.Printf("FuncValue(%s) = %s\n", obj, v) // debugging
if v == nil {
t.Errorf("FuncValue(%s) == nil", obj)
return
}
// v must be an *ssa.Function or *ssa.Builtin.
v2, _ := v.(interface {
Object() types.Object
})
if v2 == nil {
t.Errorf("FuncValue(%s) = %s %T; has no Object() method",
obj, v.Name(), v)
return
}
if vobj := v2.Object(); vobj != obj {
t.Errorf("FuncValue(%s).Object() == %s; value was %s",
obj, vobj, v.Name())
return
}
if !types.IsIdentical(v.Type(), obj.Type()) {
t.Errorf("FuncValue(%s).Type() == %s", obj, v.Type())
return
}
}
func checkConstValue(t *testing.T, prog *ssa.Program, obj *types.Const) {
lit := prog.ConstValue(obj)
// fmt.Printf("ConstValue(%s) = %s\n", obj, lit) // debugging
if lit == nil {
t.Errorf("ConstValue(%s) == nil", obj)
return
}
if !types.IsIdentical(lit.Type(), obj.Type()) {
t.Errorf("ConstValue(%s).Type() == %s", obj, lit.Type())
return
}
if obj.Name() != "nil" {
if !exact.Compare(lit.Value, token.EQL, obj.Val()) {
t.Errorf("ConstValue(%s).Value (%s) != %s",
obj, lit.Value, obj.Val())
return
}
}
}
func checkVarValue(t *testing.T, prog *ssa.Program, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) {
// The prefix of all assertions messages.
prefix := fmt.Sprintf("VarValue(%s @ L%d)",
obj, prog.Fset.Position(ref[0].Pos()).Line)
v := prog.VarValue(obj, ref)
// Kind is the concrete type of the ssa Value.
gotKind := "nil"
if v != nil {
gotKind = fmt.Sprintf("%T", v)[len("*ssa."):]
}
// fmt.Printf("%s = %v (kind %q; expect %q) addr=%t\n", prefix, v, gotKind, expKind, wantAddr) // debugging
// Check the kinds match.
// "nil" indicates expected failure (e.g. optimized away).
if expKind != gotKind {
t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind)
}
// Check the types match.
// If wantAddr, the expected type is the object's address.
if v != nil {
expType := obj.Type()
if wantAddr {
expType = types.NewPointer(expType)
}
if !types.IsIdentical(v.Type(), expType) {
t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType)
}
}
}

View File

@ -351,6 +351,7 @@ type Capture struct {
//
type Parameter struct {
name string
object types.Object // a *types.Var; nil for non-source locals
typ types.Type
pos token.Pos
parent *Function
@ -412,13 +413,14 @@ type Global struct {
// A Builtin represents a built-in function, e.g. len.
//
// Builtins are immutable values. Builtins do not have addresses.
// Builtins can only appear in CallCommon.Func.
//
// Type() returns a *types.Builtin.
// Built-in functions may have polymorphic or variadic types that are
// not expressible in Go's type system.
//
type Builtin struct {
Object *types.Func // canonical types.Universe object for this built-in
object *types.Func // canonical types.Universe object for this built-in
}
// Value-defining instructions ----------------------------------------
@ -1136,6 +1138,28 @@ type MapUpdate struct {
pos token.Pos
}
// A DebugRef instruction provides the position information for a
// specific source-level reference that denotes the SSA value X.
//
// DebugRef is a pseudo-instruction: it has no dynamic effect.
//
// Pos() returns the ast.Ident or ast.Selector.Sel of the source-level
// reference.
//
// Object() returns the source-level (var/const) object denoted by
// that reference.
//
// (By representing these as instructions, rather than out-of-band,
// consistency is maintained during transformation passes by the
// ordinary SSA renaming machinery.)
//
type DebugRef struct {
anInstruction
X Value // the value whose position we're declaring
pos token.Pos // location of the reference
object types.Object // the identity of the source var/const
}
// Embeddable mix-ins and helpers for common parts of other structs. -----------
// Register is a mix-in embedded by all SSA values that are also
@ -1302,10 +1326,11 @@ func (s *Call) Value() *Call { return s }
func (s *Defer) Value() *Call { return nil }
func (s *Go) Value() *Call { return nil }
func (v *Builtin) Type() types.Type { return v.Object.Type() }
func (v *Builtin) Name() string { return v.Object.Name() }
func (v *Builtin) Type() types.Type { return v.object.Type() }
func (v *Builtin) Name() string { return v.object.Name() }
func (*Builtin) Referrers() *[]Instruction { return nil }
func (v *Builtin) Pos() token.Pos { return token.NoPos }
func (v *Builtin) Object() types.Object { return v.object }
func (v *Capture) Type() types.Type { return v.typ }
func (v *Capture) Name() string { return v.name }
@ -1329,6 +1354,7 @@ func (v *Function) Object() types.Object { return v.object }
func (v *Parameter) Type() types.Type { return v.typ }
func (v *Parameter) Name() string { return v.name }
func (v *Parameter) Object() types.Object { return v.object }
func (v *Parameter) Referrers() *[]Instruction { return &v.referrers }
func (v *Parameter) Pos() token.Pos { return v.pos }
func (v *Parameter) Parent() *Function { return v.parent }
@ -1401,29 +1427,6 @@ func (p *Package) Type(name string) (t *Type) {
return
}
// Value returns the program-level value corresponding to the
// specified named object, which may be a universal built-in
// (*Builtin) or a package-level var (*Global) or func (*Function) of
// some package in prog. It returns nil if the object is not found.
//
func (prog *Program) Value(obj types.Object) Value {
if p := obj.Pkg(); p != nil {
if pkg, ok := prog.packages[p]; ok {
return pkg.values[obj]
}
return nil
}
return prog.builtins[obj]
}
// 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(pkg *types.Package) *Package {
return prog.packages[pkg]
}
func (v *Call) Pos() token.Pos { return v.Call.pos }
func (s *Defer) Pos() token.Pos { return s.Call.pos }
func (s *Go) Pos() token.Pos { return s.Call.pos }
@ -1435,6 +1438,7 @@ func (s *Store) Pos() token.Pos { return s.pos }
func (s *If) 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 *DebugRef) Pos() token.Pos { return s.pos }
// Operands.
@ -1478,6 +1482,10 @@ func (v *Convert) Operands(rands []*Value) []*Value {
return append(rands, &v.X)
}
func (s *DebugRef) Operands(rands []*Value) []*Value {
return append(rands, &s.X)
}
func (v *Extract) Operands(rands []*Value) []*Value {
return append(rands, &v.Tuple)
}

View File

@ -19,6 +19,7 @@ import (
var buildFlag = flag.String("build", "", `Options controlling the SSA builder.
The value is a sequence of zero or more of these letters:
C perform sanity [C]hecking of the SSA form.
D include debug info for every function.
P log [P]ackage inventory.
F log [F]unction SSA code.
S log [S]ource locations as SSA builder progresses.
@ -56,6 +57,8 @@ func main() {
var mode ssa.BuilderMode
for _, c := range *buildFlag {
switch c {
case 'D':
mode |= ssa.DebugInfo
case 'P':
mode |= ssa.LogPackages | ssa.BuildSerially
case 'F':

125
ssa/testdata/objlookup.go vendored Normal file
View File

@ -0,0 +1,125 @@
//+build ignore
package main
// This file is the input to TestObjValueLookup in source_test.go,
// which ensures that each occurrence of an ident defining or
// referring to a func, var or const object can be mapped to its
// corresponding SSA Value.
//
// For every reference to a var object, we use annotations in comments
// to denote both the expected SSA Value kind, and whether to expect
// its value (x) or its address (&x).
//
// For const and func objects, the results don't vary by reference and
// are always values not addresses, so no annotations are needed.
import "fmt"
type J int
func (*J) method() {}
const globalConst = 0
var globalVar int // &globalVar::Global
func globalFunc() {}
type I interface {
interfaceMethod() // TODO(adonovan): unimplemented (blacklisted in source_test)
}
type S struct {
x int
}
func main() {
var v0 int = 1 // v0::Literal (simple local value spec)
if v0 > 0 { // v0::Literal
v0 = 2 // v0::Literal
}
print(v0) // v0::Phi
// v1 is captured and thus implicitly address-taken.
var v1 int = 1 // v1::Literal
v1 = 2 // v1::Literal
fmt.Println(v1) // v1::UnOp (load)
f := func(param int) { // f::MakeClosure param::Parameter
if y := 1; y > 0 { // y::Literal
print(v1, param) // v1::UnOp (load) param::Parameter
}
param = 2 // param::Literal
println(param) // param::Literal
}
f(0) // f::MakeClosure
var v2 int // v2::Literal (implicitly zero-initialized local value spec)
print(v2) // v2::Literal
m := make(map[string]int) // m::MakeMap
// Local value spec with multi-valued RHS:
var v3, v4 = m[""] // v3::Extract v4::Extract m::MakeMap
print(v3) // v3::Extract
print(v4) // v4::Extract
v3++ // v3::BinOp (assign with op)
v3 += 2 // v3::BinOp (assign with op)
v5, v6 := false, "" // v5::Literal v6::Literal (defining assignment)
print(v5) // v5::Literal
print(v6) // v6::Literal
var v7 S // v7::UnOp (load from Alloc)
v7.x = 1 // &v7::Alloc
var v8 [1]int // v8::UnOp (load from Alloc)
v8[0] = 0 // &v8::Alloc
print(v8[:]) // &v8::Alloc
_ = v8[0] // v8::UnOp (load from Alloc)
_ = v8[:][0] // &v8::Alloc
v8ptr := &v8 // v8ptr::Alloc &v8::Alloc
_ = v8ptr[0] // v8ptr::Alloc
_ = *v8ptr // v8ptr::Alloc
v9 := S{} // &v9::Alloc
v10 := &v9 // v10::Alloc &v9::Alloc
var v11 *J = nil // v11::Literal
v11.method() // v11::Literal
var v12 J // v12::UnOp (load from Alloc)
v12.method() // &v12::Alloc (implicitly address-taken)
// These vars are optimised away.
if false {
v13 := 0 // v13::nil
println(v13) // v13::nil
}
switch x := 1; x { // x::Literal
case v0: // v0::Phi
}
for k, v := range m { // k::Extract v::Extract m::MakeMap
v++ // v::BinOp
}
if y := 0; y > 1 { // y::Literal y::Literal
}
var i interface{} // i::Literal (nil interface)
i = 1 // i::MakeInterface
switch i := i.(type) { // i::MakeInterface i::MakeInterface
case int:
println(i) // i::Extract
}
ch := make(chan int) // ch::MakeChan
select {
case x := <-ch: // x::UnOp (receive) ch::MakeChan
}
}