Commit c06a5335 authored by Alan Donovan's avatar Alan Donovan

exp/ssa: (#4 of 5): the SSA builder.

R=iant, gri, iant, rogpeppe
CC=golang-dev
https://golang.org/cl/7196053
parent 399dcc75
package ssa
// This file defines the SSA builder.
//
// The builder has two phases, CREATE and BUILD. In the CREATE
// phase, all packages are constructed and type-checked and
// definitions of all package members are created, method-sets are
// computed, and bridge methods are synthesized. The create phase
// proceeds in topological order over the import dependency graph,
// initiated by client calls to CreatePackage.
//
// In the BUILD phase, the Builder traverses the AST of each Go source
// function and generates SSA instructions for the function body.
// Within each package, building proceeds in a topological order over
// the symbol reference graph, whose roots are the set of
// package-level declarations in lexical order.
//
// In principle, the BUILD phases for each package can occur in
// parallel, and that is our goal though there remains work to do.
// Currently we ensure that all the imports of a package are fully
// built before we start building it.
//
// The Builder's and Program's indices (maps) are populated and
// mutated during the CREATE phase, but during the BUILD phase they
// remain constant, with the following exceptions:
// - demoteSelector mutates Builder.types during the BUILD phase.
// TODO(adonovan): fix: let's not do that.
// - globalValueSpec mutates Builder.nTo1Vars.
// TODO(adonovan): make this a per-Package map so it's thread-safe.
// - Program.methodSets is populated lazily across phases.
// It uses a mutex so that access from multiple threads is serialized.
// TODO(adonovan): fix the following:
// - append, delete details.
// - support f(g()) where g has multiple result parameters.
// - finish emitCompare, emitArith.
// - banish "untyped" types everywhere except package/universal constants?
// - concurrent SSA code generation of multiple packages.
// - consider function-local NamedTypes.
// They can have nonempty method-sets due to promotion. Test.
// - polish.
// - tests.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"os"
"strconv"
)
var (
varOk = &types.Var{Name: "ok", Type: tBool}
// Type constants.
tBool = types.Typ[types.Bool]
tByte = types.Typ[types.Byte]
tFloat32 = types.Typ[types.Float32]
tFloat64 = types.Typ[types.Float64]
tInt = types.Typ[types.Int]
tInvalid = types.Typ[types.Invalid]
tUntypedNil = types.Typ[types.UntypedNil]
tRangeIter = &types.Basic{Name: "iter"} // the type of all "range" iterators
// The result type of a "select".
tSelect = &types.Result{Values: []*types.Var{
{Name: "index", Type: tInt},
{Name: "recv", Type: tInvalid},
varOk,
}}
// SSA Value constants.
vZero = intLiteral(0)
vOne = intLiteral(1)
vTrue = newLiteral(true, tBool)
vFalse = newLiteral(false, tBool)
)
// A Builder creates the SSA representation of a single program.
// Instances may be created using NewBuilder.
//
// The SSA Builder constructs a Program containing Package instances
// for packages of Go source code, loading, parsing and recursively
// constructing packages for all imported dependencies as well.
//
// If the UseGCImporter mode flag is specified, binary object files
// produced by the gc compiler will be loaded instead of source code
// for all imported packages. Such files supply only the types of
// package-level declarations and values of constants, but no code, so
// this mode will not yield a whole program. It is intended for
// analyses that perform intraprocedural analysis of a single package.
//
// A typical client will create a Builder with NewBuilder; call
// CreatePackage for the "root" package(s), e.g. main; then call
// BuildPackage on the same set of packages to construct SSA-form code
// for functions and methods. After that, the representation of the
// program (Builder.Prog) is complete and transitively closed, and the
// Builder object can be discarded to reclaim its memory. The
// client's analysis may then begin.
//
type Builder struct {
Prog *Program // the program being built
mode BuilderMode // set of mode bits
loader SourceLoader // the loader for imported source files
importErrs map[string]error // across-packages import cache of failures
packages map[*types.Package]*Package // SSA packages by types.Package
types map[ast.Expr]types.Type // inferred types of expressions
constants map[ast.Expr]*Literal // values of constant expressions
idents map[*ast.Ident]types.Object // canonical type objects of all named entities
globals map[types.Object]Value // all package-level funcs and vars, and universal built-ins
nTo1Vars map[*ast.ValueSpec]bool // set of n:1 ValueSpecs already built [not threadsafe]
typechecker types.Context // the typechecker context (stateless)
}
// BuilderMode is a bitmask of options for diagnostics and checking.
type BuilderMode uint
const (
LogPackages BuilderMode = 1 << iota // Dump package inventory to stderr
LogFunctions // Dump function SSA code to stderr
LogSource // Show source locations as SSA builder progresses
SanityCheckFunctions // Perform sanity checking of function bodies
UseGCImporter // Ignore SourceLoader; use gc-compiled object code for all imports
)
// NewBuilder creates and returns a new SSA builder.
//
// mode is a bitfield of options controlling verbosity, logging and
// additional sanity checks.
//
// loader is a SourceLoader function that finds, loads and parses Go
// source files for a given import path. (It is ignored if the mode
// bits include UseGCImporter.)
//
// errh is an optional error handler that is called for each error
// encountered during type checking; if nil, only the first type error
// will be returned, via the result of CreatePackage.
//
func NewBuilder(mode BuilderMode, loader SourceLoader, errh func(error)) *Builder {
b := &Builder{
Prog: &Program{
Files: token.NewFileSet(),
Packages: make(map[string]*Package),
Builtins: make(map[types.Object]*Builtin),
methodSets: make(map[types.Type]MethodSet),
concreteMethods: make(map[*types.Method]*Function),
mode: mode,
},
mode: mode,
loader: loader,
constants: make(map[ast.Expr]*Literal),
globals: make(map[types.Object]Value),
idents: make(map[*ast.Ident]types.Object),
importErrs: make(map[string]error),
nTo1Vars: make(map[*ast.ValueSpec]bool),
packages: make(map[*types.Package]*Package),
types: make(map[ast.Expr]types.Type),
}
// TODO(adonovan): opt: record the Expr/Ident calls into
// constants/idents/types maps associated with the containing
// package so we can discard them once that package is built.
b.typechecker = types.Context{
// TODO(adonovan): permit the client to specify these
// values. Perhaps expose the types.Context parameter
// directly (though of course we'll have to override
// the Expr/Ident/Import callbacks).
IntSize: 8,
PtrSize: 8,
Error: errh,
Expr: func(x ast.Expr, typ types.Type, val interface{}) {
b.types[x] = typ
if val != nil {
b.constants[x] = newLiteral(val, typ)
}
},
Ident: func(ident *ast.Ident, obj types.Object) {
// Invariants:
// - obj is non-nil.
// - isBlankIdent(ident) <=> obj.GetType()==nil
b.idents[ident] = obj
},
Import: func(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
return b.doImport(imports, path)
},
}
// Create Values for built-in functions.
for _, obj := range types.Universe.Entries {
switch obj := obj.(type) {
case *types.Func:
v := &Builtin{obj}
b.globals[obj] = v
b.Prog.Builtins[obj] = v
}
}
return b
}
// isPackageRef returns the identity of the object if sel is a
// package-qualified reference to a named const, var, func or type.
// Otherwise it returns nil.
//
// It's unfortunate that this is a method of builder, but even the
// small amount of name resolution required here is no longer
// available on the AST.
//
func (b *Builder) isPackageRef(sel *ast.SelectorExpr) types.Object {
if id, ok := sel.X.(*ast.Ident); ok {
if obj := b.obj(id); objKind(obj) == ast.Pkg {
return obj.(*types.Package).Scope.Lookup(sel.Sel.Name)
}
}
return nil
}
// isType returns true iff expression e denotes a type.
//
// It's unfortunate that this is a method of builder, but even the
// small amount of name resolution required here is no longer
// available on the AST.
//
func (b *Builder) isType(e ast.Expr) bool {
switch e := e.(type) {
case *ast.SelectorExpr: // pkg.Type
if obj := b.isPackageRef(e); obj != nil {
return objKind(obj) == ast.Typ
}
case *ast.StarExpr: // *T
return b.isType(e.X)
case *ast.Ident:
return objKind(b.obj(e)) == ast.Typ
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
return true
case *ast.ParenExpr:
return b.isType(e.X)
}
return false
}
// lookup returns the package-level *Function or *Global (or universal
// *Builtin) for the named object obj, causing its initialization code
// to be emitted into v.Package.Init if not already done.
//
func (b *Builder) lookup(obj types.Object) (v Value, ok bool) {
v, ok = b.globals[obj]
if ok {
// TODO(adonovan): the build phase should only
// propagate to v if it's in the same package as the
// caller of lookup if we want to make this
// concurrent.
switch v := v.(type) {
case *Function:
b.buildFunction(v)
case *Global:
b.buildGlobal(v, obj)
}
}
return
}
// exprType(e) returns the type of expression e.
// Callers should not access b.types directly since it may lie if
// called from within a typeswitch.
//
func (b *Builder) exprType(e ast.Expr) types.Type {
// For Ident, b.types may be more specific than
// b.obj(id.(*ast.Ident)).GetType(),
// e.g. in the case of typeswitch.
if t, ok := b.types[e]; ok {
return t
}
// The typechecker doesn't notify us of all Idents,
// e.g. s.Key and s.Value in a RangeStmt.
// So we have this fallback.
// TODO(gri): Is this a typechecker bug? If so, eliminate
// this case and panic.
if id, ok := e.(*ast.Ident); ok {
return b.obj(id).GetType()
}
panic("no type for expression")
}
// obj returns the typechecker object denoted by the specified
// identifier. Panic ensues if there is none.
//
func (b *Builder) obj(id *ast.Ident) types.Object {
if obj, ok := b.idents[id]; ok {
return obj
}
panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %p", id.Name, id))
}
// cond emits to fn code to evaluate boolean condition e and jump
// to t or f depending on its value, performing various simplifications.
//
// Postcondition: fn.currentBlock is nil.
//
func (b *Builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) {
switch e := e.(type) {
case *ast.ParenExpr:
b.cond(fn, e.X, t, f)
return
case *ast.BinaryExpr:
switch e.Op {
case token.LAND:
ltrue := fn.newBasicBlock("cond.true")
b.cond(fn, e.X, ltrue, f)
fn.currentBlock = ltrue
b.cond(fn, e.Y, t, f)
return
case token.LOR:
lfalse := fn.newBasicBlock("cond.false")
b.cond(fn, e.X, t, lfalse)
fn.currentBlock = lfalse
b.cond(fn, e.Y, t, f)
return
}
case *ast.UnaryExpr:
if e.Op == token.NOT {
b.cond(fn, e.X, f, t)
return
}
}
switch cond := b.expr(fn, e).(type) {
case *Literal:
// Dispatch constant conditions statically.
if cond.Value.(bool) {
emitJump(fn, t)
} else {
emitJump(fn, f)
}
default:
emitIf(fn, cond, t, f)
}
}
// logicalBinop emits code to fn to evaluate e, a &&- or
// ||-expression whose reified boolean value is wanted.
// The value is returned.
//
func (b *Builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value {
rhs := fn.newBasicBlock("binop.rhs")
done := fn.newBasicBlock("binop.done")
var short Value // value of the short-circuit path
switch e.Op {
case token.LAND:
b.cond(fn, e.X, rhs, done)
short = vFalse
case token.LOR:
b.cond(fn, e.X, done, rhs)
short = vTrue
}
// Is rhs unreachable?
if rhs.Preds == nil {
// Simplify false&&y to false, true||y to true.
fn.currentBlock = done
return short
}
// Is done unreachable?
if done.Preds == nil {
// Simplify true&&y (or false||y) to y.
fn.currentBlock = rhs
return b.expr(fn, e.Y)
}
// All edges from e.X to done carry the short-circuit value.
var edges []Value
for _ = range done.Preds {
edges = append(edges, short)
}
// The edge from e.Y to done carries the value of e.Y.
fn.currentBlock = rhs
edges = append(edges, b.expr(fn, e.Y))
emitJump(fn, done)
fn.currentBlock = done
// TODO(adonovan): do we need emitConv on each edge?
// Test with named boolean types.
phi := &Phi{Edges: edges}
phi.Type_ = phi.Edges[0].Type()
return done.emit(phi)
}
// exprN lowers a multi-result expression e to SSA form, emitting code
// to fn and returning a single Value whose type is a *types.Results
// (tuple). The caller must access the components via Extract.
//
// Multi-result expressions include CallExprs in a multi-value
// assignment or return statement, and "value,ok" uses of
// TypeAssertExpr, IndexExpr (when X is a map), and UnaryExpr (when Op
// is token.ARROW).
//
func (b *Builder) exprN(fn *Function, e ast.Expr) Value {
var typ types.Type
var tuple Value
switch e := e.(type) {
case *ast.ParenExpr:
return b.exprN(fn, e.X)
case *ast.CallExpr:
// Currently, no built-in function nor type conversion
// has multiple results, so we can avoid some of the
// cases for single-valued CallExpr.
var c Call
b.setCall(fn, e, &c.CallCommon)
c.Type_ = b.exprType(e)
return fn.emit(&c)
case *ast.IndexExpr:
mapt := underlyingType(b.exprType(e.X)).(*types.Map)
typ = mapt.Elt
tuple = fn.emit(&Lookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key),
CommaOk: true,
})
case *ast.TypeAssertExpr:
typ = b.exprType(e)
tuple = fn.emit(&TypeAssert{
X: b.expr(fn, e.X),
AssertedType: typ,
CommaOk: true,
})
case *ast.UnaryExpr: // must be receive <-
typ = underlyingType(b.exprType(e.X)).(*types.Chan).Elt
tuple = fn.emit(&UnOp{
Op: token.ARROW,
X: b.expr(fn, e.X),
CommaOk: true,
})
default:
panic(fmt.Sprintf("unexpected exprN: %T", e))
}
// The typechecker sets the type of the expression to just the
// asserted type in the "value, ok" form, not to *types.Result
// (though it includes the valueOk operand in its error messages).
tuple.(interface {
setType(types.Type)
}).setType(&types.Result{Values: []*types.Var{
{Name: "value", Type: typ},
varOk,
}})
return tuple
}
// builtin emits to fn SSA instructions to implement a call to the
// built-in function called name with the specified arguments
// and return type. It returns the value defined by the result.
//
// The result is nil if no special handling was required; in this case
// the caller should treat this like an ordinary library function
// call.
//
func (b *Builder) builtin(fn *Function, name string, args []ast.Expr, typ types.Type) Value {
switch name {
case "make":
switch underlyingType(typ).(type) {
case *types.Slice:
n := b.expr(fn, args[1])
m := n
if len(args) == 3 {
m = b.expr(fn, args[2])
}
v := &MakeSlice{
Len: n,
Cap: m,
}
v.setType(typ)
return fn.emit(v)
case *types.Map:
var res Value
if len(args) == 2 {
res = b.expr(fn, args[1])
}
v := &MakeMap{Reserve: res}
v.setType(typ)
return fn.emit(v)
case *types.Chan:
var sz Value = vZero
if len(args) == 2 {
sz = b.expr(fn, args[1])
}
v := &MakeChan{Size: sz}
v.setType(typ)
return fn.emit(v)
}
case "new":
return emitNew(fn, indirectType(underlyingType(typ)))
case "len", "cap":
// Special case: len or cap of an array or *array is
// based on the type, not the value which may be nil.
// We must still evaluate the value, though. (If it
// was side-effect free, the whole call would have
// been constant-folded.)
t := underlyingType(deref(b.exprType(args[0])))
if at, ok := t.(*types.Array); ok {
b.expr(fn, args[0]) // for effects only
return intLiteral(at.Len)
}
// Otherwise treat as normal.
}
return nil // treat all others as a regular function call
}
// demoteSelector returns a SelectorExpr syntax tree that is
// equivalent to sel but contains no selections of promoted fields.
// It returns the field index of the explicit (=outermost) selection.
//
// pkg is the package in which the reference occurs. This is
// significant because a non-exported field is considered distinct
// from a field of that name in any other package.
//
// This is a rather clunky and inefficient implementation, but it
// (a) is simple and hopefully self-evidently correct and
// (b) permits us to decouple the demotion from the code generation,
// the latter being performed in two modes: addr() for lvalues,
// expr() for rvalues.
// It does require mutation of Builder.types though; if we want to
// make the Builder concurrent we'll have to avoid that.
// TODO(adonovan): emit code directly rather than desugaring the AST.
//
func (b *Builder) demoteSelector(sel *ast.SelectorExpr, pkg *Package) (sel2 *ast.SelectorExpr, index int) {
id := makeId(sel.Sel.Name, pkg.Types)
xtype := b.exprType(sel.X)
// fmt.Fprintln(os.Stderr, xtype, id) // debugging
st := underlyingType(deref(xtype)).(*types.Struct)
for i, f := range st.Fields {
if IdFromQualifiedName(f.QualifiedName) == id {
return sel, i
}
}
// Not a named field. Use breadth-first algorithm.
path, index := findPromotedField(st, id)
if path == nil {
panic("field not found, even with promotion: " + sel.Sel.Name)
}
// makeSelector(e, [C,B,A]) returns (((e.A).B).C).
// e is the original selector's base.
// This function has no free variables.
var makeSelector func(b *Builder, e ast.Expr, path *anonFieldPath) *ast.SelectorExpr
makeSelector = func(b *Builder, e ast.Expr, path *anonFieldPath) *ast.SelectorExpr {
x := e
if path.tail != nil {
x = makeSelector(b, e, path.tail)
}
sel := &ast.SelectorExpr{
X: x,
Sel: &ast.Ident{Name: path.field.Name},
}
b.types[sel] = path.field.Type // TODO(adonovan): fix: not thread-safe
return sel
}
// Construct new SelectorExpr, bottom up.
sel2 = &ast.SelectorExpr{
X: makeSelector(b, sel.X, path),
Sel: sel.Sel,
}
b.types[sel2] = b.exprType(sel) // TODO(adonovan): fix: not thread-safe
return
}
// addr lowers a single-result addressable expression e to SSA form,
// emitting code to fn and returning the location (an lvalue) defined
// by the expression.
//
// If escaping is true, addr marks the base variable of the
// addressable expression e as being a potentially escaping pointer
// value. For example, in this code:
//
// a := A{
// b: [1]B{B{c: 1}}
// }
// return &a.b[0].c
//
// the application of & causes a.b[0].c to have its address taken,
// which means that ultimately the local variable a must be
// heap-allocated. This is a simple but very conservative escape
// analysis.
//
// Operations forming potentially escaping pointers include:
// - &x
// - a[:] iff a is an array (not *array)
// - references to variables in lexically enclosing functions.
//
func (b *Builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
switch e := e.(type) {
case *ast.Ident:
obj := b.obj(e)
v, ok := b.lookup(obj) // var (address)
if !ok {
v = fn.lookup(obj, escaping)
}
return address{v}
case *ast.CompositeLit:
t := deref(b.exprType(e))
var v Value
if escaping {
v = emitNew(fn, t)
} else {
v = fn.addLocal(t)
}
b.compLit(fn, v, e, t) // initialize in place
return address{v}
case *ast.ParenExpr:
return b.addr(fn, e.X, escaping)
case *ast.SelectorExpr:
// p.M where p is a package.
if obj := b.isPackageRef(e); obj != nil {
if v, ok := b.lookup(obj); ok {
return address{v}
}
panic("undefined package-qualified name: " + obj.GetName())
}
// e.f where e is an expression.
e, index := b.demoteSelector(e, fn.Pkg)
var x Value
switch underlyingType(b.exprType(e.X)).(type) {
case *types.Struct:
x = b.addr(fn, e.X, escaping).(address).addr
case *types.Pointer:
x = b.expr(fn, e.X)
}
v := &FieldAddr{
X: x,
Field: index,
}
v.setType(pointer(b.exprType(e)))
return address{fn.emit(v)}
case *ast.IndexExpr:
var x Value
var et types.Type
switch t := underlyingType(b.exprType(e.X)).(type) {
case *types.Array:
x = b.addr(fn, e.X, escaping).(address).addr
et = pointer(t.Elt)
case *types.Pointer: // *array
x = b.expr(fn, e.X)
et = pointer(underlyingType(t.Base).(*types.Array).Elt)
case *types.Slice:
x = b.expr(fn, e.X)
et = pointer(t.Elt)
case *types.Map:
return &element{
m: b.expr(fn, e.X),
k: emitConv(fn, b.expr(fn, e.Index), t.Key),
t: t.Elt,
}
default:
panic("unexpected container type in IndexExpr: " + t.String())
}
v := &IndexAddr{
X: x,
Index: emitConv(fn, b.expr(fn, e.Index), tInt),
}
v.setType(et)
return address{fn.emit(v)}
case *ast.StarExpr:
return address{b.expr(fn, e.X)}
}
panic(fmt.Sprintf("unexpected address expression: %T", e))
}
// exprInPlace emits to fn code to initialize the lvalue loc with the
// value of expression e.
//
// typ is the type of the lvalue, which may be provided by the caller
// since it is sometimes only an inherited attribute (e.g. within in
// composite literals).
//
// This is equivalent to loc.store(fn, b.expr(fn, e)) but may
// generate better code in some cases, e.g. for composite literals
// in an addressable location.
//
func (b *Builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) {
if addr, ok := loc.(address); ok {
if e, ok := e.(*ast.CompositeLit); ok {
typ := addr.typ()
switch underlyingType(typ).(type) {
case *types.Pointer: // implicit & -- possibly escaping
ptr := b.addr(fn, e, true).(address).addr
addr.store(fn, ptr) // copy address
return
case *types.Interface:
// e.g. var x interface{} = T{...}
// Can't in-place initialize an interface value.
// Fall back to copying.
default:
b.compLit(fn, addr.addr, e, typ) // in place
return
}
}
}
loc.store(fn, b.expr(fn, e)) // copy value
}
// expr lowers a single-result expression e to SSA form, emitting code
// to fn and returning the Value defined by the expression.
//
func (b *Builder) expr(fn *Function, e ast.Expr) Value {
if lit := b.constants[e]; lit != nil {
return lit
}
switch e := e.(type) {
case *ast.BasicLit:
panic("non-constant BasicLit") // unreachable
case *ast.FuncLit:
posn := b.Prog.Files.Position(e.Type.Func)
fn2 := &Function{
Name_: fmt.Sprintf("func@%d.%d", posn.Line, posn.Column),
Signature: underlyingType(b.exprType(e.Type)).(*types.Signature),
Pos: e.Type.Func,
Enclosing: fn,
Pkg: fn.Pkg,
Prog: b.Prog,
syntax: &funcSyntax{
paramFields: e.Type.Params,
resultFields: e.Type.Results,
body: e.Body,
},
}
fn.Pkg.AnonFuncs = append(fn.Pkg.AnonFuncs, fn2)
b.buildFunction(fn2)
if fn2.FreeVars == nil {
return fn2
}
v := &MakeClosure{Fn: fn2}
v.setType(b.exprType(e))
for _, fv := range fn2.FreeVars {
v.Bindings = append(v.Bindings, fv.Outer)
}
return fn.emit(v)
case *ast.ParenExpr:
return b.expr(fn, e.X)
case *ast.TypeAssertExpr: // single-result form only
v := &TypeAssert{
X: b.expr(fn, e.X),
AssertedType: b.exprType(e),
}
v.setType(v.AssertedType)
return fn.emit(v)
case *ast.CallExpr:
typ := b.exprType(e)
if b.isType(e.Fun) {
// Type conversion, e.g. string(x) or big.Int(x)
return emitConv(fn, b.expr(fn, e.Args[0]), typ)
}
// Call to "intrinsic" built-ins, e.g. new, make.
wasPanic := false
if id, ok := e.Fun.(*ast.Ident); ok {
obj := b.obj(id)
if _, ok := fn.Prog.Builtins[obj]; ok {
if v := b.builtin(fn, id.Name, e.Args, typ); v != nil {
return v
}
wasPanic = id.Name == "panic"
}
}
// Regular function call.
var v Call
b.setCall(fn, e, &v.CallCommon)
v.setType(typ)
fn.emit(&v)
// Compile panic as if followed by for{} so that its
// successor is unreachable.
// TODO(adonovan): consider a dedicated Panic instruction
// (in which case, don't forget Go and Defer).
if wasPanic {
emitSelfLoop(fn)
fn.currentBlock = fn.newBasicBlock("unreachable")
}
return &v
case *ast.UnaryExpr:
switch e.Op {
case token.AND: // &X --- potentially escaping.
return b.addr(fn, e.X, true).(address).addr
case token.ADD:
return b.expr(fn, e.X)
case token.NOT, token.ARROW, token.SUB, token.XOR: // ! <- - ^
v := &UnOp{
Op: e.Op,
X: b.expr(fn, e.X),
}
v.setType(b.exprType(e))
return fn.emit(v)
default:
panic(e.Op)
}
case *ast.BinaryExpr:
switch e.Op {
case token.LAND, token.LOR:
return b.logicalBinop(fn, e)
case token.SHL, token.SHR:
fallthrough
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), b.exprType(e))
case token.EQL, token.NEQ, token.GTR, token.LSS, token.LEQ, token.GEQ:
return emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y))
default:
panic("illegal op in BinaryExpr: " + e.Op.String())
}
case *ast.SliceExpr:
var low, high Value
var x Value
switch underlyingType(b.exprType(e.X)).(type) {
case *types.Array:
// Potentially escaping.
x = b.addr(fn, e.X, true).(address).addr
case *types.Basic, *types.Slice, *types.Pointer: // *array
x = b.expr(fn, e.X)
default:
unreachable()
}
if e.High != nil {
high = b.expr(fn, e.High)
}
if e.Low != nil {
low = b.expr(fn, e.Low)
}
v := &Slice{
X: x,
Low: low,
High: high,
}
v.setType(b.exprType(e))
return fn.emit(v)
case *ast.Ident:
obj := b.obj(e)
// Global or universal?
if v, ok := b.lookup(obj); ok {
if objKind(obj) == ast.Var {
v = emitLoad(fn, v) // var (address)
}
return v
}
// Local?
return emitLoad(fn, fn.lookup(obj, false)) // var (address)
case *ast.SelectorExpr:
// p.M where p is a package.
if obj := b.isPackageRef(e); obj != nil {
return b.expr(fn, e.Sel)
}
// (*T).f or T.f, the method f from the method-set of type T.
if b.isType(e.X) {
id := makeId(e.Sel.Name, fn.Pkg.Types)
typ := b.exprType(e.X)
if m := b.Prog.MethodSet(typ)[id]; m != nil {
return m
}
// T must be an interface; return method thunk.
return makeImethodThunk(b.Prog, typ, id)
}
// e.f where e is an expression.
e, index := b.demoteSelector(e, fn.Pkg)
switch underlyingType(b.exprType(e.X)).(type) {
case *types.Struct:
// Non-addressable struct in a register.
v := &Field{
X: b.expr(fn, e.X),
Field: index,
}
v.setType(b.exprType(e))
return fn.emit(v)
case *types.Pointer: // *struct
// Addressable structs; use FieldAddr and Load.
return b.addr(fn, e, false).load(fn)
}
case *ast.IndexExpr:
switch t := underlyingType(b.exprType(e.X)).(type) {
case *types.Array:
// Non-addressable array (in a register).
v := &Index{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), tInt),
}
v.setType(t.Elt)
return fn.emit(v)
case *types.Map:
// Maps are not addressable.
mapt := underlyingType(b.exprType(e.X)).(*types.Map)
v := &Lookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key),
}
v.setType(mapt.Elt)
return fn.emit(v)
case *types.Basic: // => string
// Strings are not addressable.
v := &Lookup{
X: b.expr(fn, e.X),
Index: b.expr(fn, e.Index),
}
v.setType(tByte)
return fn.emit(v)
case *types.Slice, *types.Pointer: // *array
// Addressable slice/array; use IndexAddr and Load.
return b.addr(fn, e, false).load(fn)
default:
panic("unexpected container type in IndexExpr: " + t.String())
}
case *ast.CompositeLit, *ast.StarExpr:
// Addressable types (lvalues)
return b.addr(fn, e, false).load(fn)
}
panic(fmt.Sprintf("unexpected expr: %T", e))
}
// stmtList emits to fn code for all statements in list.
func (b *Builder) stmtList(fn *Function, list []ast.Stmt) {
for _, s := range list {
b.stmt(fn, s)
}
}
// setCallFunc populates the function parts of a CallCommon structure
// (Func, Method, Recv, Args[0]) based on the kind of invocation
// occurring in e.
//
func (b *Builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
c.Pos = e.Lparen
// Is the call of the form x.f()?
sel, ok := noparens(e.Fun).(*ast.SelectorExpr)
// Case 0: e.Fun evaluates normally to a function.
if !ok {
c.Func = b.expr(fn, e.Fun)
return
}
// Case 1: call of form x.F() where x is a package name.
if obj := b.isPackageRef(sel); obj != nil {
// This is a specialization of expr(ast.Ident(obj)).
if v, ok := b.lookup(obj); ok {
if _, ok := v.(*Function); !ok {
v = emitLoad(fn, v) // var (address)
}
c.Func = v
return
}
panic("undefined package-qualified name: " + obj.GetName())
}
// 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
// an interface. Treat like case 0.
// TODO(adonovan): inline expr() here, to make the call static
// and to avoid generation of a stub for an interface method.
if b.isType(sel.X) {
c.Func = b.expr(fn, e.Fun)
return
}
// Let X be the type of x.
typ := b.exprType(sel.X)
// Case 2: x.f(): a statically dispatched call to a method
// from the method-set of X or perhaps *X (if x is addressable
// but not a pointer).
id := makeId(sel.Sel.Name, fn.Pkg.Types)
// Consult method-set of X.
if m := b.Prog.MethodSet(typ)[id]; m != nil {
var recv Value
aptr := isPointer(typ)
fptr := isPointer(m.Signature.Recv.Type)
if aptr == fptr {
// Actual's and formal's "pointerness" match.
recv = b.expr(fn, sel.X)
} else {
// Actual is a pointer, formal is not.
// Load a copy.
recv = emitLoad(fn, b.expr(fn, sel.X))
}
c.Func = m
c.Args = append(c.Args, recv)
return
}
if !isPointer(typ) {
// Consult method-set of *X.
if m := b.Prog.MethodSet(pointer(typ))[id]; m != nil {
// A method found only in MS(*X) must have a
// pointer formal receiver; but the actual
// value is not a pointer.
// Implicit & -- possibly escaping.
recv := b.addr(fn, sel.X, true).(address).addr
c.Func = m
c.Args = append(c.Args, recv)
return
}
}
switch t := underlyingType(typ).(type) {
case *types.Struct, *types.Pointer:
// Case 3: x.f() where x.f is a function value in a
// struct field f; not a method call. f is a 'var'
// (of function type) in the Fields of types.Struct X.
// Treat like case 0.
c.Func = b.expr(fn, e.Fun)
case *types.Interface:
// Case 4: x.f() where a dynamically dispatched call
// to an interface method f. f is a 'func' object in
// the Methods of types.Interface X
c.Method, _ = methodIndex(t, t.Methods, id)
c.Recv = b.expr(fn, sel.X)
default:
panic(fmt.Sprintf("illegal (%s).%s() call; X:%T", t, sel.Sel.Name, sel.X))
}
}
// setCall emits to fn code to evaluate all the parameters of a function
// call e, and populates *c with those values.
//
func (b *Builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {
// First deal with the f(...) part.
b.setCallFunc(fn, e, c)
// Argument passing. There are 4 cases to consider:
// 1. Ordinary call to non-variadic function.
// All args are treated in the usual manner.
// 2. Ordinary call to variadic function, f(a, b, v1, ..., vn).
// Caller constructs a slice from the varargs arguments v_i.
// 3. Ellipsis call f(a, b, rest...) to variadic function.
// 'rest' is already a slice; all args treated in the usual manner.
// 4. f(g()) where g has >1 return parameters. f may also be variadic.
// TODO(adonovan): implement.
var args, varargs []ast.Expr = e.Args, nil
c.HasEllipsis = e.Ellipsis != 0
// Determine which suffix (if any) of Args is a varargs slice.
var vt types.Type // element type of the variadic slice
switch typ := underlyingType(b.exprType(e.Fun)).(type) {
case *types.Signature:
np := len(typ.Params)
if !c.HasEllipsis {
if typ.IsVariadic && len(args) > np-1 {
// case 2: ordinary call of variadic function.
vt = typ.Params[np-1].Type
args, varargs = args[:np-1], args[np-1:]
}
// TODO(adonovan): fix: not disjoint with case above!
// Consider f(...int) called with g() (int,int)
// Incomplete.
if len(args) == 1 && (np > 1 || typ.IsVariadic) {
if res, ok := b.exprType(args[0]).(*types.Result); ok {
res = res
// case 4: f(g()) where g is a multi.
// TODO(adonovan): generate:
// tuple := g()
// args = [extract 0, ... extract n]
// f(args)
}
}
}
// Non-varargs.
for i, arg := range args {
// TODO(gri): annoyingly Signature.Params
// doesn't reflect the slice type for a final
// ...T param.
t := typ.Params[i].Type
if typ.IsVariadic && c.HasEllipsis && i == len(args)-1 {
t = &types.Slice{Elt: t}
}
v := emitConv(fn, b.expr(fn, arg), t)
c.Args = append(c.Args, v)
}
default:
// builtin: ad-hoc typing rules are required for all
// variadic (append, print, println) and polymorphic
// (append, copy, delete, close) built-ins.
//
// TODO(adonovan): it would so much cleaner if the
// typechecker would ascribe a unique Signature type
// to each e.Fun expression calling a built-in.
// Then we could use the same logic as above.
//
// TODO(adonovan): fix: support case 4. But know that the
// other tools don't support it for built-ins yet
// (http://code.google.com/p/go/issues/detail?id=4573),
// so there's no rush.
var bptypes []types.Type // formal parameter types of builtins
switch builtin := e.Fun.(*ast.Ident).Name; builtin {
case "append":
// append([]T, ...T) []T
// append([]byte, string...) []byte // TODO(adonovan): fix: support.
// Infer arg types from result type:
rt := b.exprType(e)
vt = underlyingType(rt).(*types.Slice).Elt // variadic
if !c.HasEllipsis {
args, varargs = args[:1], args[1:]
}
bptypes = append(bptypes, rt)
case "close":
bptypes = append(bptypes, nil) // no conv
case "copy":
// copy([]T, []T) int
// Infer arg types from each other. Sleazy.
if st, ok := underlyingType(b.exprType(args[0])).(*types.Slice); ok {
bptypes = append(bptypes, st, st)
} else if st, ok := underlyingType(b.exprType(args[1])).(*types.Slice); ok {
bptypes = append(bptypes, st, st)
} else {
panic("cannot infer types in call to copy()")
}
case "delete":
// delete(map[K]V, K)
// TODO(adonovan): fix: this is incorrect.
bptypes = append(bptypes, nil) // map
bptypes = append(bptypes, nil) // key
case "print", "println": // print{,ln}(any, ...any)
vt = new(types.Interface) // variadic
if !c.HasEllipsis {
args, varargs = args[:1], args[1:]
}
case "len":
bptypes = append(bptypes, nil) // no conv
case "cap":
bptypes = append(bptypes, nil) // no conv
case "real", "imag":
// TODO(adonovan): fix: apply reverse conversion
// to "complex" case below.
bptypes = append(bptypes, nil)
case "complex":
// Typechecker, help us out. :(
var argType types.Type
switch b.exprType(e).(*types.Basic).Kind {
case types.UntypedComplex:
argType = types.Typ[types.UntypedFloat]
case types.Complex128:
argType = tFloat64
case types.Complex64:
argType = tFloat32
default:
unreachable()
}
bptypes = append(bptypes, argType, argType)
case "panic":
bptypes = append(bptypes, new(types.Interface))
case "recover":
// no-op
default:
panic("unknown builtin: " + builtin)
}
// Non-varargs.
for i, arg := range args {
v := b.expr(fn, arg)
if i < len(bptypes) && bptypes[i] != nil {
v = emitConv(fn, v, bptypes[i])
}
c.Args = append(c.Args, v)
}
}
// Common code for varargs.
if len(varargs) > 0 { // case 2
at := &types.Array{
Elt: vt,
Len: int64(len(varargs)),
}
a := emitNew(fn, at)
for i, arg := range varargs {
iaddr := &IndexAddr{
X: a,
Index: intLiteral(int64(i)),
}
iaddr.setType(pointer(vt))
fn.emit(iaddr)
emitStore(fn, iaddr, b.expr(fn, arg))
}
s := &Slice{X: a}
s.setType(&types.Slice{Elt: vt})
c.Args = append(c.Args, fn.emit(s))
}
}
// assignOp emits to fn code to perform loc += incr or loc -= incr.
func (b *Builder) assignOp(fn *Function, loc lvalue, incr Value, op token.Token) {
oldv := loc.load(fn)
loc.store(fn, emitArith(fn, op, oldv, emitConv(fn, incr, oldv.Type()), loc.typ()))
}
// 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 == b.globals[obj]
//
func (b *Builder) buildGlobal(g *Global, obj types.Object) {
spec := g.spec
if spec == nil {
return // already built (or in progress)
}
b.globalValueSpec(g.Pkg.Init, spec, g, obj)
}
// globalValueSpec emits to init code to define one or all of the vars
// in the package-level ValueSpec spec.
//
// It implements the build phase for a ValueSpec, ensuring that all
// vars are initialized if not already visited by buildGlobal during
// the reference graph traversal.
//
// This function may be called in two modes:
// A) with g and obj non-nil, to initialize just a single global.
// This occurs during the reference graph traversal.
// 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 == b.globals[obj]
//
// Package-level var initialization order is quite subtle.
// The side effects of:
// var a, b = f(), g()
// are not observed left-to-right if b is referenced before a in the
// reference graph traversal. So, we track which Globals have been
// initialized by setting Global.spec=nil.
//
// Blank identifiers make things more complex since they don't have
// associated types.Objects or ssa.Globals yet we must still ensure
// that their corresponding side effects are observed at the right
// moment. Consider:
// var a, _, b = f(), g(), h()
// Here, the relative ordering of the call to g() is unspecified but
// it must occur exactly once, during mode B. So globalValueSpec for
// blanks must special-case n:n assigments and just evaluate the RHS
// g() for effect.
//
// In a n:1 assignment:
// var a, _, b = f()
// a reference to either a or b causes both globals to be initialized
// at the same time. Furthermore, no further work is required to
// ensure that the effects of the blank assignment occur. We must
// keep track of which n:1 specs have been evaluated, independent of
// which Globals are on the LHS (possibly none, if all are blank).
//
// See also localValueSpec.
//
func (b *Builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global, obj types.Object) {
switch {
case len(spec.Values) == len(spec.Names):
// e.g. var x, y = 0, 1
// 1:1 assignment.
// Only the first time for a given GLOBAL has any effect.
for i, id := range spec.Names {
var lval lvalue = blank{}
if g != nil {
// Mode A: initialized only a single global, g
if isBlankIdent(id) || b.obj(id) != obj {
continue
}
g.spec = nil
lval = address{g}
} else {
// Mode B: initialize all globals.
if !isBlankIdent(id) {
g2 := b.globals[b.obj(id)].(*Global)
if g2.spec == nil {
continue // already done
}
g2.spec = nil
lval = address{g2}
}
}
if b.mode&LogSource != 0 {
fmt.Fprintln(os.Stderr, "build global", id.Name)
}
b.exprInPlace(init, lval, spec.Values[i])
if g != nil {
break
}
}
case len(spec.Values) == 0:
// e.g. var x, y int
// Globals are implicitly zero-initialized.
default:
// e.g. var x, _, y = f()
// n:1 assignment.
// Only the first time for a given SPEC has any effect.
if !b.nTo1Vars[spec] {
b.nTo1Vars[spec] = true
if b.mode&LogSource != 0 {
fmt.Fprintln(os.Stderr, "build globals", spec.Names) // ugly...
}
tuple := b.exprN(init, spec.Values[0])
rtypes := tuple.Type().(*types.Result).Values
for i, id := range spec.Names {
if !isBlankIdent(id) {
g := b.globals[b.obj(id)].(*Global)
g.spec = nil // just an optimisation
emitStore(init, g,
emitExtract(init, tuple, i, rtypes[i].Type))
}
}
}
}
}
// localValueSpec emits to fn code to define all of the vars in the
// function-local ValueSpec, spec.
//
// See also globalValueSpec: the two routines are similar but local
// ValueSpecs are much simpler since they are encountered once only,
// in their entirety, in lexical order.
//
func (b *Builder) localValueSpec(fn *Function, spec *ast.ValueSpec) {
switch {
case len(spec.Values) == len(spec.Names):
// 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{fn.addNamedLocal(b.obj(id))}
}
b.exprInPlace(fn, lval, spec.Values[i])
}
case len(spec.Values) == 0:
// e.g. var x, y int
// Locals are implicitly zero-initialized.
for _, id := range spec.Names {
if !isBlankIdent(id) {
fn.addNamedLocal(b.obj(id))
}
}
default:
// e.g. var x, y = pos()
tuple := b.exprN(fn, spec.Values[0])
rtypes := tuple.Type().(*types.Result).Values
for i, id := range spec.Names {
if !isBlankIdent(id) {
lhs := fn.addNamedLocal(b.obj(id))
emitStore(fn, lhs, emitExtract(fn, tuple, i, rtypes[i].Type))
}
}
}
}
// assignStmt emits code to fn for a parallel assignment of rhss to lhss.
// isDef is true if this is a short variable declaration (:=).
//
// Note the similarity with localValueSpec.
// TODO(adonovan): explain differences.
//
func (b *Builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool) {
// Side effects of all LHSs and RHSs must occur in left-to-right order.
var lvals []lvalue
for _, lhs := range lhss {
var lval lvalue = blank{}
if !isBlankIdent(lhs) {
if isDef {
// Local may be "redeclared" in the same
// scope, so don't blindly create anew.
obj := b.obj(lhs.(*ast.Ident))
if _, ok := fn.objects[obj]; !ok {
fn.addNamedLocal(obj)
}
}
lval = b.addr(fn, lhs, false) // non-escaping
}
lvals = append(lvals, lval)
}
if len(lhss) == len(rhss) {
// e.g. x, y = f(), g()
if len(lhss) == 1 {
// x = type{...}
// Optimisation: in-place construction
// of composite literals.
b.exprInPlace(fn, lvals[0], rhss[0])
} else {
// Parallel assignment. All reads must occur
// before all updates, precluding exprInPlace.
// TODO(adonovan): opt: is it sound to
// perform exprInPlace if !isDef?
var rvals []Value
for _, rval := range rhss {
rvals = append(rvals, b.expr(fn, rval))
}
for i, lval := range lvals {
lval.store(fn, rvals[i])
}
}
} else {
// e.g. x, y = pos()
tuple := b.exprN(fn, rhss[0])
rtypes := tuple.Type().(*types.Result).Values
for i, lval := range lvals {
lval.store(fn, emitExtract(fn, tuple, i, rtypes[i].Type))
}
}
}
// compLit emits to fn code to initialize a composite literal e at
// address addr with type typ, typically allocated by Alloc.
// Nested composite literals are recursively initialized in place
// where possible.
//
func (b *Builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, typ types.Type) {
// TODO(adonovan): test whether typ ever differs from
// b.exprType(e) and if so document why.
switch t := underlyingType(typ).(type) {
case *types.Struct:
for i, e := range e.Elts {
// Subtle: field index i is updated in KeyValue case.
var sf *types.Field
if kv, ok := e.(*ast.KeyValueExpr); ok {
fname := kv.Key.(*ast.Ident).Name
for i, sf = range t.Fields {
if sf.Name == fname {
e = kv.Value
break
}
}
} else {
sf = t.Fields[i]
}
faddr := &FieldAddr{
X: addr,
Field: i,
}
faddr.setType(pointer(sf.Type))
fn.emit(faddr)
b.exprInPlace(fn, address{faddr}, e)
}
case *types.Array, *types.Slice:
var at *types.Array
var array Value
switch t := t.(type) {
case *types.Slice:
at = &types.Array{Elt: t.Elt} // set Len later
array = emitNew(fn, at)
case *types.Array:
at = t
array = addr
}
var idx *Literal
var max int64 = -1
for _, e := range e.Elts {
if kv, ok := e.(*ast.KeyValueExpr); ok {
idx = b.expr(fn, kv.Key).(*Literal)
e = kv.Value
} else {
var idxval int64
if idx != nil {
idxval = idx.Int64() + 1
}
idx = intLiteral(idxval)
}
if idx.Int64() > max {
max = idx.Int64()
}
iaddr := &IndexAddr{
X: array,
Index: idx,
}
iaddr.setType(pointer(at.Elt))
fn.emit(iaddr)
b.exprInPlace(fn, address{iaddr}, e)
}
if t != at { // slice
at.Len = max + 1
s := &Slice{X: array}
s.setType(t)
emitStore(fn, addr, fn.emit(s))
}
case *types.Map:
m := &MakeMap{Reserve: intLiteral(int64(len(e.Elts)))}
m.setType(typ)
emitStore(fn, addr, fn.emit(m))
for _, e := range e.Elts {
e := e.(*ast.KeyValueExpr)
up := &MapUpdate{
Map: m,
Key: emitConv(fn, b.expr(fn, e.Key), t.Key),
Value: emitConv(fn, b.expr(fn, e.Value), t.Elt),
}
fn.emit(up)
}
case *types.Pointer:
// Pointers can only occur in the recursive case; we
// strip them off in addr() before calling compLit
// again, so that we allocate space for a T not a *T.
panic("compLit(fn, addr, e, *types.Pointer")
default:
panic("unexpected CompositeLit type: " + t.String())
}
}
// switchStmt emits to fn code for the switch statement s, optionally
// labelled by label.
//
func (b *Builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) {
// We treat SwitchStmt like a sequential if-else chain.
// More efficient strategies (e.g. multiway dispatch)
// are possible if all cases are free of side effects.
if s.Init != nil {
b.stmt(fn, s.Init)
}
var tag Value = vTrue
if s.Tag != nil {
tag = b.expr(fn, s.Tag)
}
done := fn.newBasicBlock("switch.done")
if label != nil {
label._break = done
}
// We pull the default case (if present) down to the end.
// But each fallthrough label must point to the next
// body block in source order, so we preallocate a
// body block (fallthru) for the next case.
// Unfortunately this makes for a confusing block order.
var dfltBody *[]ast.Stmt
var dfltFallthrough *BasicBlock
var fallthru, dfltBlock *BasicBlock
ncases := len(s.Body.List)
for i, clause := range s.Body.List {
body := fallthru
if body == nil {
body = fn.newBasicBlock("switch.body") // first case only
}
// Preallocate body block for the next case.
fallthru = done
if i+1 < ncases {
fallthru = fn.newBasicBlock("switch.body")
}
cc := clause.(*ast.CaseClause)
if cc.List == nil {
// Default case.
dfltBody = &cc.Body
dfltFallthrough = fallthru
dfltBlock = body
continue
}
var nextCond *BasicBlock
for _, cond := range cc.List {
nextCond = fn.newBasicBlock("switch.next")
// TODO(adonovan): opt: when tag==vTrue, we'd
// get better much code if we use b.cond(cond)
// instead of BinOp(EQL, tag, b.expr(cond))
// followed by If. Don't forget conversions
// though.
cond := emitCompare(fn, token.EQL, tag, b.expr(fn, cond))
emitIf(fn, cond, body, nextCond)
fn.currentBlock = nextCond
}
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
_fallthrough: fallthru,
}
b.stmtList(fn, cc.Body)
fn.targets = fn.targets.tail
emitJump(fn, done)
fn.currentBlock = nextCond
}
if dfltBlock != nil {
emitJump(fn, dfltBlock)
fn.currentBlock = dfltBlock
fn.targets = &targets{
tail: fn.targets,
_break: done,
_fallthrough: dfltFallthrough,
}
b.stmtList(fn, *dfltBody)
fn.targets = fn.targets.tail
}
emitJump(fn, done)
fn.currentBlock = done
}
// typeSwitchStmt emits to fn code for the type switch statement s, optionally
// labelled by label.
//
func (b *Builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lblock) {
// We treat TypeSwitchStmt like a sequential if-else
// chain. More efficient strategies (e.g. multiway
// dispatch) are possible.
// Typeswitch lowering:
//
// var x X
// switch y := x.(type) {
// case T1, T2: S1 // >1 (y := x)
// default: SD // 0 types (y := x)
// case T3: S3 // 1 type (y := x.(T3))
// }
//
// ...s.Init...
// x := eval x
// y := x
// .caseT1:
// t1, ok1 := typeswitch,ok x <T1>
// if ok1 then goto S1 else goto .caseT2
// .caseT2:
// t2, ok2 := typeswitch,ok x <T2>
// if ok2 then goto S1 else goto .caseT3
// .S1:
// ...S1...
// goto done
// .caseT3:
// t3, ok3 := typeswitch,ok x <T3>
// if ok3 then goto S3 else goto default
// .S3:
// y' := t3 // Kludge: within scope of S3, y resolves here
// ...S3...
// goto done
// .default:
// goto done
// .done:
if s.Init != nil {
b.stmt(fn, s.Init)
}
var x, y Value
var id *ast.Ident
switch ass := s.Assign.(type) {
case *ast.ExprStmt: // x.(type)
x = b.expr(fn, noparens(ass.X).(*ast.TypeAssertExpr).X)
case *ast.AssignStmt: // y := x.(type)
x = b.expr(fn, noparens(ass.Rhs[0]).(*ast.TypeAssertExpr).X)
id = ass.Lhs[0].(*ast.Ident)
y = fn.addNamedLocal(b.obj(id))
emitStore(fn, y, x)
}
done := fn.newBasicBlock("typeswitch.done")
if label != nil {
label._break = done
}
var dfltBody []ast.Stmt
for _, clause := range s.Body.List {
cc := clause.(*ast.CaseClause)
if cc.List == nil {
dfltBody = cc.Body
continue
}
body := fn.newBasicBlock("typeswitch.body")
var next *BasicBlock
var casetype types.Type
var ti Value // t_i, ok := typeassert,ok x <T_i>
for _, cond := range cc.List {
next = fn.newBasicBlock("typeswitch.next")
casetype = b.exprType(cond)
var condv Value
if casetype == tUntypedNil {
condv = emitCompare(fn, token.EQL, x, nilLiteral(x.Type()))
} else {
yok := &TypeAssert{
X: x,
AssertedType: casetype,
CommaOk: true,
}
yok.setType(&types.Result{Values: []*types.Var{
{Name: "value", Type: casetype},
varOk,
}})
fn.emit(yok)
ti = emitExtract(fn, yok, 0, casetype)
condv = emitExtract(fn, yok, 1, tBool)
}
emitIf(fn, condv, body, next)
fn.currentBlock = next
}
fn.currentBlock = body
if id != nil && len(cc.List) == 1 && casetype != tUntypedNil {
// Declare a new shadow local variable of the
// same name but a more specific type.
// Side effect: reassociates binding for y's object.
y2 := fn.addNamedLocal(b.obj(id))
y2.Name_ += "'" // debugging aid
y2.Type_ = pointer(casetype)
emitStore(fn, y2, ti)
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, cc.Body)
fn.targets = fn.targets.tail
if id != nil {
fn.objects[b.obj(id)] = y // restore previous y binding
}
emitJump(fn, done)
fn.currentBlock = next
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, dfltBody)
fn.targets = fn.targets.tail
emitJump(fn, done)
fn.currentBlock = done
}
// selectStmt emits to fn code for the select statement s, optionally
// labelled by label.
//
func (b *Builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
// A blocking select of a single case degenerates to a
// simple send or receive.
// TODO(adonovan): is this optimisation worth its weight?
if len(s.Body.List) == 1 {
clause := s.Body.List[0].(*ast.CommClause)
if clause.Comm != nil {
b.stmt(fn, clause.Comm)
done := fn.newBasicBlock("select.done")
if label != nil {
label._break = done
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, clause.Body)
fn.targets = fn.targets.tail
emitJump(fn, done)
fn.currentBlock = done
return
}
}
// First evaluate all channels in all cases, and find
// the directions of each state.
var states []SelectState
blocking := true
for _, clause := range s.Body.List {
switch comm := clause.(*ast.CommClause).Comm.(type) {
case nil: // default case
blocking = false
case *ast.SendStmt: // ch<- i
ch := b.expr(fn, comm.Chan)
states = append(states, SelectState{
Dir: ast.SEND,
Chan: ch,
Send: emitConv(fn, b.expr(fn, comm.Value),
underlyingType(ch.Type()).(*types.Chan).Elt),
})
case *ast.AssignStmt: // x := <-ch
states = append(states, SelectState{
Dir: ast.RECV,
Chan: b.expr(fn, noparens(comm.Rhs[0]).(*ast.UnaryExpr).X),
})
case *ast.ExprStmt: // <-ch
states = append(states, SelectState{
Dir: ast.RECV,
Chan: b.expr(fn, noparens(comm.X).(*ast.UnaryExpr).X),
})
}
}
// We dispatch on the (fair) result of Select using a
// sequential if-else chain, in effect:
//
// idx, recv, recvOk := select(...)
// if idx == 0 { // receive on channel 0
// x, ok := recv, recvOk
// ...state0...
// } else if v == 1 { // send on channel 1
// ...state1...
// } else {
// ...default...
// }
//
// TODO(adonovan): opt: define and use a multiway dispatch instr.
pair := &Select{
States: states,
Blocking: blocking,
}
pair.setType(tSelect)
fn.emit(pair)
idx := emitExtract(fn, pair, 0, tInt)
done := fn.newBasicBlock("select.done")
if label != nil {
label._break = done
}
var dfltBody *[]ast.Stmt
state := 0
for _, cc := range s.Body.List {
clause := cc.(*ast.CommClause)
if clause.Comm == nil {
dfltBody = &clause.Body
continue
}
body := fn.newBasicBlock("select.body")
next := fn.newBasicBlock("select.next")
emitIf(fn, emitCompare(fn, token.EQL, idx, intLiteral(int64(state))), body, next)
fn.currentBlock = body
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
switch comm := clause.Comm.(type) {
case *ast.AssignStmt: // x := <-states[state].Chan
xdecl := fn.addNamedLocal(b.obj(comm.Lhs[0].(*ast.Ident)))
emitStore(fn, xdecl, emitExtract(fn, pair, 1, indirectType(xdecl.Type())))
if len(comm.Lhs) == 2 { // x, ok := ...
okdecl := fn.addNamedLocal(b.obj(comm.Lhs[1].(*ast.Ident)))
emitStore(fn, okdecl, emitExtract(fn, pair, 2, indirectType(okdecl.Type())))
}
}
b.stmtList(fn, clause.Body)
fn.targets = fn.targets.tail
emitJump(fn, done)
fn.currentBlock = next
state++
}
if dfltBody != nil {
fn.targets = &targets{
tail: fn.targets,
_break: done,
}
b.stmtList(fn, *dfltBody)
fn.targets = fn.targets.tail
}
emitJump(fn, done)
fn.currentBlock = done
}
// forStmt emits to fn code for the for statement s, optionally
// labelled by label.
//
func (b *Builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) {
// ...init...
// jump loop
// loop:
// if cond goto body else done
// body:
// ...body...
// jump post
// post: (target of continue)
// ...post...
// jump loop
// done: (target of break)
if s.Init != nil {
b.stmt(fn, s.Init)
}
body := fn.newBasicBlock("for.body")
done := fn.newBasicBlock("for.done") // target of 'break'
loop := body // target of back-edge
if s.Cond != nil {
loop = fn.newBasicBlock("for.loop")
}
cont := loop // target of 'continue'
if s.Post != nil {
cont = fn.newBasicBlock("for.post")
}
if label != nil {
label._break = done
label._continue = cont
}
emitJump(fn, loop)
fn.currentBlock = loop
if loop != body {
b.cond(fn, s.Cond, body, done)
fn.currentBlock = body
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
_continue: cont,
}
b.stmt(fn, s.Body)
fn.targets = fn.targets.tail
emitJump(fn, cont)
if s.Post != nil {
fn.currentBlock = cont
b.stmt(fn, s.Post)
emitJump(fn, loop) // back-edge
}
fn.currentBlock = done
}
// rangeStmt emits to fn code for the range statement s, optionally
// labelled by label.
//
func (b *Builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) {
// it := range x
// jump loop
// loop: (target of continue)
// okv := next it (ok, key, value?)
// ok = extract okv #0
// if ok goto body else done
// body:
// t0 = extract okv #1
// k = *t0
// t1 = extract okv #2
// v = *t1
// ...body...
// jump loop
// done: (target of break)
hasK := !isBlankIdent(s.Key)
hasV := s.Value != nil && !isBlankIdent(s.Value)
// Ranging over just the keys of a pointer to an array
// doesn't (need to) evaluate the array:
// for i := range (*[10]int)(nil) {...}
// Instead it is transformed into a simple loop:
// i = -1
// jump loop
// loop: (target of continue)
// increment i
// if i < 10 goto body else done
// body:
// k = i
// ...body...
// jump loop
// done: (target of break)
var arrayLen int64 = -1
if !hasV {
if ptr, ok := underlyingType(b.exprType(s.X)).(*types.Pointer); ok {
if arr, ok := underlyingType(ptr.Base).(*types.Array); ok {
arrayLen = arr.Len
}
}
}
// If iteration variables are defined (:=), this
// occurs once outside the loop.
//
// Unlike a short variable declaration, a RangeStmt
// using := never redeclares an existing variable; it
// always creates a new one.
if s.Tok == token.DEFINE {
if hasK {
fn.addNamedLocal(b.obj(s.Key.(*ast.Ident)))
}
if hasV {
fn.addNamedLocal(b.obj(s.Value.(*ast.Ident)))
}
}
var ok Value
var okv *Next
var okvVars []*types.Var
var index *Alloc // *array index loops only
loop := fn.newBasicBlock("range.loop")
var body, done *BasicBlock
if arrayLen == -1 {
rng := &Range{X: b.expr(fn, s.X)}
rng.setType(tRangeIter)
it := fn.emit(rng)
emitJump(fn, loop)
fn.currentBlock = loop
okv = &Next{Iter: it}
okvVars = []*types.Var{
varOk,
{Name: "k", Type: tInvalid}, // mutated below
{Name: "v", Type: tInvalid}, // mutated below
}
okv.setType(&types.Result{Values: okvVars})
fn.emit(okv)
ok = emitExtract(fn, okv, 0, tBool)
} else {
index = fn.addLocal(tInt)
emitStore(fn, index, intLiteral(-1))
emitJump(fn, loop)
fn.currentBlock = loop
// TODO use emitArith here and elsewhere?
incr := &BinOp{
Op: token.ADD,
X: emitLoad(fn, index),
Y: intLiteral(1),
}
incr.setType(tInt)
emitStore(fn, index, fn.emit(incr))
ok = emitCompare(fn, token.LSS, incr, intLiteral(arrayLen))
}
body = fn.newBasicBlock("range.body")
done = fn.newBasicBlock("range.done")
emitIf(fn, ok, body, done)
fn.currentBlock = body
if label != nil {
label._break = done
label._continue = loop
}
if arrayLen == -1 {
// Evaluate both LHS expressions before we update either.
var k, v lvalue
if hasK {
k = b.addr(fn, s.Key, false) // non-escaping
okvVars[1].Type = b.exprType(s.Key)
}
if hasV {
v = b.addr(fn, s.Value, false) // non-escaping
okvVars[2].Type = b.exprType(s.Value)
}
if hasK {
k.store(fn, emitExtract(fn, okv, 1, okvVars[1].Type))
}
if hasV {
v.store(fn, emitExtract(fn, okv, 2, okvVars[2].Type))
}
} else {
// Store a copy of the index variable to k.
if hasK {
k := b.addr(fn, s.Key, false) // non-escaping
k.store(fn, emitLoad(fn, index))
}
}
fn.targets = &targets{
tail: fn.targets,
_break: done,
_continue: loop,
}
b.stmt(fn, s.Body)
fn.targets = fn.targets.tail
emitJump(fn, loop) // back-edge
fn.currentBlock = done
}
// stmt lowers statement s to SSA form, emitting code to fn.
func (b *Builder) stmt(fn *Function, _s ast.Stmt) {
// The label of the current statement. If non-nil, its _goto
// target is always set; its _break and _continue are set only
// within the body of switch/typeswitch/select/for/range.
// It is effectively an additional default-nil parameter of stmt().
// TODO(adonovan): fix: handle multiple labels on the same stmt.
var label *lblock
start:
switch s := _s.(type) {
case *ast.EmptyStmt:
// ignore. (Usually removed by gofmt.)
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)
}
}
case *ast.LabeledStmt:
label = fn.labelledBlock(s.Label)
emitJump(fn, label._goto)
fn.currentBlock = label._goto
_s = s.Stmt
goto start // effectively: tailcall stmt(fn, s.Stmt, label)
case *ast.ExprStmt:
b.expr(fn, s.X)
case *ast.SendStmt:
fn.emit(&Send{
Chan: b.expr(fn, s.Chan),
X: emitConv(fn, b.expr(fn, s.Value),
underlyingType(b.exprType(s.Chan)).(*types.Chan).Elt),
})
case *ast.IncDecStmt:
op := token.ADD
if s.Tok == token.DEC {
op = token.SUB
}
b.assignOp(fn, b.addr(fn, s.X, false), vOne, op)
case *ast.AssignStmt:
switch s.Tok {
case token.ASSIGN, token.DEFINE:
b.assignStmt(fn, s.Lhs, s.Rhs, s.Tok == token.DEFINE)
default: // +=, etc.
op := s.Tok + token.ADD - token.ADD_ASSIGN
b.assignOp(fn, b.addr(fn, s.Lhs[0], false), b.expr(fn, s.Rhs[0]), op)
}
case *ast.GoStmt:
// The "intrinsics" new/make/len/cap are forbidden here.
// panic() is not forbidden, but is not (yet) an intrinsic.
var v Go
b.setCall(fn, s.Call, &v.CallCommon)
fn.emit(&v)
case *ast.DeferStmt:
// The "intrinsics" new/make/len/cap are forbidden here.
// panic() is not forbidden, but is not (yet) an intrinsic.
var v Defer
b.setCall(fn, s.Call, &v.CallCommon)
fn.emit(&v)
case *ast.ReturnStmt:
if fn == fn.Pkg.Init {
// A "return" within an init block is treated
// like a "goto" to the next init block. We
// use the outermost BREAK target for this purpose.
var block *BasicBlock
for t := fn.targets; t != nil; t = t.tail {
if t._break != nil {
block = t._break
}
}
emitJump(fn, block)
fn.currentBlock = fn.newBasicBlock("unreachable")
return
}
var results []Value
// Per the spec, there are three distinct cases of return.
switch {
case len(s.Results) == 0:
// Return with no arguments.
// Prior assigns to named result params are
// reloaded into results tuple.
// A void function is a degenerate case of this.
for _, r := range fn.results {
results = append(results, emitLoad(fn, r))
}
case len(s.Results) == 1 && len(fn.Signature.Results) > 1:
// Return of one expression in a multi-valued function.
tuple := b.exprN(fn, s.Results[0])
for i, v := range tuple.Type().(*types.Result).Values {
results = append(results, emitExtract(fn, tuple, i, v.Type))
}
default:
// Return one or more single-valued expressions.
// These become the scalar or tuple result.
for _, r := range s.Results {
results = append(results, b.expr(fn, r))
}
}
// Perform implicit conversions.
for i := range results {
results[i] = emitConv(fn, results[i], fn.Signature.Results[i].Type)
}
fn.emit(&Ret{Results: results})
fn.currentBlock = fn.newBasicBlock("unreachable")
case *ast.BranchStmt:
var block *BasicBlock
switch s.Tok {
case token.BREAK:
if s.Label != nil {
block = fn.labelledBlock(s.Label)._break
} else {
for t := fn.targets; t != nil && block == nil; t = t.tail {
block = t._break
}
}
case token.CONTINUE:
if s.Label != nil {
block = fn.labelledBlock(s.Label)._continue
} else {
for t := fn.targets; t != nil && block == nil; t = t.tail {
block = t._continue
}
}
case token.FALLTHROUGH:
for t := fn.targets; t != nil && block == nil; t = t.tail {
block = t._fallthrough
}
case token.GOTO:
block = fn.labelledBlock(s.Label)._goto
}
if block == nil {
// TODO(gri): fix: catch these in the typechecker.
fmt.Printf("ignoring illegal branch: %s %s\n", s.Tok, s.Label)
} else {
emitJump(fn, block)
fn.currentBlock = fn.newBasicBlock("unreachable")
}
case *ast.BlockStmt:
b.stmtList(fn, s.List)
case *ast.IfStmt:
if s.Init != nil {
b.stmt(fn, s.Init)
}
then := fn.newBasicBlock("if.then")
done := fn.newBasicBlock("if.done")
els := done
if s.Else != nil {
els = fn.newBasicBlock("if.else")
}
b.cond(fn, s.Cond, then, els)
fn.currentBlock = then
b.stmt(fn, s.Body)
emitJump(fn, done)
if s.Else != nil {
fn.currentBlock = els
b.stmt(fn, s.Else)
emitJump(fn, done)
}
fn.currentBlock = done
case *ast.SwitchStmt:
b.switchStmt(fn, s, label)
case *ast.TypeSwitchStmt:
b.typeSwitchStmt(fn, s, label)
case *ast.SelectStmt:
b.selectStmt(fn, s, label)
case *ast.ForStmt:
b.forStmt(fn, s, label)
case *ast.RangeStmt:
b.rangeStmt(fn, s, label)
default:
panic(fmt.Sprintf("unexpected statement kind: %T", s))
}
}
// buildFunction builds SSA code for the body of function fn. Idempotent.
func (b *Builder) buildFunction(fn *Function) {
if fn.Blocks != nil {
return // building already started
}
if fn.syntax == nil {
return // not a Go source function
}
if fn.syntax.body == nil {
return // Go source function with no body (external)
}
fn.start(b.idents)
b.stmt(fn, fn.syntax.body)
if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) {
// We fell off the end: an implicit no-arg return statement.
fn.emit(new(Ret))
}
fn.finish()
}
// memberFromObject populates package pkg with a member for the
// typechecker object obj.
//
// For objects from Go source code, syntax is the associated syntax
// tree (for funcs and vars only); it will be used during the build
// phase.
//
func (b *Builder) memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
name := obj.GetName()
switch obj := obj.(type) {
case *types.TypeName:
pkg.Members[name] = &Type{NamedType: obj.Type.(*types.NamedType)}
case *types.Const:
pkg.Members[name] = newLiteral(obj.Val, obj.Type)
case *types.Var:
spec, _ := syntax.(*ast.ValueSpec)
g := &Global{
Pkg: pkg,
Name_: name,
Type_: pointer(obj.Type), // address
spec: spec,
}
b.globals[obj] = g
pkg.Members[name] = g
case *types.Func:
var fs *funcSyntax
var pos token.Pos
if decl, ok := syntax.(*ast.FuncDecl); ok {
fs = &funcSyntax{
recvField: decl.Recv,
paramFields: decl.Type.Params,
resultFields: decl.Type.Results,
body: decl.Body,
}
// TODO(gri): make GcImported types.Object
// implement the full object interface
// including Pos(). Or at least not crash.
pos = obj.GetPos()
}
sig := obj.Type.(*types.Signature)
fn := &Function{
Name_: name,
Signature: sig,
Pos: pos,
Pkg: pkg,
Prog: b.Prog,
syntax: fs,
}
if sig.Recv == nil {
// Function declaration.
b.globals[obj] = fn
pkg.Members[name] = fn
} else {
// Method declaration.
nt := deref(sig.Recv.Type).(*types.NamedType)
_, method := methodIndex(nt, nt.Methods, makeId(name, pkg.Types))
b.Prog.concreteMethods[method] = fn
}
default: // (incl. *types.Package)
panic(fmt.Sprintf("unexpected Object type: %T", obj))
}
}
// membersFromDecl populates package pkg with members for each
// typechecker object (var, func, const or type) associated with the
// specified decl.
//
func (b *Builder) membersFromDecl(pkg *Package, decl ast.Decl) {
switch decl := decl.(type) {
case *ast.GenDecl: // import, const, type or var
switch decl.Tok {
case token.CONST:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
if !isBlankIdent(id) {
b.memberFromObject(pkg, b.obj(id), nil)
}
}
}
case token.VAR:
for _, spec := range decl.Specs {
for _, id := range spec.(*ast.ValueSpec).Names {
if !isBlankIdent(id) {
b.memberFromObject(pkg, b.obj(id), spec)
}
}
}
case token.TYPE:
for _, spec := range decl.Specs {
id := spec.(*ast.TypeSpec).Name
if !isBlankIdent(id) {
b.memberFromObject(pkg, b.obj(id), nil)
}
}
}
case *ast.FuncDecl:
id := decl.Name
if decl.Recv == nil && id.Name == "init" {
return // init blocks aren't functions
}
if !isBlankIdent(id) {
b.memberFromObject(pkg, b.obj(id), decl)
}
}
}
// CreatePackage creates a package from the specified set of files,
// performs type-checking, and allocates all global SSA Values for the
// package. It returns a new SSA Package providing access to these
// values.
//
// importPath is the full name under which this package is known, such
// as appears in an import declaration. e.g. "sync/atomic".
//
// The ParseFiles() utility may be helpful for parsing a set of Go
// source files.
//
func (b *Builder) CreatePackage(importPath string, files []*ast.File) (*Package, error) {
typkg, firstErr := b.typechecker.Check(b.Prog.Files, files)
if firstErr != nil {
return nil, firstErr
}
return b.createPackageImpl(typkg, importPath, files), nil
}
// createPackageImpl constructs an SSA Package from an error-free
// types.Package typkg and populates its Members mapping. It returns
// the newly constructed ssa.Package.
//
// The real work of building SSA form for each function is not done
// until a subsequent call to BuildPackage.
//
// If files is non-nil, its declarations will be used to generate code
// for functions, methods and init blocks in a subsequent call to
// BuildPackage. Otherwise, typkg is assumed to have been imported
// from the gc compiler's object files; no code will be available.
//
func (b *Builder) createPackageImpl(typkg *types.Package, importPath string, files []*ast.File) *Package {
// TODO(gri): make this an invariant and eliminate importPath
// param and Package field.
// if importPath != p.Types.Path {
// panic(importPath + " != " + p.Types.Path)
// }
p := &Package{
Prog: b.Prog,
Types: typkg,
ImportPath: importPath,
Members: make(map[string]Member),
files: files,
}
b.packages[typkg] = p
b.Prog.Packages[importPath] = p
// CREATE phase.
// Allocate all package members: vars, funcs and consts and types.
if len(files) > 0 {
// Go source package.
p.Pos = files[0].Package // arbitrary file
// TODO(gri): make it a typechecker error for there to
// be duplicate (e.g.) main functions in the same package.
for _, file := range p.files {
// ast.Print(b.Prog.Files, file) // debugging
for _, decl := range file.Decls {
b.membersFromDecl(p, decl)
}
}
} else {
// GC-compiled binary package.
// No code.
// No position information.
for _, obj := range p.Types.Scope.Entries {
b.memberFromObject(p, obj, nil)
}
}
// Compute the method sets
for _, mem := range p.Members {
switch t := mem.(type) {
case *Type:
t.Methods = b.Prog.MethodSet(t.NamedType)
t.PtrMethods = b.Prog.MethodSet(pointer(t.NamedType))
}
}
// Add init() function (but not to Members since it can't be referenced).
p.Init = &Function{
Name_: "init",
Signature: new(types.Signature),
Pos: p.Pos,
Pkg: p,
Prog: b.Prog,
}
// Add initializer guard variable.
initguard := &Global{
Pkg: p,
Name_: "init·guard",
Type_: pointer(tBool),
}
p.Members[initguard.Name()] = initguard
if b.mode&LogPackages != 0 {
p.DumpTo(os.Stderr)
}
return p
}
// buildDecl builds SSA code for all globals, functions or methods
// declared by decl in package pkg.
//
func (b *Builder) buildDecl(pkg *Package, decl ast.Decl) {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
// Nothing to do for CONST, IMPORT.
case token.VAR:
for _, spec := range decl.Specs {
b.globalValueSpec(pkg.Init, spec.(*ast.ValueSpec), nil, nil)
}
case token.TYPE:
for _, spec := range decl.Specs {
id := spec.(*ast.TypeSpec).Name
if isBlankIdent(id) {
continue
}
obj := b.obj(id).(*types.TypeName)
for _, method := range obj.Type.(*types.NamedType).Methods {
b.buildFunction(b.Prog.concreteMethods[method])
}
}
}
case *ast.FuncDecl:
id := decl.Name
if isBlankIdent(id) {
// no-op
} else if decl.Recv == nil && id.Name == "init" {
// init() block
if b.mode&LogSource != 0 {
fmt.Fprintln(os.Stderr, "build init block @", b.Prog.Files.Position(decl.Pos()))
}
init := pkg.Init
// A return statement within an init block is
// treated like a "goto" to the the next init
// block, which we stuff in the outermost
// break label.
next := init.newBasicBlock("init.next")
init.targets = &targets{
tail: init.targets,
_break: next,
}
b.stmt(init, decl.Body)
emitJump(init, next)
init.targets = init.targets.tail
init.currentBlock = next
} else if m, ok := b.globals[b.obj(id)]; ok {
// Package-level function.
b.buildFunction(m.(*Function))
}
}
}
// BuildPackage builds SSA code for all functions and vars in package p.
//
// BuildPackage is idempotent.
//
func (b *Builder) BuildPackage(p *Package) {
if p.files == nil {
return // already done (or nothing to do)
}
if b.mode&LogSource != 0 {
fmt.Fprintln(os.Stderr, "build package", p.ImportPath)
}
init := p.Init
init.start(b.idents)
// Make init() skip if package is already initialized.
initguard := p.Var("init·guard")
doinit := init.newBasicBlock("init.start")
done := init.newBasicBlock("init.done")
emitIf(init, emitLoad(init, initguard), done, doinit)
init.currentBlock = doinit
emitStore(init, initguard, vTrue)
// TODO(gri): fix: the types.Package.Imports map may contains
// entries for other package's import statements, if produced
// by GcImport. Project it down to just the ones for us.
imports := make(map[string]*types.Package)
for _, file := range p.files {
for _, imp := range file.Imports {
path, _ := strconv.Unquote(imp.Path.Value)
if path != "unsafe" {
imports[path] = p.Types.Imports[path]
}
}
}
// Call the init() function of each package we import.
// Order is unspecified (and is in fact nondeterministic).
for name, imported := range imports {
p2 := b.packages[imported]
if p2 == nil {
panic("Building " + p.Name() + ": CreatePackage has not been called for package " + name)
}
// TODO(adonovan): opt: BuildPackage should be
// package-local, so we can run it for all packages in
// parallel once CreatePackage has been called for all
// prerequisites. Until then, ensure all import
// dependencies are completely built before we are.
b.BuildPackage(p2)
var v Call
v.Func = p2.Init
v.Pos = init.Pos
v.setType(new(types.Result))
init.emit(&v)
}
// Visit the package's var decls and init funcs in source
// order. This causes init() code to be generated in
// topological order. We visit them transitively through
// functions of the same package, but we don't treat functions
// as roots. TODO(adonovan): fix: don't visit through other
// packages.
//
// We also ensure all functions and methods are built, even if
// they are unreachable.
//
// The order between files is unspecified (and is in fact
// nondeterministic).
//
// TODO(adonovan): the partial order of initialization is
// underspecified. Discuss this with gri.
for _, file := range p.files {
for _, decl := range file.Decls {
b.buildDecl(p, decl)
}
}
p.files = nil
// Finish up.
emitJump(init, done)
init.currentBlock = done
init.emit(new(Ret))
init.finish()
}
package ssa
// Helpers for emitting SSA instructions.
import (
"go/token"
"go/types"
)
// emitNew emits to f a new (heap Alloc) instruction allocating an
// object of type typ.
//
func emitNew(f *Function, typ types.Type) Value {
return f.emit(&Alloc{
Type_: pointer(typ),
Heap: true,
})
}
// emitLoad emits to f an instruction to load the address addr into a
// new temporary, and returns the value so defined.
//
func emitLoad(f *Function, addr Value) Value {
v := &UnOp{Op: token.MUL, X: addr}
v.setType(indirectType(addr.Type()))
return f.emit(v)
}
// 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
// non-eager operations.)
//
func emitArith(f *Function, op token.Token, x, y Value, t types.Type) Value {
switch op {
case token.SHL, token.SHR:
// TODO(adonovan): fix: is this correct?
x = emitConv(f, x, t)
y = emitConv(f, y, types.Typ[types.Uint64])
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
x = emitConv(f, x, t)
y = emitConv(f, y, t)
default:
panic("illegal op in emitArith: " + op.String())
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(t)
return f.emit(v)
}
// emitCompare emits to f code compute the boolean result of
// comparison comparison 'x op y'.
//
func emitCompare(f *Function, op token.Token, x, y Value) Value {
// TODO(adonovan): fix: this is incomplete.
xt := underlyingType(x.Type())
yt := underlyingType(y.Type())
// Special case to optimise a tagless SwitchStmt so that
// these are equivalent
// switch { case e: ...}
// switch true { case e: ... }
// if e==true { ... }
// even in the case when e's type is an interface.
// TODO(adonovan): generalise to x==true, false!=y, etc.
if x == vTrue && op == token.EQL {
if yt, ok := yt.(*types.Basic); ok && yt.Info&types.IsBoolean != 0 {
return y
}
}
if types.IsIdentical(xt, yt) {
// no conversion necessary
} else if _, ok := xt.(*types.Interface); ok {
y = emitConv(f, y, x.Type())
} else if _, ok := yt.(*types.Interface); ok {
x = emitConv(f, x, y.Type())
} else if _, ok := x.(*Literal); ok {
x = emitConv(f, x, y.Type())
} else if _, ok := y.(*Literal); ok {
y = emitConv(f, y, x.Type())
} else {
// other cases, e.g. channels. No-op.
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(tBool)
return f.emit(v)
}
// emitConv emits to f code to convert Value val to exactly type typ,
// and returns the converted value. Implicit conversions are implied
// by language assignability rules in the following operations:
//
// - from rvalue type to lvalue type in assignments.
// - from actual- to formal-parameter types in function calls.
// - from return value type to result type in return statements.
// - population of struct fields, array and slice elements, and map
// keys and values within compoisite literals
// - from index value to index type in indexing expressions.
// - for both arguments of comparisons.
// - from value type to channel type in send expressions.
//
func emitConv(f *Function, val Value, typ types.Type) Value {
// fmt.Printf("emitConv %s -> %s, %T", val.Type(), typ, val) // debugging
// Identical types? Conversion is a no-op.
if types.IsIdentical(val.Type(), typ) {
return val
}
ut_dst := underlyingType(typ)
ut_src := underlyingType(val.Type())
// Identical underlying types? Conversion is a name change.
if types.IsIdentical(ut_dst, ut_src) {
// TODO(adonovan): make this use a distinct
// instruction, ChangeType. This instruction must
// also cover the cases of channel type restrictions and
// conversions between pointers to identical base
// types.
c := &Conv{X: val}
c.setType(typ)
return f.emit(c)
}
// Conversion to, or construction of a value of, an interface type?
if _, ok := ut_dst.(*types.Interface); ok {
// Assignment from one interface type to a different one?
if _, ok := ut_src.(*types.Interface); ok {
c := &ChangeInterface{X: val}
c.setType(typ)
return f.emit(c)
}
// Untyped nil literal? Return interface-typed nil literal.
if ut_src == tUntypedNil {
return nilLiteral(typ)
}
// Convert (non-nil) "untyped" literals to their default type.
// TODO(gri): expose types.isUntyped().
if t, ok := ut_src.(*types.Basic); ok && t.Info&types.IsUntyped != 0 {
val = emitConv(f, val, DefaultType(ut_src))
}
mi := &MakeInterface{
X: val,
Methods: f.Prog.MethodSet(val.Type()),
}
mi.setType(typ)
return f.emit(mi)
}
// Conversion of a literal to a non-interface type results in
// a new literal of the destination type and (initially) the
// same abstract value. We don't compute the representation
// change yet; this defers the point at which the number of
// possible representations explodes.
if l, ok := val.(*Literal); ok {
return newLiteral(l.Value, typ)
}
// A representation-changing conversion.
c := &Conv{X: val}
c.setType(typ)
return f.emit(c)
}
// emitStore emits to f an instruction to store value val at location
// addr, applying implicit conversions as required by assignabilty rules.
//
func emitStore(f *Function, addr, val Value) {
f.emit(&Store{
Addr: addr,
Val: emitConv(f, val, indirectType(addr.Type())),
})
}
// emitJump emits to f a jump to target, and updates the control-flow graph.
// Postcondition: f.currentBlock is nil.
//
func emitJump(f *Function, target *BasicBlock) {
b := f.currentBlock
b.emit(new(Jump))
addEdge(b, target)
f.currentBlock = nil
}
// emitIf emits to f a conditional jump to tblock or fblock based on
// cond, and updates the control-flow graph.
// Postcondition: f.currentBlock is nil.
//
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock) {
b := f.currentBlock
b.emit(&If{Cond: cond})
addEdge(b, tblock)
addEdge(b, fblock)
f.currentBlock = nil
}
// emitExtract emits to f an instruction to extract the index'th
// component of tuple, ascribing it type typ. It returns the
// extracted value.
//
func emitExtract(f *Function, tuple Value, index int, typ types.Type) Value {
e := &Extract{Tuple: tuple, Index: index}
// In all cases but one (tSelect's recv), typ is redundant w.r.t.
// tuple.Type().(*types.Result).Values[index].Type.
e.setType(typ)
return f.emit(e)
}
// emitTailCall emits to f a function call in tail position.
// Postcondition: f.currentBlock is nil.
//
func emitTailCall(f *Function, call *Call) {
tuple := f.emit(call)
var ret Ret
switch {
case len(f.Signature.Results) > 1:
for i, o := range call.Type().(*types.Result).Values {
v := emitExtract(f, tuple, i, o.Type)
// TODO(adonovan): in principle, this is required:
// v = emitConv(f, o.Type, f.Signature.Results[i].Type)
// but in practice emitTailCall is only used when
// the types exactly match.
ret.Results = append(ret.Results, v)
}
case len(f.Signature.Results) == 1:
ret.Results = []Value{tuple}
default:
// no-op
}
f.emit(&ret)
f.currentBlock = nil
}
// emitSelfLoop emits to f a self-loop.
// This is a defensive measure to ensure control-flow integrity.
// It should never be reachable.
// Postcondition: f.currentBlock is nil.
//
func emitSelfLoop(f *Function) {
loop := f.newBasicBlock("selfloop")
emitJump(f, loop)
f.currentBlock = loop
emitJump(f, loop)
}
......@@ -10,18 +10,6 @@ import (
"os"
)
// Mode bits for additional diagnostics and checking.
// TODO(adonovan): move these to builder.go once submitted.
type BuilderMode uint
const (
LogPackages BuilderMode = 1 << iota // Dump package inventory to stderr
LogFunctions // Dump function SSA code to stderr
LogSource // Show source locations as SSA builder progresses
SanityCheckFunctions // Perform sanity checking of function bodies
UseGCImporter // Ignore SourceLoader; use gc-compiled object code for all imports
)
// addEdge adds a control-flow graph edge from from to to.
func addEdge(from, to *BasicBlock) {
from.Succs = append(from.Succs, to)
......@@ -182,8 +170,8 @@ func (f *Function) addSpilledParam(obj types.Object) {
// Otherwise, idents is ignored and the usual set-up for Go source
// functions is skipped.
//
func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
if mode&LogSource != 0 {
func (f *Function) start(idents map[*ast.Ident]types.Object) {
if f.Prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build function %s @ %s\n", f.FullName(), f.Prog.Files.Position(f.Pos))
}
f.currentBlock = f.newBasicBlock("entry")
......@@ -226,7 +214,7 @@ func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
}
// finish() finalizes the function after SSA code generation of its body.
func (f *Function) finish(mode BuilderMode) {
func (f *Function) finish() {
f.objects = nil
f.results = nil
f.currentBlock = nil
......@@ -269,13 +257,13 @@ func (f *Function) finish(mode BuilderMode) {
}
optimizeBlocks(f)
if mode&LogFunctions != 0 {
if f.Prog.mode&LogFunctions != 0 {
f.DumpTo(os.Stderr)
}
if mode&SanityCheckFunctions != 0 {
if f.Prog.mode&SanityCheckFunctions != 0 {
MustSanityCheck(f, nil)
}
if mode&LogSource != 0 {
if f.Prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build function %s done\n", f.FullName())
}
}
......@@ -345,6 +333,46 @@ func (f *Function) emit(instr Instruction) Value {
return f.currentBlock.emit(instr)
}
// FullName returns the full name of this function, qualified by
// package name, receiver type, etc.
//
// Examples:
// "math.IsNaN" // a package-level function
// "(*sync.WaitGroup).Add" // a declared method
// "(*exp/ssa.Ret).Block" // a bridge method
// "(ssa.Instruction).Block" // an interface method thunk
// "func@5.32" // an anonymous function
//
func (f *Function) FullName() string {
// Anonymous?
if f.Enclosing != nil {
return f.Name_
}
recv := f.Signature.Recv
// Synthetic?
if f.Pkg == nil {
if recv != nil {
// TODO(adonovan): print type package-qualified, if NamedType.
return fmt.Sprintf("(%s).%s", recv.Type, f.Name_) // bridge method
}
return fmt.Sprintf("(%s).%s", f.Params[0].Type(), f.Name_) // interface method thunk
}
// Declared method?
if recv != nil {
star := ""
if isPointer(recv.Type) {
star = "*"
}
return fmt.Sprintf("(%s%s.%s).%s", star, f.Pkg.ImportPath, deref(recv.Type), f.Name_)
}
// Package-level function.
return fmt.Sprintf("%s.%s", f.Pkg.ImportPath, f.Name_)
}
// DumpTo prints to w a human readable "disassembly" of the SSA code of
// all basic blocks of function f.
//
......@@ -364,18 +392,21 @@ func (f *Function) DumpTo(w io.Writer) {
}
}
io.WriteString(w, "func ")
params := f.Params
if f.Signature.Recv != nil {
fmt.Fprintf(w, "func (%s) %s(", params[0].Name(), f.Name())
fmt.Fprintf(w, "(%s %s) ", params[0].Name(), params[0].Type())
params = params[1:]
} else {
fmt.Fprintf(w, "func %s(", f.Name())
}
io.WriteString(w, f.Name())
io.WriteString(w, "(")
for i, v := range params {
if i > 0 {
io.WriteString(w, ", ")
}
io.WriteString(w, v.Name())
io.WriteString(w, " ")
io.WriteString(w, v.Type().String())
}
io.WriteString(w, "):\n")
......
package ssa
// This file defines an implementation of the types.Importer interface
// (func) that loads the transitive closure of dependencies of a
// "main" package.
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"os"
"path/filepath"
)
// Prototype of a function that locates, reads and parses a set of
// source files given an import path.
//
// fset is the fileset to which the ASTs should be added.
// path is the imported path, e.g. "sync/atomic".
//
// On success, the function returns files, the set of ASTs produced,
// or the first error encountered.
//
type SourceLoader func(fset *token.FileSet, path string) (files []*ast.File, err error)
// doImport loads the typechecker package identified by path
// Implements the types.Importer prototype.
//
func (b *Builder) doImport(imports map[string]*types.Package, path string) (typkg *types.Package, err error) {
// Package unsafe is handled specially, and has no ssa.Package.
if path == "unsafe" {
return types.Unsafe, nil
}
if pkg := b.Prog.Packages[path]; pkg != nil {
typkg = pkg.Types
imports[path] = typkg
return // positive cache hit
}
if err = b.importErrs[path]; err != nil {
return // negative cache hit
}
var files []*ast.File
if b.mode&UseGCImporter != 0 {
typkg, err = types.GcImport(imports, path)
} else {
files, err = b.loader(b.Prog.Files, path)
if err == nil {
typkg, err = b.typechecker.Check(b.Prog.Files, files)
}
}
if err != nil {
// Cache failure
b.importErrs[path] = err
return nil, err
}
// Cache success
imports[path] = typkg // cache for just this package.
b.Prog.Packages[path] = b.createPackageImpl(typkg, path, files) // cache across all packages
return typkg, nil
}
// GorootLoader is an implementation of the SourceLoader function
// prototype that loads and parses Go source files from the package
// directory beneath $GOROOT/src/pkg.
//
// TODO(adonovan): get rsc and adg (go/build owners) to review this.
//
func GorootLoader(fset *token.FileSet, path string) (files []*ast.File, err error) {
// TODO(adonovan): fix: Do we need cwd? Shouldn't ImportDir(path) / $GOROOT suffice?
srcDir, err := os.Getwd()
if err != nil {
return // serious misconfiguration
}
bp, err := build.Import(path, srcDir, 0)
if err != nil {
return // import failed
}
files, err = ParseFiles(fset, bp.Dir, bp.GoFiles...)
if err != nil {
return nil, err
}
return
}
// ParseFiles parses the Go source files files within directory dir
// and returns their ASTs, or the first parse error if any.
//
// This utility function is provided to facilitate implementing a
// SourceLoader.
//
func ParseFiles(fset *token.FileSet, dir string, files ...string) (parsed []*ast.File, err error) {
for _, file := range files {
var f *ast.File
if !filepath.IsAbs(file) {
file = filepath.Join(dir, file)
}
f, err = parser.ParseFile(fset, file, nil, parser.DeclarationErrors)
if err != nil {
return // parsing failed
}
parsed = append(parsed, f)
}
return
}
package ssa
// lvalues are the union of addressable expressions and map-index
// expressions.
import (
"go/types"
)
// An lvalue represents an assignable location that may appear on the
// left-hand side of an assignment. This is a generalization of a
// pointer to permit updates to elements of maps.
//
type lvalue interface {
store(fn *Function, v Value) // stores v into the location
load(fn *Function) Value // loads the contents 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
}
func (a address) load(fn *Function) Value {
return emitLoad(fn, a.addr)
}
func (a address) store(fn *Function, v Value) {
emitStore(fn, a.addr, v)
}
func (a address) typ() types.Type {
return indirectType(a.addr.Type())
}
// An element is an lvalue represented by m[k], the location of an
// element of a map or string. These locations are not addressable
// since pointers cannot be formed from them, but they do support
// load(), and in the case of maps, store().
//
type element struct {
m, k Value // map or string
t types.Type // map element type or string byte type
}
func (e *element) load(fn *Function) Value {
l := &Lookup{
X: e.m,
Index: e.k,
}
l.setType(e.t)
return fn.emit(l)
}
func (e *element) store(fn *Function, v Value) {
fn.emit(&MapUpdate{
Map: e.m,
Key: e.k,
Value: emitConv(fn, v, e.t),
})
}
func (e *element) typ() types.Type {
return e.t
}
// A blanks is a dummy variable whose name is "_".
// It is not reified: loads are illegal and stores are ignored.
//
type blank struct{}
func (bl blank) load(fn *Function) Value {
panic("blank.load is illegal")
}
func (bl blank) store(fn *Function, v Value) {
// no-op
}
func (bl blank) typ() types.Type {
// TODO(adonovan): this should be the type of the blank Ident;
// the typechecker doesn't provide this yet, but fortunately,
// we don't need it yet either.
panic("blank.typ is unimplemented")
}
......@@ -8,6 +8,8 @@ import (
"fmt"
"go/ast"
"go/types"
"io"
"sort"
)
func (id Id) String() string {
......@@ -67,18 +69,6 @@ func (r *Function) String() string {
return fmt.Sprintf("function %s : %s", r.Name(), r.Type())
}
// FullName returns the name of this function qualified by the
// package name, unless it is anonymous or synthetic.
//
// TODO(adonovan): move to func.go when it's submitted.
//
func (f *Function) FullName() string {
if f.Enclosing != nil || f.Pkg == nil {
return f.Name_ // anonymous or synthetic
}
return fmt.Sprintf("%s.%s", f.Pkg.ImportPath, f.Name_)
}
// FullName returns g's package-qualified name.
func (g *Global) FullName() string {
return fmt.Sprintf("%s.%s", g.Pkg.ImportPath, g.Name_)
......@@ -340,39 +330,51 @@ func (s *MapUpdate) String() string {
}
func (p *Package) String() string {
// TODO(adonovan): prettify output.
var b bytes.Buffer
fmt.Fprintf(&b, "Package %s at %s:\n", p.ImportPath, p.Prog.Files.File(p.Pos).Name())
return "Package " + p.ImportPath
}
func (p *Package) DumpTo(w io.Writer) {
fmt.Fprintf(w, "Package %s at %s:\n", p.ImportPath, p.Prog.Files.File(p.Pos).Name())
// TODO(adonovan): make order deterministic.
var names []string
maxname := 0
for name := range p.Members {
if l := len(name); l > maxname {
maxname = l
}
names = append(names, name)
}
for name, mem := range p.Members {
switch mem := mem.(type) {
sort.Strings(names)
for _, name := range names {
switch mem := p.Members[name].(type) {
case *Literal:
fmt.Fprintf(&b, " const %-*s %s\n", maxname, name, mem.Name())
fmt.Fprintf(w, " const %-*s %s\n", maxname, name, mem.Name())
case *Function:
fmt.Fprintf(&b, " func %-*s %s\n", maxname, name, mem.Type())
fmt.Fprintf(w, " func %-*s %s\n", maxname, name, mem.Type())
case *Type:
fmt.Fprintf(&b, " type %-*s %s\n", maxname, name, mem.NamedType.Underlying)
// TODO(adonovan): make order deterministic.
for name, method := range mem.Methods {
fmt.Fprintf(&b, " method %s %s\n", name, method.Signature)
fmt.Fprintf(w, " type %-*s %s\n", maxname, name, mem.NamedType.Underlying)
// We display only PtrMethods since its keys
// are a superset of Methods' keys, though the
// methods themselves may differ,
// e.g. different bridge methods.
var keys ids
for id := range mem.PtrMethods {
keys = append(keys, id)
}
sort.Sort(keys)
for _, id := range keys {
method := mem.PtrMethods[id]
fmt.Fprintf(w, " method %s %s\n", id, method.Signature)
}
case *Global:
fmt.Fprintf(&b, " var %-*s %s\n", maxname, name, mem.Type())
fmt.Fprintf(w, " var %-*s %s\n", maxname, name, mem.Type())
}
}
return b.String()
}
func commaOk(x bool) string {
......
package ssa
// This file defines algorithms related to "promotion" of field and
// method selector expressions e.x, such as desugaring implicit field
// and method selections, method-set computation, and construction of
// synthetic "bridge" methods.
import (
"fmt"
"go/types"
"os"
)
// anonFieldPath is a linked list of anonymous fields entered by
// breadth-first traversal has entered, rightmost (outermost) first.
// e.g. "e.f" denoting "e.A.B.C.f" would have a path [C, B, A].
// Common tails may be shared.
//
// It is used by various "promotion"-related algorithms.
//
type anonFieldPath struct {
tail *anonFieldPath
index int // index of field within enclosing types.Struct.Fields
field *types.Field
}
func (p *anonFieldPath) contains(f *types.Field) bool {
for ; p != nil; p = p.tail {
if p.field == f {
return true
}
}
return false
}
// reverse returns the linked list reversed, as a slice.
func (p *anonFieldPath) reverse() []*anonFieldPath {
n := 0
for q := p; q != nil; q = q.tail {
n++
}
s := make([]*anonFieldPath, n)
n = 0
for ; p != nil; p = p.tail {
s[len(s)-1-n] = p
n++
}
return s
}
// isIndirect returns true if the path indirects a pointer.
func (p *anonFieldPath) isIndirect() bool {
for ; p != nil; p = p.tail {
if isPointer(p.field.Type) {
return true
}
}
return false
}
// Method Set construction ----------------------------------------
// A candidate is a method eligible for promotion: a method of an
// abstract (interface) or concrete (anonymous struct or named) type,
// along with the anonymous field path via which it is implicitly
// reached. If there is exactly one candidate for a given id, it will
// be promoted to membership of the original type's method-set.
//
// Candidates with path=nil are trivially members of the original
// type's method-set.
//
type candidate struct {
method *types.Method // method object of abstract or concrete type
concrete *Function // actual method (iff concrete)
path *anonFieldPath // desugared selector path
}
// For debugging.
func (c candidate) String() string {
s := ""
// Inefficient!
for p := c.path; p != nil; p = p.tail {
s = "." + p.field.Name + s
}
return "@" + s + "." + c.method.Name
}
// ptrRecv returns true if this candidate has a pointer receiver.
func (c candidate) ptrRecv() bool {
return c.concrete != nil && isPointer(c.concrete.Signature.Recv.Type)
}
// MethodSet returns the method set for type typ,
// building bridge methods as needed for promoted methods.
// A nil result indicates an empty set.
//
// Thread-safe. TODO(adonovan): explain concurrency invariants in detail.
func (p *Program) MethodSet(typ types.Type) MethodSet {
if !canHaveConcreteMethods(typ, true) {
return nil
}
p.methodSetsMu.Lock()
defer p.methodSetsMu.Unlock()
// TODO(adonovan): Using Types as map keys doesn't properly
// de-dup. e.g. *NamedType are canonical but *Struct and
// others are not. Need to de-dup based on using a two-level
// hash-table with hash function types.Type.String and
// equivalence relation types.IsIdentical.
mset := p.methodSets[typ]
if mset == nil {
mset = buildMethodSet(p, typ)
p.methodSets[typ] = mset
}
return mset
}
// buildMethodSet computes the concrete method set for type typ.
// It is the implementation of Program.MethodSet.
//
func buildMethodSet(prog *Program, typ types.Type) MethodSet {
if prog.mode&LogSource != 0 {
// TODO(adonovan): this isn't quite appropriate for LogSource
fmt.Fprintf(os.Stderr, "buildMethodSet %s %T\n", typ, typ)
}
// cands maps ids (field and method names) encountered at any
// level of of the breadth-first traversal to a unique
// promotion candidate. A nil value indicates a "blocked" id
// (i.e. a field or ambiguous method).
//
// nextcands is the same but carries just the level in progress.
cands, nextcands := make(map[Id]*candidate), make(map[Id]*candidate)
var next, list []*anonFieldPath
list = append(list, nil) // hack: nil means "use typ"
// For each level of the type graph...
for len(list) > 0 {
// Invariant: next=[], nextcands={}.
// Collect selectors from one level into 'nextcands'.
// Record the next levels into 'next'.
for _, node := range list {
t := typ // first time only
if node != nil {
t = node.field.Type
}
t = deref(t)
if nt, ok := t.(*types.NamedType); ok {
for _, meth := range nt.Methods {
addCandidate(nextcands, IdFromQualifiedName(meth.QualifiedName), meth, prog.concreteMethods[meth], node)
}
t = nt.Underlying
}
switch t := t.(type) {
case *types.Interface:
for _, meth := range t.Methods {
addCandidate(nextcands, IdFromQualifiedName(meth.QualifiedName), meth, nil, node)
}
case *types.Struct:
for i, f := range t.Fields {
nextcands[IdFromQualifiedName(f.QualifiedName)] = nil // a field: block id
// Queue up anonymous fields for next iteration.
// Break cycles to ensure termination.
if f.IsAnonymous && !node.contains(f) {
next = append(next, &anonFieldPath{node, i, f})
}
}
}
}
// Examine collected selectors.
// Promote unique, non-blocked ones to cands.
for id, cand := range nextcands {
delete(nextcands, id)
if cand == nil {
// Update cands so we ignore it at all deeper levels.
// Don't clobber existing (shallower) binding!
if _, ok := cands[id]; !ok {
cands[id] = nil // block id
}
continue
}
if _, ok := cands[id]; ok {
// Ignore candidate: a shallower binding exists.
} else {
cands[id] = cand
}
}
list, next = next, list[:0] // reuse array
}
// Build method sets and bridge methods.
mset := make(MethodSet)
for id, cand := range cands {
if cand == nil {
continue // blocked; ignore
}
if cand.ptrRecv() && !(isPointer(typ) || cand.path.isIndirect()) {
// A candidate concrete method f with receiver
// *C is promoted into the method set of
// (non-pointer) E iff the implicit path selection
// is indirect, e.g. e.A->B.C.f
continue
}
var method *Function
if cand.path == nil {
// Trivial member of method-set; no bridge needed.
method = cand.concrete
} else {
method = makeBridgeMethod(prog, typ, cand)
}
if method == nil {
panic("unexpected nil method in method set")
}
mset[id] = method
}
return mset
}
// addCandidate adds the promotion candidate (method, node) to m[id].
// If m[id] already exists (whether nil or not), m[id] is set to nil.
// If method denotes a concrete method, concrete is its implementation.
//
func addCandidate(m map[Id]*candidate, id Id, method *types.Method, concrete *Function, node *anonFieldPath) {
prev, found := m[id]
switch {
case prev != nil:
// Two candidates for same selector: ambiguous; block it.
m[id] = nil
case found:
// Already blocked.
default:
// A viable candidate.
m[id] = &candidate{method, concrete, node}
}
}
// makeBridgeMethod creates a synthetic Function that delegates to a
// "promoted" method. For example, given these decls:
//
// type A struct {B}
// type B struct {*C}
// type C ...
// func (*C) f()
//
// then makeBridgeMethod(typ=A, cand={method:(*C).f, path:[B,*C]}) will
// synthesize this bridge method:
//
// func (a A) f() { return a.B.C->f() }
//
// prog is the program to which the synthesized method will belong.
// typ is the receiver type of the bridge method. cand is the
// candidate method to be promoted; it may be concrete or an interface
// method.
//
func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function {
sig := *cand.method.Type // make a copy, sharing underlying Values
sig.Recv = &types.Var{Name: "recv", Type: typ}
if prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "makeBridgeMethod %s, %s, type %s\n", typ, cand, &sig)
}
fn := &Function{
Name_: cand.method.Name,
Signature: &sig,
Prog: prog,
}
fn.start(nil)
fn.addSpilledParam(sig.Recv)
// TODO(adonovan): fix: test variadic case---careful with types.
for _, p := range fn.Signature.Params {
fn.addParam(p.Name, p.Type)
}
// Each bridge method performs a sequence of selections,
// then tailcalls the promoted method.
// We use pointer arithmetic (FieldAddr possibly followed by
// Load) in preference to value extraction (Field possibly
// preceded by Load).
var v Value = fn.Locals[0] // spilled receiver
if isPointer(typ) {
v = emitLoad(fn, v)
}
// Iterate over selections e.A.B.C.f in the natural order [A,B,C].
for _, p := range cand.path.reverse() {
// Loop invariant: v holds a pointer to a struct.
if _, ok := underlyingType(indirectType(v.Type())).(*types.Struct); !ok {
panic(fmt.Sprint("not a *struct: ", v.Type(), p.field.Type))
}
sel := &FieldAddr{
X: v,
Field: p.index,
}
sel.setType(pointer(p.field.Type))
v = fn.emit(sel)
if isPointer(p.field.Type) {
v = emitLoad(fn, v)
}
}
if !cand.ptrRecv() {
v = emitLoad(fn, v)
}
var c Call
if cand.concrete != nil {
c.Func = cand.concrete
fn.Pos = c.Func.(*Function).Pos // TODO(adonovan): fix: wrong.
c.Pos = fn.Pos // TODO(adonovan): fix: wrong.
c.Args = append(c.Args, v)
for _, arg := range fn.Params[1:] {
c.Args = append(c.Args, arg)
}
} else {
c.Recv = v
c.Method = 0
for _, arg := range fn.Params {
c.Args = append(c.Args, arg)
}
}
c.Type_ = &types.Result{Values: sig.Results}
emitTailCall(fn, &c)
fn.finish()
return fn
}
// Thunks for standalone interface methods ----------------------------------------
// makeImethodThunk returns a synthetic thunk function permitting an
// method id of interface typ to be called like a standalone function,
// e.g.:
//
// type I interface { f(x int) R }
// m := I.f // thunk
// var i I
// m(i, 0)
//
// The thunk is defined as if by:
//
// func I.f(i I, x int, ...) R {
// return i.f(x, ...)
// }
//
// The generated thunks do not belong to any package. (Arguably they
// belong in the package that defines the interface, but we have no
// way to determine that on demand; we'd have to create all possible
// thunks a priori.)
//
// TODO(adonovan): opt: currently the stub is created even when used
// in call position: I.f(i, 0). Clearly this is suboptimal.
//
// TODO(adonovan): memoize creation of these functions in the Program.
//
func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function {
if prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "makeImethodThunk %s.%s\n", typ, id)
}
itf := underlyingType(typ).(*types.Interface)
index, meth := methodIndex(itf, itf.Methods, id)
sig := *meth.Type // copy; shared Values
fn := &Function{
Name_: meth.Name,
Signature: &sig,
Prog: prog,
}
// TODO(adonovan): set fn.Pos to location of interface method ast.Field.
fn.start(nil)
fn.addParam("recv", typ)
// TODO(adonovan): fix: test variadic case---careful with types.
for _, p := range fn.Signature.Params {
fn.addParam(p.Name, p.Type)
}
var c Call
c.Method = index
c.Recv = fn.Params[0]
for _, arg := range fn.Params[1:] {
c.Args = append(c.Args, arg)
}
c.Type_ = &types.Result{Values: sig.Results}
emitTailCall(fn, &c)
fn.finish()
return fn
}
// Implicit field promotion ----------------------------------------
// For a given struct type and (promoted) field Id, findEmbeddedField
// returns the path of implicit anonymous field selections, and the
// field index of the explicit (=outermost) selection.
//
// TODO(gri): if go/types/operand.go's lookupFieldBreadthFirst were to
// record (e.g. call a client-provided callback) the implicit field
// selection path discovered for a particular ast.SelectorExpr, we could
// eliminate this function.
//
func findPromotedField(st *types.Struct, id Id) (*anonFieldPath, int) {
// visited records the types that have been searched already.
// Invariant: keys are all *types.NamedType.
// (types.Type is not a sound map key in general.)
visited := make(map[types.Type]bool)
var list, next []*anonFieldPath
for i, f := range st.Fields {
if f.IsAnonymous {
list = append(next, &anonFieldPath{nil, i, f})
}
}
// Search the current level if there is any work to do and collect
// embedded types of the next lower level in the next list.
for {
// look for name in all types at this level
for _, node := range list {
typ := deref(node.field.Type).(*types.NamedType)
if visited[typ] {
continue
}
visited[typ] = true
switch typ := typ.Underlying.(type) {
case *types.Struct:
for i, f := range typ.Fields {
if IdFromQualifiedName(f.QualifiedName) == id {
return node, i
}
}
for i, f := range typ.Fields {
if f.IsAnonymous {
next = append(next, &anonFieldPath{node, i, f})
}
}
}
}
if len(next) == 0 {
panic("field not found: " + id.String())
}
// No match so far.
list, next = next, list[:0] // reuse arrays
}
panic("unreachable")
}
......@@ -8,6 +8,7 @@ import (
"go/ast"
"go/token"
"go/types"
"sync"
)
// A Program is a partial or complete Go program converted to SSA form.
......@@ -16,18 +17,17 @@ import (
//
// TODO(adonovan): synthetic methods for promoted methods and for
// standalone interface methods do not belong to any package. Make
// them enumerable here.
//
// TODO(adonovan): MethodSets of types other than named types
// (i.e. anon structs) are not currently accessible, nor are they
// memoized. Add a method: MethodSetForType() which looks in the
// appropriate Package (for methods of named types) or in
// Program.AnonStructMethods (for methods of anon structs).
// them enumerable here so clients can (e.g.) generate code for them.
//
type Program struct {
Files *token.FileSet // position information for the files of this Program
Packages map[string]*Package // all loaded Packages, keyed by import path
Builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
methodSets map[types.Type]MethodSet // concrete method sets for all needed types [TODO(adonovan): de-dup]
methodSetsMu sync.Mutex // serializes all accesses to methodSets
concreteMethods map[*types.Method]*Function // maps named concrete methods to their code
mode BuilderMode // set of mode bits
}
// A Package is a single analyzed Go package, containing Members for
......@@ -80,27 +80,18 @@ type Id struct {
Name string
}
// A MethodSet contains all the methods whose receiver is either T or
// *T, for some named or struct type T.
//
// TODO(adonovan): the client is required to adapt T<=>*T, e.g. when
// invoking an interface method. (This could be simplified for the
// client by having distinct method sets for T and *T, with the SSA
// Builder generating wrappers as needed, but probably the client is
// able to do a better job.) Document the precise rules the client
// must follow.
// A MethodSet contains all the methods for a particular type.
// The method sets for T and *T are distinct entities.
//
type MethodSet map[Id]*Function
// A Type is a Member of a Package representing the name, underlying
// type and method set of a named type declared at package scope.
//
// The method set contains only concrete methods; it is empty for
// interface types.
//
type Type struct {
NamedType *types.NamedType
Methods MethodSet
Methods MethodSet // concrete method set of N
PtrMethods MethodSet // concrete method set of (*N)
}
// An SSA value that can be referenced by an instruction.
......@@ -479,7 +470,7 @@ type ChangeInterface struct {
type MakeInterface struct {
Register
X Value
Methods MethodSet // method set of (non-interface) X iff converting to interface
Methods MethodSet // method set of (non-interface) X
}
// A MakeClosure instruction yields an anonymous function value whose
......
......@@ -6,6 +6,7 @@ import (
"fmt"
"go/ast"
"go/types"
"reflect"
)
func unreachable() {
......@@ -118,6 +119,26 @@ func objKind(obj types.Object) ast.ObjKind {
panic(fmt.Sprintf("unexpected Object type: %T", obj))
}
// canHaveConcreteMethods returns true iff typ may have concrete
// methods associated with it. Callers must supply allowPtr=true.
//
// TODO(gri): consider putting this in go/types. It's surprisingly subtle.
func canHaveConcreteMethods(typ types.Type, allowPtr bool) bool {
switch typ := typ.(type) {
case *types.Pointer:
return allowPtr && canHaveConcreteMethods(typ.Base, false)
case *types.NamedType:
switch typ.Underlying.(type) {
case *types.Pointer, *types.Interface:
return false
}
return true
case *types.Struct:
return true
}
return false
}
// DefaultType returns the default "typed" type for an "untyped" type;
// it returns the incoming type for all other types. If there is no
// corresponding untyped type, the result is types.Typ[types.Invalid].
......@@ -158,6 +179,10 @@ func makeId(name string, pkg *types.Package) (id Id) {
id.Name = name
if !ast.IsExported(name) {
id.Pkg = pkg
// TODO(gri): fix
// if pkg.Path == "" {
// panic("Package " + pkg.Name + "has empty Path")
// }
}
return
}
......@@ -170,3 +195,16 @@ func makeId(name string, pkg *types.Package) (id Id) {
func IdFromQualifiedName(qn types.QualifiedName) Id {
return makeId(qn.Name, qn.Pkg)
}
type ids []Id // a sortable slice of Id
func (p ids) Len() int { return len(p) }
func (p ids) Less(i, j int) bool {
x, y := p[i], p[j]
// *Package pointers are canonical so order by them.
// Don't use x.Pkg.ImportPath because sometimes it's empty.
// (TODO(gri): fix that.)
return reflect.ValueOf(x.Pkg).Pointer() < reflect.ValueOf(y.Pkg).Pointer() ||
x.Pkg == y.Pkg && x.Name < y.Name
}
func (p ids) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment