From: Alan Donovan Date: Mon, 4 Feb 2013 17:22:35 +0000 (-0500) Subject: exp/ssa: (#4 of 5): the SSA builder. X-Git-Tag: go1.1rc2~1147 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=c06a5335baf80308c0765700378826ff929db86d;p=gostls13.git exp/ssa: (#4 of 5): the SSA builder. R=iant, gri, iant, rogpeppe CC=golang-dev https://golang.org/cl/7196053 --- diff --git a/src/pkg/exp/ssa/builder.go b/src/pkg/exp/ssa/builder.go new file mode 100644 index 0000000000..d970654a9f --- /dev/null +++ b/src/pkg/exp/ssa/builder.go @@ -0,0 +1,2682 @@ +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 + // if ok1 then goto S1 else goto .caseT2 + // .caseT2: + // t2, ok2 := typeswitch,ok x + // if ok2 then goto S1 else goto .caseT3 + // .S1: + // ...S1... + // goto done + // .caseT3: + // t3, ok3 := typeswitch,ok x + // 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 + 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() +} diff --git a/src/pkg/exp/ssa/emit.go b/src/pkg/exp/ssa/emit.go new file mode 100644 index 0000000000..9a176b4f50 --- /dev/null +++ b/src/pkg/exp/ssa/emit.go @@ -0,0 +1,261 @@ +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) +} diff --git a/src/pkg/exp/ssa/func.go b/src/pkg/exp/ssa/func.go index 6af5e1efcd..507eb7c329 100644 --- a/src/pkg/exp/ssa/func.go +++ b/src/pkg/exp/ssa/func.go @@ -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") diff --git a/src/pkg/exp/ssa/importer.go b/src/pkg/exp/ssa/importer.go new file mode 100644 index 0000000000..ce73323d44 --- /dev/null +++ b/src/pkg/exp/ssa/importer.go @@ -0,0 +1,110 @@ +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 +} diff --git a/src/pkg/exp/ssa/lvalue.go b/src/pkg/exp/ssa/lvalue.go new file mode 100644 index 0000000000..9ca9f68e31 --- /dev/null +++ b/src/pkg/exp/ssa/lvalue.go @@ -0,0 +1,86 @@ +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") +} diff --git a/src/pkg/exp/ssa/print.go b/src/pkg/exp/ssa/print.go index b8708b6ede..4e55dc9ff5 100644 --- a/src/pkg/exp/ssa/print.go +++ b/src/pkg/exp/ssa/print.go @@ -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 { diff --git a/src/pkg/exp/ssa/promote.go b/src/pkg/exp/ssa/promote.go new file mode 100644 index 0000000000..7438f4d3e3 --- /dev/null +++ b/src/pkg/exp/ssa/promote.go @@ -0,0 +1,450 @@ +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") +} diff --git a/src/pkg/exp/ssa/ssa.go b/src/pkg/exp/ssa/ssa.go index eb0f7fc0b0..2b0b049f3a 100644 --- a/src/pkg/exp/ssa/ssa.go +++ b/src/pkg/exp/ssa/ssa.go @@ -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 + NamedType *types.NamedType + 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 diff --git a/src/pkg/exp/ssa/util.go b/src/pkg/exp/ssa/util.go index 0d2ebde268..a335e94e92 100644 --- a/src/pkg/exp/ssa/util.go +++ b/src/pkg/exp/ssa/util.go @@ -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] }