go/ssa: simplify TypesWithMethodSets
Details: - rename (*Program).TypesWithMethodSets() to RuntimeTypes() - delete (*Package).TypesWithMethodSets() method and simplify - move code to methods.go - update test to use 1-2% improvement in space and time (though I barely trust this data because the GC at tip is in such terrible state). Change-Id: I38eab78b11e0ad0ff16e0530e775b6ff6a2ab246 Reviewed-on: https://go-review.googlesource.com/3148 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
e079f6c632
commit
f011631cea
|
@ -1262,7 +1262,7 @@ func (a *analysis) generate() {
|
|||
|
||||
// Create nodes and constraints for all methods of all types
|
||||
// that are dynamically accessible via reflection or interfaces.
|
||||
for _, T := range a.prog.TypesWithMethodSets() {
|
||||
for _, T := range a.prog.RuntimeTypes() {
|
||||
a.genMethodsOf(T)
|
||||
}
|
||||
|
||||
|
|
|
@ -2193,7 +2193,7 @@ func (p *Package) Build() {
|
|||
// that would require package creation in topological order.
|
||||
for name, mem := range p.Members {
|
||||
if ast.IsExported(name) {
|
||||
p.needMethodsOf(mem.Type())
|
||||
p.Prog.needMethodsOf(mem.Type())
|
||||
}
|
||||
}
|
||||
if p.Prog.mode&LogSource != 0 {
|
||||
|
@ -2301,129 +2301,3 @@ func (p *Package) typeOf(e ast.Expr) types.Type {
|
|||
panic(fmt.Sprintf("no type for %T @ %s",
|
||||
e, p.Prog.Fset.Position(e.Pos())))
|
||||
}
|
||||
|
||||
// needMethodsOf ensures that runtime type information (including the
|
||||
// complete method set) is available for the specified type T and all
|
||||
// its subcomponents.
|
||||
//
|
||||
// needMethodsOf must be called for at least every type that is an
|
||||
// operand of some MakeInterface instruction, and for the type of
|
||||
// every exported package member.
|
||||
//
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
//
|
||||
// Thread-safe. (Called via emitConv from multiple builder goroutines.)
|
||||
//
|
||||
// TODO(adonovan): make this faster. It accounts for 20% of SSA build
|
||||
// time. Do we need to maintain a distinct needRTTI and methodSets per
|
||||
// package? Using just one in the program might be much faster.
|
||||
//
|
||||
func (p *Package) needMethodsOf(T types.Type) {
|
||||
p.methodsMu.Lock()
|
||||
p.needMethods(T, false)
|
||||
p.methodsMu.Unlock()
|
||||
}
|
||||
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
// Precondition: the p.methodsMu lock is held.
|
||||
// Recursive case: skip => don't call makeMethods(T).
|
||||
func (p *Package) needMethods(T types.Type, skip bool) {
|
||||
// Each package maintains its own set of types it has visited.
|
||||
if prevSkip, ok := p.needRTTI.At(T).(bool); ok {
|
||||
// needMethods(T) was previously called
|
||||
if !prevSkip || skip {
|
||||
return // already seen, with same or false 'skip' value
|
||||
}
|
||||
}
|
||||
p.needRTTI.Set(T, skip)
|
||||
|
||||
// Prune the recursion if we find a named or *named type
|
||||
// belonging to another package.
|
||||
var n *types.Named
|
||||
switch T := T.(type) {
|
||||
case *types.Named:
|
||||
n = T
|
||||
case *types.Pointer:
|
||||
n, _ = T.Elem().(*types.Named)
|
||||
}
|
||||
if n != nil {
|
||||
owner := n.Obj().Pkg()
|
||||
if owner == nil {
|
||||
return // built-in error type
|
||||
}
|
||||
if owner != p.Object {
|
||||
return // belongs to another package
|
||||
}
|
||||
}
|
||||
|
||||
// All the actual method sets live in the Program so that
|
||||
// multiple packages can share a single copy in memory of the
|
||||
// symbols that would be compiled into multiple packages (as
|
||||
// weak symbols).
|
||||
if !skip && p.Prog.makeMethods(T) {
|
||||
p.methodSets = append(p.methodSets, T)
|
||||
}
|
||||
|
||||
// Recursion over signatures of each method.
|
||||
tmset := p.Prog.MethodSets.MethodSet(T)
|
||||
for i := 0; i < tmset.Len(); i++ {
|
||||
sig := tmset.At(i).Type().(*types.Signature)
|
||||
p.needMethods(sig.Params(), false)
|
||||
p.needMethods(sig.Results(), false)
|
||||
}
|
||||
|
||||
switch t := T.(type) {
|
||||
case *types.Basic:
|
||||
// nop
|
||||
|
||||
case *types.Interface:
|
||||
// nop---handled by recursion over method set.
|
||||
|
||||
case *types.Pointer:
|
||||
p.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Slice:
|
||||
p.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Chan:
|
||||
p.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Map:
|
||||
p.needMethods(t.Key(), false)
|
||||
p.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Signature:
|
||||
if t.Recv() != nil {
|
||||
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
|
||||
}
|
||||
p.needMethods(t.Params(), false)
|
||||
p.needMethods(t.Results(), false)
|
||||
|
||||
case *types.Named:
|
||||
// A pointer-to-named type can be derived from a named
|
||||
// type via reflection. It may have methods too.
|
||||
p.needMethods(types.NewPointer(T), false)
|
||||
|
||||
// Consider 'type T struct{S}' where S has methods.
|
||||
// Reflection provides no way to get from T to struct{S},
|
||||
// only to S, so the method set of struct{S} is unwanted,
|
||||
// so set 'skip' flag during recursion.
|
||||
p.needMethods(t.Underlying(), true)
|
||||
|
||||
case *types.Array:
|
||||
p.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Struct:
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
p.needMethods(t.Field(i).Type(), false)
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
for i, n := 0, t.Len(); i < n; i++ {
|
||||
p.needMethods(t.At(i).Type(), false)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(T)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,8 +151,8 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
// TestTypesWithMethodSets tests that Package.TypesWithMethodSets includes all necessary types.
|
||||
func TestTypesWithMethodSets(t *testing.T) {
|
||||
// TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types.
|
||||
func TestRuntimeTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want []string
|
||||
|
@ -167,7 +167,7 @@ func TestTypesWithMethodSets(t *testing.T) {
|
|||
},
|
||||
// Subcomponents of type of exported package-level var are needed.
|
||||
{`package C; import "bytes"; var V struct {*bytes.Buffer}`,
|
||||
[]string{"*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"},
|
||||
[]string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// Subcomponents of type of unexported package-level var are not needed.
|
||||
{`package D; import "bytes"; var v struct {*bytes.Buffer}`,
|
||||
|
@ -175,7 +175,7 @@ func TestTypesWithMethodSets(t *testing.T) {
|
|||
},
|
||||
// Subcomponents of type of exported package-level function are needed.
|
||||
{`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`,
|
||||
[]string{"struct{*bytes.Buffer}"},
|
||||
[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// Subcomponents of type of unexported package-level function are not needed.
|
||||
{`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`,
|
||||
|
@ -187,11 +187,11 @@ func TestTypesWithMethodSets(t *testing.T) {
|
|||
},
|
||||
// ...unless used by MakeInterface.
|
||||
{`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`,
|
||||
[]string{"*p.x", "p.x", "struct{*bytes.Buffer}"},
|
||||
[]string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// Subcomponents of type of unexported method are not needed.
|
||||
{`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`,
|
||||
[]string{"*p.X", "p.X", "struct{*bytes.Buffer}"},
|
||||
[]string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// Local types aren't needed.
|
||||
{`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`,
|
||||
|
@ -199,11 +199,11 @@ func TestTypesWithMethodSets(t *testing.T) {
|
|||
},
|
||||
// ...unless used by MakeInterface.
|
||||
{`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`,
|
||||
[]string{"*p.T", "p.T"},
|
||||
[]string{"*bytes.Buffer", "*p.T", "p.T"},
|
||||
},
|
||||
// Types used as operand of MakeInterface are needed.
|
||||
{`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`,
|
||||
[]string{"struct{*bytes.Buffer}"},
|
||||
[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
|
||||
},
|
||||
// MakeInterface is optimized away when storing to a blank.
|
||||
{`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`,
|
||||
|
@ -226,17 +226,17 @@ func TestTypesWithMethodSets(t *testing.T) {
|
|||
continue
|
||||
}
|
||||
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
||||
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||
prog.BuildAll()
|
||||
|
||||
var typstrs []string
|
||||
for _, T := range mainPkg.TypesWithMethodSets() {
|
||||
for _, T := range prog.RuntimeTypes() {
|
||||
typstrs = append(typstrs, T.String())
|
||||
}
|
||||
sort.Strings(typstrs)
|
||||
|
||||
if !reflect.DeepEqual(typstrs, test.want) {
|
||||
t.Errorf("test 'package %s': got %q, want %q", f.Name.Name, typstrs, test.want)
|
||||
t.Errorf("test 'package %s': got %q, want %q",
|
||||
f.Name.Name, typstrs, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,7 +208,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
|
|||
val = emitConv(f, val, DefaultType(ut_src))
|
||||
}
|
||||
|
||||
f.Pkg.needMethodsOf(val.Type())
|
||||
f.Pkg.Prog.needMethodsOf(val.Type())
|
||||
mi := &MakeInterface{X: val}
|
||||
mi.setType(typ)
|
||||
return f.emit(mi)
|
||||
|
|
|
@ -55,44 +55,6 @@ func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string)
|
|||
return prog.Method(sel)
|
||||
}
|
||||
|
||||
// makeMethods ensures that all concrete methods of type T are
|
||||
// generated. It is equivalent to calling prog.Method() on all
|
||||
// members of T.methodSet(), but acquires fewer locks.
|
||||
//
|
||||
// It reports whether the type's (concrete) method set is non-empty.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) makeMethods(T types.Type) bool {
|
||||
if isInterface(T) {
|
||||
return false // abstract method
|
||||
}
|
||||
tmset := prog.MethodSets.MethodSet(T)
|
||||
n := tmset.Len()
|
||||
if n == 0 {
|
||||
return false // empty (common case)
|
||||
}
|
||||
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("makeMethods %s", T)()
|
||||
}
|
||||
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
mset := prog.createMethodSet(T)
|
||||
if !mset.complete {
|
||||
mset.complete = true
|
||||
for i := 0; i < n; i++ {
|
||||
prog.addMethod(mset, tmset.At(i))
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// methodSet contains the (concrete) methods of a non-interface type.
|
||||
type methodSet struct {
|
||||
mapping map[string]*Function // populated lazily
|
||||
|
@ -135,18 +97,15 @@ func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function
|
|||
return fn
|
||||
}
|
||||
|
||||
// TypesWithMethodSets returns a new unordered slice containing all
|
||||
// RuntimeTypes returns a new unordered slice containing all
|
||||
// concrete types in the program for which a complete (non-empty)
|
||||
// method set is required at run-time.
|
||||
//
|
||||
// It is the union of pkg.TypesWithMethodSets() for all pkg in
|
||||
// prog.AllPackages().
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) TypesWithMethodSets() []types.Type {
|
||||
func (prog *Program) RuntimeTypes() []types.Type {
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
|
||||
|
@ -159,33 +118,6 @@ func (prog *Program) TypesWithMethodSets() []types.Type {
|
|||
return res
|
||||
}
|
||||
|
||||
// TypesWithMethodSets returns an unordered slice containing the set
|
||||
// of all concrete types referenced within package pkg and not
|
||||
// belonging to some other package, for which a complete (non-empty)
|
||||
// method set is required at run-time.
|
||||
//
|
||||
// A type belongs to a package if it is a named type or a pointer to a
|
||||
// named type, and the name was defined in that package. All other
|
||||
// types belong to no package.
|
||||
//
|
||||
// A type may appear in the TypesWithMethodSets() set of multiple
|
||||
// distinct packages if that type belongs to no package. Typical
|
||||
// compilers emit method sets for such types multiple times (using
|
||||
// weak symbols) into each package that references them, with the
|
||||
// linker performing duplicate elimination.
|
||||
//
|
||||
// This set includes the types of all operands of some MakeInterface
|
||||
// instruction, the types of all exported members of some package, and
|
||||
// all types that are subcomponents, since even types that aren't used
|
||||
// directly may be derived via reflection.
|
||||
//
|
||||
// Callers must not mutate the result.
|
||||
//
|
||||
func (pkg *Package) TypesWithMethodSets() []types.Type {
|
||||
// pkg.methodsMu not required; concurrent (build) phase is over.
|
||||
return pkg.methodSets
|
||||
}
|
||||
|
||||
// declaredFunc returns the concrete function/method denoted by obj.
|
||||
// Panic ensues if there is none.
|
||||
//
|
||||
|
@ -195,3 +127,117 @@ func (prog *Program) declaredFunc(obj *types.Func) *Function {
|
|||
}
|
||||
panic("no concrete method: " + obj.String())
|
||||
}
|
||||
|
||||
// needMethodsOf ensures that runtime type information (including the
|
||||
// complete method set) is available for the specified type T and all
|
||||
// its subcomponents.
|
||||
//
|
||||
// needMethodsOf must be called for at least every type that is an
|
||||
// operand of some MakeInterface instruction, and for the type of
|
||||
// every exported package member.
|
||||
//
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
//
|
||||
// Thread-safe. (Called via emitConv from multiple builder goroutines.)
|
||||
//
|
||||
// TODO(adonovan): make this faster. It accounts for 20% of SSA build time.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) needMethodsOf(T types.Type) {
|
||||
prog.methodsMu.Lock()
|
||||
prog.needMethods(T, false)
|
||||
prog.methodsMu.Unlock()
|
||||
}
|
||||
|
||||
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
|
||||
// Recursive case: skip => don't create methods for T.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) needMethods(T types.Type, skip bool) {
|
||||
// Each package maintains its own set of types it has visited.
|
||||
if prevSkip, ok := prog.runtimeTypes.At(T).(bool); ok {
|
||||
// needMethods(T) was previously called
|
||||
if !prevSkip || skip {
|
||||
return // already seen, with same or false 'skip' value
|
||||
}
|
||||
}
|
||||
prog.runtimeTypes.Set(T, skip)
|
||||
|
||||
tmset := prog.MethodSets.MethodSet(T)
|
||||
|
||||
if !skip && !isInterface(T) && tmset.Len() > 0 {
|
||||
// Create methods of T.
|
||||
mset := prog.createMethodSet(T)
|
||||
if !mset.complete {
|
||||
mset.complete = true
|
||||
n := tmset.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
prog.addMethod(mset, tmset.At(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursion over signatures of each method.
|
||||
for i := 0; i < tmset.Len(); i++ {
|
||||
sig := tmset.At(i).Type().(*types.Signature)
|
||||
prog.needMethods(sig.Params(), false)
|
||||
prog.needMethods(sig.Results(), false)
|
||||
}
|
||||
|
||||
switch t := T.(type) {
|
||||
case *types.Basic:
|
||||
// nop
|
||||
|
||||
case *types.Interface:
|
||||
// nop---handled by recursion over method set.
|
||||
|
||||
case *types.Pointer:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Slice:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Chan:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Map:
|
||||
prog.needMethods(t.Key(), false)
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Signature:
|
||||
if t.Recv() != nil {
|
||||
panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv()))
|
||||
}
|
||||
prog.needMethods(t.Params(), false)
|
||||
prog.needMethods(t.Results(), false)
|
||||
|
||||
case *types.Named:
|
||||
// A pointer-to-named type can be derived from a named
|
||||
// type via reflection. It may have methods too.
|
||||
prog.needMethods(types.NewPointer(T), false)
|
||||
|
||||
// Consider 'type T struct{S}' where S has methods.
|
||||
// Reflection provides no way to get from T to struct{S},
|
||||
// only to S, so the method set of struct{S} is unwanted,
|
||||
// so set 'skip' flag during recursion.
|
||||
prog.needMethods(t.Underlying(), true)
|
||||
|
||||
case *types.Array:
|
||||
prog.needMethods(t.Elem(), false)
|
||||
|
||||
case *types.Struct:
|
||||
for i, n := 0, t.NumFields(); i < n; i++ {
|
||||
prog.needMethods(t.Field(i).Type(), false)
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
for i, n := 0, t.Len(); i < n; i++ {
|
||||
prog.needMethods(t.At(i).Type(), false)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(T)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ type Program struct {
|
|||
|
||||
methodsMu sync.Mutex // guards the following maps:
|
||||
methodSets typeutil.Map // maps type to its concrete methodSet
|
||||
runtimeTypes typeutil.Map // types for which rtypes are needed
|
||||
canon typeutil.Map // type canonicalization map
|
||||
bounds map[*types.Func]*Function // bounds for curried x.Method closures
|
||||
thunks map[selectionKey]*Function // thunks for T.Method expressions
|
||||
|
@ -44,8 +45,6 @@ type Package struct {
|
|||
Prog *Program // the owning program
|
||||
Object *types.Package // the type checker's package object for this package
|
||||
Members map[string]Member // all package members keyed by name
|
||||
methodsMu sync.Mutex // guards needRTTI and methodSets
|
||||
methodSets []types.Type // types whose method sets are included in this package
|
||||
values map[types.Object]Value // package members (incl. types and methods), keyed by object
|
||||
init *Function // Func("init"); the package's init function
|
||||
debug bool // include full debug info in this package.
|
||||
|
@ -55,7 +54,6 @@ type Package struct {
|
|||
started int32 // atomically tested and set at start of build phase
|
||||
ninit int32 // number of init functions
|
||||
info *loader.PackageInfo // package ASTs and type information
|
||||
needRTTI typeutil.Map // types for which runtime type info is needed
|
||||
}
|
||||
|
||||
// A Member is a member of a Go package, implemented by *NamedConst,
|
||||
|
|
|
@ -41,7 +41,7 @@ func (visit *visitor) program() {
|
|||
}
|
||||
}
|
||||
}
|
||||
for _, T := range visit.prog.TypesWithMethodSets() {
|
||||
for _, T := range visit.prog.RuntimeTypes() {
|
||||
mset := visit.prog.MethodSets.MethodSet(T)
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
visit.function(visit.prog.Method(mset.At(i)))
|
||||
|
|
Loading…
Reference in New Issue