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. // emits initialization code into from.init if not already done.
// //
func (b *builder) lookup(from *Package, obj types.Object) Value { 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) { switch v := v.(type) {
case *Function: case *Function:
if from == v.Pkg { 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), // for !wantAddr, when safe (i.e. e.X is addressible),
// since (FieldAddr;Load) is cheaper than (Load;Field). // since (FieldAddr;Load) is cheaper than (Load;Field).
// Requires go/types to expose addressibility. // 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 { } else {
v = b.expr(fn, e.X) 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 { func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
switch e := e.(type) { switch e := e.(type) {
case *ast.Ident: case *ast.Ident:
if isBlankIdent(e) {
return blank{}
}
obj := fn.Pkg.objectOf(e) obj := fn.Pkg.objectOf(e)
v := b.lookup(fn.Pkg, obj) // var (address) v := b.lookup(fn.Pkg, obj) // var (address)
if v == nil { if v == nil {
v = fn.lookup(obj, escaping) v = fn.lookup(obj, escaping)
} }
return address{addr: v} return address{addr: v, id: e, object: obj}
case *ast.CompositeLit: case *ast.CompositeLit:
t := deref(fn.Pkg.typeOf(e)) 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 var et types.Type
switch t := fn.Pkg.typeOf(e.X).Underlying().(type) { switch t := fn.Pkg.typeOf(e.X).Underlying().(type) {
case *types.Array: 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()) et = pointer(t.Elem())
case *types.Pointer: // *array case *types.Pointer: // *array
x = b.expr(fn, e.X) 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)} return address{addr: fn.emit(v)}
case *ast.StarExpr: 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)) 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. // in an addressable location.
// //
func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) { 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 { if e, ok := e.(*ast.CompositeLit); ok {
typ := addr.typ() typ := loc.typ()
switch typ.Underlying().(type) { switch typ.Underlying().(type) {
case *types.Pointer: // implicit & -- possibly escaping case *types.Pointer: // implicit & -- possibly escaping
ptr := b.addr(fn, e, true).(address).addr ptr := b.addr(fn, e, true).address(fn)
addr.store(fn, ptr) // copy address loc.store(fn, ptr) // copy address
return return
case *types.Interface: case *types.Interface:
@ -531,7 +534,7 @@ func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) {
// Fall back to copying. // Fall back to copying.
default: default:
b.compLit(fn, addr.addr, e, typ) // in place b.compLit(fn, loc.address(fn), e, typ) // in place
return 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 { func (b *builder) expr(fn *Function, e ast.Expr) Value {
if v := fn.Pkg.info.ValueOf(e); v != nil { 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) { switch e := e.(type) {
@ -561,9 +573,8 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
Pkg: fn.Pkg, Pkg: fn.Pkg,
Prog: fn.Prog, Prog: fn.Prog,
syntax: &funcSyntax{ syntax: &funcSyntax{
paramFields: e.Type.Params, functype: e.Type,
resultFields: e.Type.Results, body: e.Body,
body: e.Body,
}, },
} }
fn.AnonFuncs = append(fn.AnonFuncs, fn2) fn.AnonFuncs = append(fn.AnonFuncs, fn2)
@ -621,7 +632,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
case *ast.UnaryExpr: case *ast.UnaryExpr:
switch e.Op { switch e.Op {
case token.AND: // &X --- potentially escaping. 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: case token.ADD:
return b.expr(fn, e.X) return b.expr(fn, e.X)
case token.NOT, token.ARROW, token.SUB, token.XOR: // ! <- - ^ 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) { switch fn.Pkg.typeOf(e.X).Underlying().(type) {
case *types.Array: case *types.Array:
// Potentially escaping. // 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 case *types.Basic, *types.Slice, *types.Pointer: // *array
x = b.expr(fn, e.X) x = b.expr(fn, e.X)
default: default:
@ -691,8 +702,10 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
} }
return v // (func) return v // (func)
} }
// Local? // Local var.
return emitLoad(fn, fn.lookup(obj, false)) // var (address) v := emitLoad(fn, fn.lookup(obj, false)) // var (address)
emitDebugRef(fn, e, v)
return v
case *ast.SelectorExpr: case *ast.SelectorExpr:
// p.M where p is a package. // 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 // pointer formal receiver; but the actual
// value is not a pointer. // value is not a pointer.
// Implicit & -- possibly escaping. // Implicit & -- possibly escaping.
return m, b.addr(fn, base, true).(address).addr return m, b.addr(fn, base, true).address(fn)
} }
} }
return nil, nil 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) sel, ok := unparen(e.Fun).(*ast.SelectorExpr)
// Case 0: e.Fun evaluates normally to a function. // 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) c.Func = b.expr(fn, e.Fun)
return return
} }
// Case 1: call of form x.F() where x is a package name. // Case 1: X.f() or (*X).f(): a statically dipatched call to
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
// the method f in the method-set of X or *X. X may be // the method f in the method-set of X or *X. X may be
// an interface. Treat like case 0. // an interface. Treat like case 0.
// TODO(adonovan): opt: inline expr() here, to make the call static // 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 // buildGlobal emits code to the g.Pkg.init function for the variable
// definition(s) of g. Effects occur out of lexical order; see // definition(s) of g. Effects occur out of lexical order; see
// explanation at globalValueSpec. // explanation at globalValueSpec.
// Precondition: g == g.Prog.Value(obj) // Precondition: g == g.Prog.value(obj)
// //
func (b *builder) buildGlobal(g *Global, obj types.Object) { func (b *builder) buildGlobal(g *Global, obj types.Object) {
spec := g.spec 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. // 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. // 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. // Package-level var initialization order is quite subtle.
// The side effects of: // The side effects of:
@ -1061,7 +1061,7 @@ func (b *builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global
} else { } else {
// Mode B: initialize all globals. // Mode B: initialize all globals.
if !isBlankIdent(id) { 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 { if g2.spec == nil {
continue // already done continue // already done
} }
@ -1095,7 +1095,7 @@ func (b *builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global
result := tuple.Type().(*types.Tuple) result := tuple.Type().(*types.Tuple)
for i, id := range spec.Names { for i, id := range spec.Names {
if !isBlankIdent(id) { 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 g.spec = nil // just an optimization
emitStore(init, g, emitExtract(init, tuple, i, result.At(i).Type())) 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 // e.g. var x, y = 0, 1
// 1:1 assignment // 1:1 assignment
for i, id := range spec.Names { for i, id := range spec.Names {
var lval lvalue = blank{}
if !isBlankIdent(id) { 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]) 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. // Locals are implicitly zero-initialized.
for _, id := range spec.Names { for _, id := range spec.Names {
if !isBlankIdent(id) { 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) result := tuple.Type().(*types.Tuple)
for i, id := range spec.Names { for i, id := range spec.Names {
if !isBlankIdent(id) { if !isBlankIdent(id) {
lhs := fn.addLocalForIdent(id) fn.addLocalForIdent(id)
emitStore(fn, lhs, emitExtract(fn, tuple, i, result.At(i).Type())) 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) x = b.expr(fn, unparen(ass.X).(*ast.TypeAssertExpr).X)
case *ast.AssignStmt: // y := x.(type) case *ast.AssignStmt: // y := x.(type)
x = b.expr(fn, unparen(ass.Rhs[0]).(*ast.TypeAssertExpr).X) 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") done := fn.newBasicBlock("typeswitch.done")
@ -1999,9 +2008,11 @@ start:
case *ast.DeclStmt: // Con, Var or Typ case *ast.DeclStmt: // Con, Var or Typ
d := s.Decl.(*ast.GenDecl) d := s.Decl.(*ast.GenDecl)
for _, spec := range d.Specs { if d.Tok == token.VAR {
if vs, ok := spec.(*ast.ValueSpec); ok { for _, spec := range d.Specs {
b.localValueSpec(fn, vs) 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 { } else {
// Package-level function. // 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 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
@ -88,10 +89,12 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
pkg.Members[name] = &Type{object: obj} pkg.Members[name] = &Type{object: obj}
case *types.Const: case *types.Const:
pkg.Members[name] = &Constant{ c := &Constant{
object: obj, object: obj,
Value: NewLiteral(obj.Val(), obj.Type(), obj.Pos()), Value: NewLiteral(obj.Val(), obj.Type(), obj.Pos()),
} }
pkg.values[obj] = c.Value
pkg.Members[name] = c
case *types.Var: case *types.Var:
spec, _ := syntax.(*ast.ValueSpec) 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 { if decl, ok := syntax.(*ast.FuncDecl); ok {
synthetic = "" synthetic = ""
fs = &funcSyntax{ fs = &funcSyntax{
recvField: decl.Recv, functype: decl.Type,
paramFields: decl.Type.Params, recvField: decl.Recv,
resultFields: decl.Type.Results, body: decl.Body,
body: decl.Body,
} }
} }
sig := obj.Type().(*types.Signature) sig := obj.Type().(*types.Signature)

View File

@ -3,6 +3,7 @@ package ssa
// Helpers for emitting SSA instructions. // Helpers for emitting SSA instructions.
import ( import (
"go/ast"
"go/token" "go/token"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
@ -29,6 +30,27 @@ func emitLoad(f *Function, addr Value) *UnOp {
return v 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) // emitArith emits to f code to compute the binary operation op(x, y)
// where op is an eager shift, logical or arithmetic operation. // where op is an eager shift, logical or arithmetic operation.
// (Use emitCompare() for comparisons and Builder.logicalBinop() for // (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. // funcSyntax holds the syntax tree for the function declaration and body.
type funcSyntax struct { type funcSyntax struct {
recvField *ast.FieldList recvField *ast.FieldList
paramFields *ast.FieldList body *ast.BlockStmt
resultFields *ast.FieldList functype *ast.FuncType
body *ast.BlockStmt
} }
// labelledBlock returns the branch target associated with the // labelledBlock returns the branch target associated with the
@ -185,7 +184,9 @@ func (f *Function) addParamObj(obj types.Object) *Parameter {
if name == "" { if name == "" {
name = fmt.Sprintf("arg%d", len(f.Params)) 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 // addSpilledParam declares a parameter that is pre-spilled to the
@ -238,9 +239,9 @@ func (f *Function) createSyntacticParams() {
} }
// Parameters. // Parameters.
if f.syntax.paramFields != nil { if f.syntax.functype.Params != nil {
n := len(f.Params) // 1 if has recv, 0 otherwise 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 { for _, n := range field.Names {
f.addSpilledParam(f.Pkg.objectOf(n)) f.addSpilledParam(f.Pkg.objectOf(n))
} }
@ -252,8 +253,8 @@ func (f *Function) createSyntacticParams() {
} }
// Named results. // Named results.
if f.syntax.resultFields != nil { if f.syntax.functype.Results != nil {
for _, field := range f.syntax.resultFields.List { for _, field := range f.syntax.functype.Results.List {
// Implicit "var" decl of locals for named results. // Implicit "var" decl of locals for named results.
for _, n := range field.Names { for _, n := range field.Names {
f.namedResults = append(f.namedResults, f.addLocalForIdent(n)) f.namedResults = append(f.namedResults, f.addLocalForIdent(n))
@ -373,6 +374,12 @@ func (f *Function) removeNilBlocks() {
f.Blocks = f.Blocks[:j] 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 // addNamedLocal creates a local variable, adds it to function f and
// returns it. Its name and type are taken from obj. Subsequent // returns it. Its name and type are taken from obj. Subsequent
// calls to f.lookup(obj) will return the same local. // 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. // read the next instruction from.
func visitInstr(fr *frame, instr ssa.Instruction) continuation { func visitInstr(fr *frame, instr ssa.Instruction) continuation {
switch instr := instr.(type) { switch instr := instr.(type) {
case *ssa.DebugRef:
// no-op
case *ssa.UnOp: case *ssa.UnOp:
fr.env[instr] = unop(instr, fr.get(instr.X)) fr.env[instr] = unop(instr, fr.get(instr.X))

View File

@ -4,8 +4,10 @@ package ssa
// expressions. // expressions.
import ( import (
"code.google.com/p/go.tools/go/types" "go/ast"
"go/token" "go/token"
"code.google.com/p/go.tools/go/types"
) )
// An lvalue represents an assignable location that may appear on the // An lvalue represents an assignable location that may appear on the
@ -15,23 +17,39 @@ import (
type lvalue interface { type lvalue interface {
store(fn *Function, v Value) // stores v into the location store(fn *Function, v Value) // stores v into the location
load(fn *Function) Value // loads the contents of 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 typ() types.Type // returns the type of the location
} }
// An address is an lvalue represented by a true pointer. // An address is an lvalue represented by a true pointer.
type address struct { type address struct {
addr Value addr Value
star token.Pos // source position, if explicit *addr 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 { func (a address) load(fn *Function) Value {
load := emitLoad(fn, a.addr) load := emitLoad(fn, a.addr)
load.pos = a.star load.pos = a.starPos
return load return load
} }
func (a address) store(fn *Function, v Value) { 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 { 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 { func (e *element) typ() types.Type {
return e.t return e.t
} }
@ -82,6 +104,10 @@ func (bl blank) store(fn *Function, v Value) {
// no-op // no-op
} }
func (bl blank) address(fn *Function) Value {
panic("blank var is not addressable")
}
func (bl blank) typ() types.Type { func (bl blank) typ() types.Type {
// This should be the type of the blank Ident; the typechecker // This should be the type of the blank Ident; the typechecker
// doesn't provide this yet, but fortunately, we don't need it // 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)) 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 { func (p *Package) String() string {
return "package " + p.Object.Path() return "package " + p.Object.Path()
} }

View File

@ -155,6 +155,7 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
case *Store: case *Store:
case *TypeAssert: case *TypeAssert:
case *UnOp: case *UnOp:
case *DebugRef:
// TODO(adonovan): implement checks. // TODO(adonovan): implement checks.
default: default:
panic(fmt.Sprintf("Unknown instruction type: %T", instr)) 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. // 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 ( import (
"code.google.com/p/go.tools/importer"
"go/ast" "go/ast"
"go/token" "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) // 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) { 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 importPath, info := range imp.Packages {
for _, f := range info.Files { for _, f := range info.Files {
if !tokenFileContainsPos(prog.Fset.File(f.Package), start) { if !tokenFileContainsPos(imp.Fset.File(f.Package), start) {
continue continue
} }
if path, exact := PathEnclosingInterval(f, start, end); path != nil { 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 prog.PackagesByPath[importPath], path, exact
} }
} }
@ -227,3 +229,141 @@ func CanonicalPos(n ast.Node) token.Pos {
return token.NoPos 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. // This file defines tests of the source and source_ast utilities.
// TODO(adonovan): exhaustive tests that run over the whole input // TODO(adonovan): exhaustive tests that run over the whole input
// tree, not just andcrafted examples. // tree, not just handcrafted examples.
import ( import (
"bytes" "bytes"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/ssa"
"fmt" "fmt"
"go/ast" "go/ast"
"go/parser" "go/parser"
"go/token" "go/token"
"regexp"
"strings" "strings"
"testing" "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 ------------------------------------- // -------- 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 { type Parameter struct {
name string name string
object types.Object // a *types.Var; nil for non-source locals
typ types.Type typ types.Type
pos token.Pos pos token.Pos
parent *Function parent *Function
@ -412,13 +413,14 @@ type Global struct {
// A Builtin represents a built-in function, e.g. len. // A Builtin represents a built-in function, e.g. len.
// //
// Builtins are immutable values. Builtins do not have addresses. // Builtins are immutable values. Builtins do not have addresses.
// Builtins can only appear in CallCommon.Func.
// //
// Type() returns a *types.Builtin. // Type() returns a *types.Builtin.
// Built-in functions may have polymorphic or variadic types that are // Built-in functions may have polymorphic or variadic types that are
// not expressible in Go's type system. // not expressible in Go's type system.
// //
type Builtin struct { 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 ---------------------------------------- // Value-defining instructions ----------------------------------------
@ -1136,6 +1138,28 @@ type MapUpdate struct {
pos token.Pos 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. ----------- // Embeddable mix-ins and helpers for common parts of other structs. -----------
// Register is a mix-in embedded by all SSA values that are also // 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 *Defer) Value() *Call { return nil }
func (s *Go) Value() *Call { return nil } func (s *Go) Value() *Call { return nil }
func (v *Builtin) Type() types.Type { return v.Object.Type() } func (v *Builtin) Type() types.Type { return v.object.Type() }
func (v *Builtin) Name() string { return v.Object.Name() } func (v *Builtin) Name() string { return v.object.Name() }
func (*Builtin) Referrers() *[]Instruction { return nil } func (*Builtin) Referrers() *[]Instruction { return nil }
func (v *Builtin) Pos() token.Pos { return token.NoPos } 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) Type() types.Type { return v.typ }
func (v *Capture) Name() string { return v.name } 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) Type() types.Type { return v.typ }
func (v *Parameter) Name() string { return v.name } 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) Referrers() *[]Instruction { return &v.referrers }
func (v *Parameter) Pos() token.Pos { return v.pos } func (v *Parameter) Pos() token.Pos { return v.pos }
func (v *Parameter) Parent() *Function { return v.parent } func (v *Parameter) Parent() *Function { return v.parent }
@ -1401,29 +1427,6 @@ func (p *Package) Type(name string) (t *Type) {
return 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 (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.Call.pos }
func (s *Go) 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 *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 }
// Operands. // Operands.
@ -1478,6 +1482,10 @@ func (v *Convert) Operands(rands []*Value) []*Value {
return append(rands, &v.X) return append(rands, &v.X)
} }
func (s *DebugRef) Operands(rands []*Value) []*Value {
return append(rands, &s.X)
}
func (v *Extract) Operands(rands []*Value) []*Value { func (v *Extract) Operands(rands []*Value) []*Value {
return append(rands, &v.Tuple) return append(rands, &v.Tuple)
} }

View File

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