mirror of https://github.com/golang/go.git
exp/ssa: (#4 of 5): the SSA builder.
R=iant, gri, iant, rogpeppe CC=golang-dev https://golang.org/cl/7196053
This commit is contained in:
parent
399dcc75a8
commit
c06a5335ba
File diff suppressed because it is too large
Load Diff
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
// TODO(adonovan): make order deterministic.
|
||||
func (p *Package) DumpTo(w io.Writer) {
|
||||
fmt.Fprintf(w, "Package %s at %s:\n", p.ImportPath, p.Prog.Files.File(p.Pos).Name())
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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] }
|
||||
|
|
|
|||
Loading…
Reference in New Issue