diff --git a/go/ssa/methods.go b/go/ssa/methods.go new file mode 100644 index 00000000..14fc2a30 --- /dev/null +++ b/go/ssa/methods.go @@ -0,0 +1,196 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ssa + +// This file defines utilities for population of method sets. + +import ( + "fmt" + + "code.google.com/p/go.tools/go/types" +) + +// Method returns the Function implementing method sel, building +// wrapper methods on demand. It returns nil if sel denotes an +// abstract (interface) method. +// +// Precondition: sel.Kind() == MethodVal. +// +// TODO(adonovan): rename this to MethodValue because of the +// precondition, and for consistency with functions in source.go. +// +// Thread-safe. +// +// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) +// +func (prog *Program) Method(sel *types.Selection) *Function { + if sel.Kind() != types.MethodVal { + panic(fmt.Sprintf("Method(%s) kind != MethodVal", sel)) + } + T := sel.Recv() + if isInterface(T) { + return nil // abstract method + } + if prog.mode&LogSource != 0 { + defer logStack("Method %s %v", T, sel)() + } + + prog.methodsMu.Lock() + defer prog.methodsMu.Unlock() + + return prog.addMethod(prog.createMethodSet(T), sel) +} + +// LookupMethod returns the implementation of the method of type T +// identified by (pkg, name). It returns nil if the method exists but +// is abstract, and panics if T has no such method. +// +func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { + sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) + if sel == nil { + panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name))) + } + 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 + complete bool // mapping contains all methods +} + +// Precondition: !isInterface(T). +// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) +func (prog *Program) createMethodSet(T types.Type) *methodSet { + mset, ok := prog.methodSets.At(T).(*methodSet) + if !ok { + mset = &methodSet{mapping: make(map[string]*Function)} + prog.methodSets.Set(T, mset) + } + return mset +} + +// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) +func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function { + if sel.Kind() == types.MethodExpr { + panic(sel) + } + id := sel.Obj().Id() + fn := mset.mapping[id] + if fn == nil { + obj := sel.Obj().(*types.Func) + + needsPromotion := len(sel.Index()) > 1 + needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv()) + if needsPromotion || needsIndirection { + fn = makeWrapper(prog, sel) + } else { + fn = prog.declaredFunc(obj) + } + if fn.Signature.Recv() == nil { + panic(fn) // missing receiver + } + mset.mapping[id] = fn + } + return fn +} + +// TypesWithMethodSets 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 { + prog.methodsMu.Lock() + defer prog.methodsMu.Unlock() + + var res []types.Type + prog.methodSets.Iterate(func(T types.Type, v interface{}) { + if v.(*methodSet).complete { + res = append(res, T) + } + }) + 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 { + return pkg.methodSets +} + +// declaredFunc returns the concrete function/method denoted by obj. +// Panic ensues if there is none. +// +func (prog *Program) declaredFunc(obj *types.Func) *Function { + if v := prog.packageLevelValue(obj); v != nil { + return v.(*Function) + } + panic("no concrete method: " + obj.String()) +} diff --git a/go/ssa/util.go b/go/ssa/util.go index d44ddce3..738ac7b8 100644 --- a/go/ssa/util.go +++ b/go/ssa/util.go @@ -50,6 +50,12 @@ func isPointer(typ types.Type) bool { return ok } +// isInterface reports whether T's underlying type is an interface. +func isInterface(T types.Type) bool { + _, ok := T.Underlying().(*types.Interface) + return ok +} + // deref returns a pointer's element type; otherwise it returns typ. func deref(typ types.Type) types.Type { if p, ok := typ.Underlying().(*types.Pointer); ok { @@ -58,6 +64,11 @@ func deref(typ types.Type) types.Type { return typ } +// recvType returns the receiver type of method obj. +func recvType(obj *types.Func) types.Type { + return obj.Type().(*types.Signature).Recv().Type() +} + // DefaultType returns the default "typed" type for an "untyped" type; // it returns the incoming type for all other types. The default type // for untyped nil is untyped nil. diff --git a/go/ssa/promote.go b/go/ssa/wrappers.go similarity index 54% rename from go/ssa/promote.go rename to go/ssa/wrappers.go index f1104361..d23075f2 100644 --- a/go/ssa/promote.go +++ b/go/ssa/wrappers.go @@ -4,9 +4,8 @@ package ssa -// This file defines utilities for population of method sets and -// synthesis of Functions that delegate to declared methods, which -// come in three kinds: +// This file defines synthesis of Functions that delegate to declared +// methods, which come in three kinds: // // (1) wrappers: methods that wrap declared methods, performing // implicit pointer indirections and embedded field selections. @@ -19,9 +18,6 @@ package ssa // free variable, supplied by a closure, is used as the receiver // for the method call. No indirections or field selections are // performed since they can be done before the call. -// -// TODO(adonovan): split and rename to {methodset,delegate}.go. -// TODO(adonovan): use 'sel' not 'meth' for *types.Selection; reserve 'meth' for *Function. import ( "fmt" @@ -29,190 +25,7 @@ import ( "code.google.com/p/go.tools/go/types" ) -// Method returns the Function implementing method meth, building -// wrapper methods on demand. It returns nil if meth denotes an -// abstract (interface) method. -// -// Precondition: meth Kind() == MethodVal. -// -// TODO(adonovan): rename this to MethodValue because of the -// precondition, and for consistency with functions in source.go. -// -// Thread-safe. -// -// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu) -// -func (prog *Program) Method(meth *types.Selection) *Function { - if meth.Kind() != types.MethodVal { - panic(fmt.Sprintf("Method(%s) kind != MethodVal", meth)) - } - T := meth.Recv() - if isInterface(T) { - return nil // abstract method - } - if prog.mode&LogSource != 0 { - defer logStack("Method %s %v", T, meth)() - } - - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - - return prog.addMethod(prog.createMethodSet(T), meth) -} - -// LookupMethod returns the implementation of the method of type T -// identified by (pkg, name). It returns nil if the method exists but -// is abstract, and panics if T has no such method. -// -func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { - sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) - if sel == nil { - panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name))) - } - 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 - complete bool // mapping contains all methods -} - -// Precondition: !isInterface(T). -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func (prog *Program) createMethodSet(T types.Type) *methodSet { - mset, ok := prog.methodSets.At(T).(*methodSet) - if !ok { - mset = &methodSet{mapping: make(map[string]*Function)} - prog.methodSets.Set(T, mset) - } - return mset -} - -// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) -func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function { - if sel.Kind() == types.MethodExpr { - panic(sel) - } - id := sel.Obj().Id() - fn := mset.mapping[id] - if fn == nil { - obj := sel.Obj().(*types.Func) - - needsPromotion := len(sel.Index()) > 1 - needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv()) - if needsPromotion || needsIndirection { - fn = makeWrapper(prog, sel) - } else { - fn = prog.declaredFunc(obj) - } - if fn.Signature.Recv() == nil { - panic(fn) // missing receiver - } - mset.mapping[id] = fn - } - return fn -} - -// TypesWithMethodSets 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 { - prog.methodsMu.Lock() - defer prog.methodsMu.Unlock() - - var res []types.Type - prog.methodSets.Iterate(func(T types.Type, v interface{}) { - if v.(*methodSet).complete { - res = append(res, T) - } - }) - 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 { - return pkg.methodSets -} - -// declaredFunc returns the concrete function/method denoted by obj. -// Panic ensues if there is none. -// -func (prog *Program) declaredFunc(obj *types.Func) *Function { - if v := prog.packageLevelValue(obj); v != nil { - return v.(*Function) - } - panic("no concrete method: " + obj.String()) -} - -// -- wrappers --------------------------------------------------------- +// -- wrappers ----------------------------------------------------------- // makeWrapper returns a synthetic method that delegates to the // declared method denoted by meth.Obj(), first performing any @@ -229,15 +42,15 @@ func (prog *Program) declaredFunc(obj *types.Func) *Function { // // EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu) // -func makeWrapper(prog *Program, meth *types.Selection) *Function { - obj := meth.Obj().(*types.Func) // the declared function - sig := meth.Type().(*types.Signature) // type of this wrapper +func makeWrapper(prog *Program, sel *types.Selection) *Function { + obj := sel.Obj().(*types.Func) // the declared function + sig := sel.Type().(*types.Signature) // type of this wrapper var recv *types.Var // wrapper's receiver or thunk's params[0] name := obj.Name() var description string var start int // first regular param - if meth.Kind() == types.MethodExpr { + if sel.Kind() == types.MethodExpr { name += "$thunk" description = "thunk" recv = sig.Params().At(0) @@ -247,13 +60,13 @@ func makeWrapper(prog *Program, meth *types.Selection) *Function { recv = sig.Recv() } - description = fmt.Sprintf("%s for %s", description, meth.Obj()) + description = fmt.Sprintf("%s for %s", description, sel.Obj()) if prog.mode&LogSource != 0 { defer logStack("make %s to (%s)", description, recv.Type())() } fn := &Function{ name: name, - method: meth, + method: sel, object: obj, Signature: sig, Synthetic: description, @@ -264,10 +77,10 @@ func makeWrapper(prog *Program, meth *types.Selection) *Function { fn.addSpilledParam(recv) createParams(fn, start) - indices := meth.Index() + indices := sel.Index() var v Value = fn.Locals[0] // spilled receiver - if isPointer(meth.Recv()) { + if isPointer(sel.Recv()) { v = emitLoad(fn, v) // For simple indirection wrappers, perform an informative nil-check: @@ -277,13 +90,13 @@ func makeWrapper(prog *Program, meth *types.Selection) *Function { c.Call.Value = &Builtin{ name: "ssa:wrapnilchk", sig: types.NewSignature(nil, nil, - types.NewTuple(anonVar(meth.Recv()), anonVar(tString), anonVar(tString)), - types.NewTuple(anonVar(meth.Recv())), false), + types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)), + types.NewTuple(anonVar(sel.Recv())), false), } c.Call.Args = []Value{ v, - stringConst(deref(meth.Recv()).String()), - stringConst(meth.Obj().Name()), + stringConst(deref(sel.Recv()).String()), + stringConst(sel.Obj().Name()), } c.setType(v.Type()) v = fn.emit(&c) @@ -464,16 +277,6 @@ func changeRecv(s *types.Signature, recv *types.Var) *types.Signature { return types.NewSignature(nil, recv, s.Params(), s.Results(), s.Variadic()) } -// recvType returns the receiver type of method obj. -func recvType(obj *types.Func) types.Type { - return obj.Type().(*types.Signature).Recv().Type() -} - -func isInterface(T types.Type) bool { - _, ok := T.Underlying().(*types.Interface) - return ok -} - // selectionKey is like types.Selection but a usable map key. type selectionKey struct { kind types.SelectionKind