1
0
mirror of https://github.com/golang/go synced 2024-10-01 12:28:37 -06:00
go/ssa/promote.go
Alan Donovan 87ced824bd go.tools/ssa: fix computation of set of types requiring method sets.
Motivation:

Previously, we assumed that the set of types for which a
complete method set (containing all synthesized wrapper
functions) is required at runtime was the set of types
used as operands to some *ssa.MakeInterface instruction.

In fact, this is an underapproximation because types can
be derived from other ones via reflection, and some of
these may need methods.  The reflect.Type API allows *T to
be derived from T, and these may have different method
sets.  Reflection also allows almost any subcomponent of a
type to be accessed (with one exception: given T, defined
'type T struct{S}', you can reach S but not struct{S}).

As a result, the pointer analysis was unable to generate
all necessary constraints before running the solver,
causing a crash when reflection derives types whose
methods are unavailable.  (A similar problem would afflict
an ahead-of-time compiler based on ssa.  The ssa/interp
interpreter was immune only because it does not require
all wrapper methods to be created before execution
begins.)

Description:

This change causes the SSA builder to record, for each
package, the set of all types with non-empty method sets that
are referenced within that package.  This set is accessed via
Packages.TypesWithMethodSets().  Program.TypesWithMethodSets()
returns its union across all packages.

The set of references that matter are:
- types of operands to some MakeInterface instruction (as before)
- types of all exported package members
- all subcomponents of the above, recursively.
This is a conservative approximation to the set of types
whose methods may be called dynamically.

We define the owning package of a type as follows:
- the owner of a named type is the package in which it is defined;
- the owner of a pointer-to-named type is the owner of that named type;
- the owner of all other types is nil.

A package must include the method sets for all types that it
owns, and all subcomponents of that type that are not owned by
another package, recursively.  Types with an owner appear in
exactly one package; types with no owner (such as struct{T})
may appear within multiple packages.
(A typical Go compiler would emit multiple copies of these
methods as weak symbols; a typical linker would eliminate
duplicates.)

Also:
- go/types/typemap: implement hash function for *Tuple.
- pointer: generate nodes/constraints for all of
  ssa.Program.TypesWithMethodSets().
  Add rtti.go regression test.
- Add API test of Package.TypesWithMethodSets().
- Set Function.Pkg to nil (again) for wrapper functions,
  since these may be shared by many packages.
- Remove a redundant logging statement.
- Document that ssa CREATE phase is in fact sequential.

Fixes golang/go#6605

R=gri
CC=golang-dev
https://golang.org/cl/14920056
2013-10-23 17:07:52 -04:00

420 lines
12 KiB
Go

// 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 and
// synthesis of wrapper methods.
//
// Wrappers include:
// - indirection/promotion wrappers for methods of embedded fields.
// - interface method wrappers for expressions I.f.
// - bound method wrappers, for uncalled obj.Method closures.
// TODO(adonovan): split and rename to {methodset,wrappers}.go.
import (
"fmt"
"go/token"
"code.google.com/p/go.tools/go/types"
)
// Method returns the Function implementing method meth, building
// wrapper methods on demand.
//
// Thread-safe.
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
//
func (prog *Program) Method(meth *types.Selection) *Function {
if meth == nil {
panic("Method(nil)")
}
T := meth.Recv()
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)
}
// makeMethods ensures that all wrappers in the complete method set of
// 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 method set is non-empty.
//
// Thread-safe.
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
//
func (prog *Program) makeMethods(T types.Type) bool {
tmset := T.MethodSet()
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
}
type methodSet struct {
mapping map[string]*Function // populated lazily
complete bool // mapping contains all methods
}
// 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, meth *types.Selection) *Function {
id := meth.Obj().Id()
fn := mset.mapping[id]
if fn == nil {
fn = findMethod(prog, meth)
mset.mapping[id] = fn
}
return fn
}
// TypesWithMethodSets returns a new unordered slice containing all
// 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 a new unordered slice containing the
// set of all 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())
}
// recvType returns the receiver type of method obj.
func recvType(obj *types.Func) types.Type {
return obj.Type().(*types.Signature).Recv().Type()
}
// findMethod returns the concrete Function for the method meth,
// synthesizing wrappers as needed.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
//
func findMethod(prog *Program, meth *types.Selection) *Function {
needsPromotion := len(meth.Index()) > 1
obj := meth.Obj().(*types.Func)
needsIndirection := !isPointer(recvType(obj)) && isPointer(meth.Recv())
if needsPromotion || needsIndirection {
return makeWrapper(prog, meth.Recv(), meth)
}
if _, ok := meth.Recv().Underlying().(*types.Interface); ok {
return interfaceMethodWrapper(prog, meth.Recv(), obj)
}
return prog.declaredFunc(obj)
}
// makeWrapper returns a synthetic wrapper Function that optionally
// performs receiver indirection, implicit field selections and then a
// tailcall of a "promoted" method. For example, given these decls:
//
// type A struct {B}
// type B struct {*C}
// type C ...
// func (*C) f()
//
// then makeWrapper(typ=A, obj={Func:(*C).f, Indices=[B,C,f]})
// synthesize this wrapper 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 wrapper method. obj is the
// type-checker's object for the promoted method; its Func may be a
// concrete or an interface method.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
//
func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function {
obj := meth.Obj().(*types.Func)
oldsig := obj.Type().(*types.Signature)
recv := types.NewVar(token.NoPos, nil, "recv", typ)
description := fmt.Sprintf("wrapper for %s", obj)
if prog.mode&LogSource != 0 {
defer logStack("make %s to (%s)", description, typ)()
}
fn := &Function{
name: obj.Name(),
method: meth,
Signature: changeRecv(oldsig, recv),
Synthetic: description,
Prog: prog,
pos: obj.Pos(),
}
fn.startBody()
fn.addSpilledParam(recv)
createParams(fn)
var v Value = fn.Locals[0] // spilled receiver
if isPointer(typ) {
// TODO(adonovan): consider emitting a nil-pointer check here
// with a nice error message, like gc does.
v = emitLoad(fn, v)
}
// Invariant: v is a pointer, either
// value of *A receiver param, or
// address of A spilled receiver.
// We use pointer arithmetic (FieldAddr possibly followed by
// Load) in preference to value extraction (Field possibly
// preceded by Load).
indices := meth.Index()
v = emitImplicitSelections(fn, v, indices[:len(indices)-1])
// Invariant: v is a pointer, either
// value of implicit *C field, or
// address of implicit C field.
var c Call
if _, ok := oldsig.Recv().Type().Underlying().(*types.Interface); !ok { // concrete method
if !isPointer(oldsig.Recv().Type()) {
v = emitLoad(fn, v)
}
c.Call.Value = prog.declaredFunc(obj)
c.Call.Args = append(c.Call.Args, v)
} else {
c.Call.Method = obj
c.Call.Value = emitLoad(fn, v)
}
for _, arg := range fn.Params[1:] {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
fn.finishBody()
return fn
}
// createParams creates parameters for wrapper method fn based on its
// Signature.Params, which do not include the receiver.
//
func createParams(fn *Function) {
var last *Parameter
tparams := fn.Signature.Params()
for i, n := 0, tparams.Len(); i < n; i++ {
last = fn.addParamObj(tparams.At(i))
}
if fn.Signature.IsVariadic() {
last.typ = types.NewSlice(last.typ)
}
}
// Wrappers for standalone interface methods ----------------------------------
// interfaceMethodWrapper returns a synthetic wrapper function
// permitting an abstract method obj to be called like a standalone
// function, e.g.:
//
// type I interface { f(x int) R }
// m := I.f // wrapper
// var i I
// m(i, 0)
//
// The wrapper is defined as if by:
//
// func (i I) f(x int, ...) R {
// return i.f(x, ...)
// }
//
// typ is the type of the receiver (I here). It isn't necessarily
// equal to the recvType(obj) because one interface may embed another.
// TODO(adonovan): more tests.
//
// TODO(adonovan): opt: currently the stub is created even when used
// in call position: I.f(i, 0). Clearly this is suboptimal.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
//
func interfaceMethodWrapper(prog *Program, typ types.Type, obj *types.Func) *Function {
// If one interface embeds another they'll share the same
// wrappers for common methods. This is safe, but it might
// confuse some tools because of the implicit interface
// conversion applied to the first argument. If this becomes
// a problem, we should include 'typ' in the memoization key.
fn, ok := prog.ifaceMethodWrappers[obj]
if !ok {
description := "interface method wrapper"
if prog.mode&LogSource != 0 {
defer logStack("(%s).%s, %s", typ, obj.Name(), description)()
}
fn = &Function{
name: obj.Name(),
object: obj,
Signature: obj.Type().(*types.Signature),
Synthetic: description,
pos: obj.Pos(),
Prog: prog,
}
fn.startBody()
fn.addParam("recv", typ, token.NoPos)
createParams(fn)
var c Call
c.Call.Method = obj
c.Call.Value = fn.Params[0]
for _, arg := range fn.Params[1:] {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
fn.finishBody()
prog.ifaceMethodWrappers[obj] = fn
}
return fn
}
// Wrappers for bound methods -------------------------------------------------
// boundMethodWrapper returns a synthetic wrapper function that
// delegates to a concrete or interface method.
// The wrapper has one free variable, the method's receiver.
// Use MakeClosure with such a wrapper to construct a bound-method
// closure. e.g.:
//
// type T int or: type T interface { meth() }
// func (t T) meth()
// var t T
// f := t.meth
// f() // calls t.meth()
//
// f is a closure of a synthetic wrapper defined as if by:
//
// f := func() { return t.meth() }
//
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
//
func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
fn, ok := prog.boundMethodWrappers[obj]
if !ok {
description := fmt.Sprintf("bound method wrapper for %s", obj)
if prog.mode&LogSource != 0 {
defer logStack("%s", description)()
}
fn = &Function{
name: "bound$" + obj.FullName(),
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
Synthetic: description,
Prog: prog,
pos: obj.Pos(),
}
cap := &Capture{name: "recv", typ: recvType(obj), parent: fn}
fn.FreeVars = []*Capture{cap}
fn.startBody()
createParams(fn)
var c Call
if _, ok := recvType(obj).Underlying().(*types.Interface); !ok { // concrete
c.Call.Value = prog.declaredFunc(obj)
c.Call.Args = []Value{cap}
} else {
c.Call.Value = cap
c.Call.Method = obj
}
for _, arg := range fn.Params {
c.Call.Args = append(c.Call.Args, arg)
}
emitTailCall(fn, &c)
fn.finishBody()
prog.boundMethodWrappers[obj] = fn
}
return fn
}
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
return types.NewSignature(nil, recv, s.Params(), s.Results(), s.IsVariadic())
}