Skip to content

Commit

Permalink
cmd/compile: implement generic method expressions with closures in di…
Browse files Browse the repository at this point in the history
…ctionary

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 <[email protected]>
Run-TryBot: Keith Randall <[email protected]>
Reviewed-by: Matthew Dempsky <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
randall77 committed Mar 26, 2022
1 parent 0652274 commit 7fc3880
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 76 deletions.
23 changes: 17 additions & 6 deletions src/cmd/compile/internal/noder/irgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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().
Expand Down
154 changes: 84 additions & 70 deletions src/cmd/compile/internal/noder/stencil.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)
}

0 comments on commit 7fc3880

Please sign in to comment.