diff --git a/ssa/builder.go b/ssa/builder.go index 1806ada7..6eea7e69 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -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)) } } diff --git a/ssa/create.go b/ssa/create.go index 37615cdf..3e9a7af4 100644 --- a/ssa/create.go +++ b/ssa/create.go @@ -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) diff --git a/ssa/emit.go b/ssa/emit.go index c34b8806..530351f5 100644 --- a/ssa/emit.go +++ b/ssa/emit.go @@ -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 diff --git a/ssa/func.go b/ssa/func.go index 9de5fa45..8a692fb7 100644 --- a/ssa/func.go +++ b/ssa/func.go @@ -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. diff --git a/ssa/interp/interp.go b/ssa/interp/interp.go index ee68c97c..ac83e673 100644 --- a/ssa/interp/interp.go +++ b/ssa/interp/interp.go @@ -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)) diff --git a/ssa/lvalue.go b/ssa/lvalue.go index 33b09bb7..16a44370 100644 --- a/ssa/lvalue.go +++ b/ssa/lvalue.go @@ -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 diff --git a/ssa/print.go b/ssa/print.go index c6429dff..798e9a75 100644 --- a/ssa/print.go +++ b/ssa/print.go @@ -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() } diff --git a/ssa/sanity.go b/ssa/sanity.go index 3211d8cf..0d326e2c 100644 --- a/ssa/sanity.go +++ b/ssa/sanity.go @@ -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)) diff --git a/ssa/source.go b/ssa/source.go index 9edb9acc..ed3f1dd4 100644 --- a/ssa/source.go +++ b/ssa/source.go @@ -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 +} diff --git a/ssa/source_test.go b/ssa/source_test.go index dd1ef7cd..0abc62a6 100644 --- a/ssa/source_test.go +++ b/ssa/source_test.go @@ -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) + } + } +} diff --git a/ssa/ssa.go b/ssa/ssa.go index a9674a0c..bdf57b93 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -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) } diff --git a/ssa/ssadump.go b/ssa/ssadump.go index 64b14b2b..0941dd57 100644 --- a/ssa/ssadump.go +++ b/ssa/ssadump.go @@ -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': diff --git a/ssa/testdata/objlookup.go b/ssa/testdata/objlookup.go new file mode 100644 index 00000000..86eec80c --- /dev/null +++ b/ssa/testdata/objlookup.go @@ -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 + } +}