1
0
mirror of https://github.com/golang/go synced 2024-09-29 13:14:28 -06:00

cmd/compile: implement generic method expressions with closures in dictionary

Currently we do quite a dance for method expressions on generic
types. We write a new closure, and in that closure convert the
receiver to an interface with the required method, then call the
target using an interface call.

Instead in this CL, we just allocate a (captureless) closure in the
dictionary which implements that method expression.

This CL makes method expressions faster and simpler. But the real win
is some followon CLs, where we can use the same closure to implement
bound method calls using the same closure, instead of converting to
interface and having wrappers convert back. Much faster and simpler.

Still thinking about how to do method values. The receiver still
needs to be captured, so there must be some closure involved, I think.

Update #50182

Change-Id: I1fbd57e7105663f8b049955b8f4111649a5f4aa8
Reviewed-on: https://go-review.googlesource.com/c/go/+/385254
Trust: Keith Randall <khr@golang.org>
Run-TryBot: Keith Randall <khr@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Keith Randall 2022-02-10 16:40:18 -08:00
parent 0652274c10
commit 7fc38802e1
2 changed files with 101 additions and 76 deletions

View File

@ -99,8 +99,8 @@ type subDictInfo struct {
}
// dictInfo is the dictionary format for an instantiation of a generic function with
// particular shapes. shapeParams, derivedTypes, subDictCalls, and itabConvs describe
// the actual dictionary entries in order, and the remaining fields are other info
// particular shapes. shapeParams, derivedTypes, subDictCalls, itabConvs, and methodExprClosures
// describe the actual dictionary entries in order, and the remaining fields are other info
// needed in doing dictionary processing during compilation.
type dictInfo struct {
// Types substituted for the type parameters, which are shape types.
@ -114,6 +114,11 @@ type dictInfo struct {
// Nodes in the instantiation that are a conversion from a typeparam/derived
// type to a specific interface.
itabConvs []ir.Node
// Method expression closures. For a generic type T with method M(arg1, arg2) res,
// these closures are func(rcvr T, arg1, arg2) res.
// These closures capture no variables, they are just the generic version of ·f symbols
// that live in the dictionary instead of in the readonly globals section.
methodExprClosures []methodExprClosure
// Mapping from each shape type that substitutes a type param, to its
// type bound (which is also substituted with shapes if it is parameterized)
@ -123,9 +128,15 @@ type dictInfo struct {
// HasShape type, to the interface type we're switching from.
type2switchType map[ir.Node]*types.Type
startSubDict int // Start of dict entries for subdictionaries
startItabConv int // Start of dict entries for itab conversions
dictLen int // Total number of entries in dictionary
startSubDict int // Start of dict entries for subdictionaries
startItabConv int // Start of dict entries for itab conversions
startMethodExprClosures int // Start of dict entries for closures for method expressions
dictLen int // Total number of entries in dictionary
}
type methodExprClosure struct {
idx int // index in list of shape parameters
name string // method name
}
// instInfo is information gathered on an shape instantiation of a function.
@ -182,7 +193,7 @@ type genInst struct {
instInfoMap map[*types.Sym]*instInfo
// Dictionary syms which we need to finish, by writing out any itabconv
// entries.
// or method expression closure entries.
dictSymsToFinalize []*delayInfo
// New instantiations created during this round of buildInstantiations().

View File

@ -888,6 +888,13 @@ func getDictionaryEntry(pos src.XPos, dict *ir.Name, i int, size int) ir.Node {
return r
}
// getDictionaryEntryAddr gets the address of the i'th entry in dictionary dict.
func getDictionaryEntryAddr(pos src.XPos, dict *ir.Name, i int, size int) ir.Node {
a := ir.NewAddrExpr(pos, getDictionaryEntry(pos, dict, i, size))
typed(types.Types[types.TUINTPTR].PtrTo(), a)
return a
}
// getDictionaryType returns a *runtime._type from the dictionary entry i (which
// refers to a type param or a derived type that uses type params). It uses the
// specified dictionary dictParam, rather than the one in info.dictParam.
@ -1239,9 +1246,10 @@ func (g *genInst) dictPass(info *instInfo) {
if mse.X.Op() == ir.OTYPE {
// Method expression T.M
m = g.buildClosure2(info, m)
// No need for transformDot - buildClosure2 has already
// transformed to OCALLINTER/ODOTINTER.
idx := findMethodExprClosure(info.dictInfo, mse)
c := getDictionaryEntryAddr(m.Pos(), info.dictParam, info.dictInfo.startMethodExprClosures+idx, info.dictInfo.dictLen)
m = ir.NewConvExpr(m.Pos(), ir.OCONVNOP, mse.Type(), c)
m.SetTypecheck(1)
} else {
// If we can't find the selected method in the
// AllMethods of the bound, then this must be an access
@ -1720,6 +1728,7 @@ func (g *genInst) getSymForMethodCall(se *ir.SelectorExpr, subst *typecheck.Tsub
// dictionaries and method instantiations to be complete, so, to avoid recursive
// dependencies, we finalize the itab lsyms only after all dictionaries syms and
// instantiations have been created.
// Also handles writing method expression closures into the dictionaries.
func (g *genInst) finalizeSyms() {
for _, d := range g.dictSymsToFinalize {
infoPrint("=== Finalizing dictionary %s\n", d.sym.Name)
@ -1768,6 +1777,31 @@ func (g *genInst) finalizeSyms() {
}
}
// Emit an entry for each method expression closure.
// Each entry is a (captureless) closure pointing to the method on the instantiating type.
// In other words, the entry is a runtime.funcval whose fn field is set to the method
// in question, and has no other fields. The address of this dictionary entry can be
// cast to a func of the appropriate type.
// TODO: do these need to be done when finalizing, or can we do them earlier?
for _, bf := range info.methodExprClosures {
rcvr := d.targs[bf.idx]
rcvr2 := deref(rcvr)
found := false
typecheck.CalcMethods(rcvr2) // Ensure methods on all instantiating types are computed.
for _, f := range rcvr2.AllMethods().Slice() {
if f.Sym.Name == bf.name {
codePtr := ir.MethodSym(rcvr, f.Sym).Linksym()
d.off = objw.SymPtr(lsym, d.off, codePtr, 0)
infoPrint(" + MethodExprClosure for %v.%s\n", rcvr, bf.name)
found = true
break
}
}
if !found {
base.Fatalf("method %s on %v not found", bf.name, rcvr)
}
}
objw.Global(lsym, int32(d.off), obj.DUPOK|obj.RODATA)
infoPrint("=== Finalized dictionary %s\n", d.sym.Name)
}
@ -1824,7 +1858,7 @@ func hasShapeTypes(targs []*types.Type) bool {
}
// getInstInfo get the dictionary format for a function instantiation- type params, derived
// types, and needed subdictionaries and itabs.
// types, and needed subdictionaries, itabs, and method expression closures.
func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instInfo) {
info := instInfo.dictInfo
info.shapeParams = shapes
@ -1920,7 +1954,12 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI
}
case ir.OXDOT:
se := n.(*ir.SelectorExpr)
if se.X.Op() == ir.OTYPE && se.X.Type().IsShape() {
addMethodExprClosure(info, se)
break
}
if isBoundMethod(info, se) {
// TODO: handle these using method expression closures also.
infoPrint(" Itab for bound call: %v\n", n)
info.itabConvs = append(info.itabConvs, n)
}
@ -1973,7 +2012,8 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI
}
info.startSubDict = len(info.shapeParams) + len(info.derivedTypes)
info.startItabConv = len(info.shapeParams) + len(info.derivedTypes) + len(info.subDictCalls)
info.dictLen = len(info.shapeParams) + len(info.derivedTypes) + len(info.subDictCalls) + len(info.itabConvs)
info.startMethodExprClosures = len(info.shapeParams) + len(info.derivedTypes) + len(info.subDictCalls) + len(info.itabConvs)
info.dictLen = len(info.shapeParams) + len(info.derivedTypes) + len(info.subDictCalls) + len(info.itabConvs) + len(info.methodExprClosures)
}
// isBoundMethod returns true if the selection indicated by se is a bound method of
@ -1985,6 +2025,45 @@ func isBoundMethod(info *dictInfo, se *ir.SelectorExpr) bool {
return typecheck.Lookdot1(se, se.Sel, bound, bound.AllMethods(), 1) != nil
}
func shapeIndex(info *dictInfo, t *types.Type) int {
for i, s := range info.shapeParams {
if s == t {
return i
}
}
base.Fatalf("can't find type %v in shape params", t)
return -1
}
// addMethodExprClosure adds the T.M method expression to the list of bound method expressions
// used in the generic body.
// isBoundMethod must have returned true on the same arguments.
func addMethodExprClosure(info *dictInfo, se *ir.SelectorExpr) {
idx := shapeIndex(info, se.X.Type())
name := se.Sel.Name
for _, b := range info.methodExprClosures {
if idx == b.idx && name == b.name {
return
}
}
info.methodExprClosures = append(info.methodExprClosures, methodExprClosure{idx: idx, name: name})
}
// findMethodExprClosure finds the entry in the dictionary to use for the T.M
// method expression encoded in se.
// isBoundMethod must have returned true on the same arguments.
func findMethodExprClosure(info *dictInfo, se *ir.SelectorExpr) int {
idx := shapeIndex(info, se.X.Type())
name := se.Sel.Name
for i, b := range info.methodExprClosures {
if idx == b.idx && name == b.name {
return i
}
}
base.Fatalf("can't find method expression closure for %s %s", se.X.Type(), name)
return -1
}
// addType adds t to info.derivedTypes if it is parameterized type (which is not
// just a simple shape) that is different from any existing type on
// info.derivedTypes.
@ -2154,68 +2233,3 @@ func assertToBound(info *instInfo, dictVar *ir.Name, pos src.XPos, rcvr ir.Node,
}
return rcvr
}
// buildClosure2 makes a closure to implement a method expression m (generic form x)
// which has a shape type as receiver. If the receiver is exactly a shape (i.e. from
// a typeparam), then the body of the closure converts m.X (the receiver) to the
// interface bound type, and makes an interface call with the remaining arguments.
//
// The returned closure is fully substituted and has already had any needed
// transformations done.
func (g *genInst) buildClosure2(info *instInfo, m ir.Node) ir.Node {
outer := info.fun
pos := m.Pos()
typ := m.Type() // type of the closure
fn, formalParams, formalResults := startClosure(pos, outer, typ)
// Capture dictionary calculated in the outer function
dictVar := ir.CaptureName(pos, fn, info.dictParam)
typed(types.Types[types.TUINTPTR], dictVar)
// Build arguments to call inside the closure.
var args []ir.Node
for i := 0; i < typ.NumParams(); i++ {
args = append(args, formalParams[i].Nname.(*ir.Name))
}
// Build call itself. This involves converting the first argument to the
// bound type (an interface) using the dictionary, and then making an
// interface call with the remaining arguments.
var innerCall ir.Node
rcvr := args[0]
args = args[1:]
assert(m.(*ir.SelectorExpr).X.Type().IsShape())
dst := info.dictInfo.shapeToBound[m.(*ir.SelectorExpr).X.Type()]
if m.(*ir.SelectorExpr).X.Type().IsInterface() {
// If type arg is an interface (unusual case), we do a type assert to
// the type bound.
rcvr = assertToBound(info, dictVar, pos, rcvr, dst)
} else {
rcvr = convertUsingDictionary(info, dictVar, pos, rcvr, m, dst, false)
}
dot := ir.NewSelectorExpr(pos, ir.ODOTINTER, rcvr, m.(*ir.SelectorExpr).Sel)
dot.Selection = typecheck.Lookdot1(dot, dot.Sel, dot.X.Type(), dot.X.Type().AllMethods(), 1)
typed(dot.Selection.Type, dot)
innerCall = ir.NewCallExpr(pos, ir.OCALLINTER, dot, args)
t := m.Type()
if t.NumResults() == 0 {
innerCall.SetTypecheck(1)
} else if t.NumResults() == 1 {
typed(t.Results().Field(0).Type, innerCall)
} else {
typed(t.Results(), innerCall)
}
if len(formalResults) > 0 {
innerCall = ir.NewReturnStmt(pos, []ir.Node{innerCall})
innerCall.SetTypecheck(1)
}
fn.Body = []ir.Node{innerCall}
// We're all done with the captured dictionary
ir.FinishCaptureNames(pos, outer, fn)
// Do final checks on closure and return it.
return ir.UseClosure(fn.OClosure, typecheck.Target)
}