]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: use method expression closures to implement bound method calls
authorKeith Randall <khr@golang.org>
Fri, 11 Feb 2022 20:58:37 +0000 (12:58 -0800)
committerKeith Randall <khr@golang.org>
Sat, 26 Mar 2022 20:36:29 +0000 (20:36 +0000)
When we have x.M(args) where x is a value of type parameter type, we
currently cast x to the bound of that type parameter (which is an interface)
and then invoke the method on that interface. That's pretty inefficient
because:
 1) We need to convert x to an interface, which often requires allocation.
    With CL 378178 it is at least stack allocation, but allocation nontheless.
 2) We need to call through wrapper functions to unpack the interface
    into the right argument locations for the callee.

Instead, let's just call the target directly. The previous CL to this one
added method expression closures to the dictionary, which is a simple
captureless closure that implements T.M for type parameter T and method M.
So to implement x.M(args) for x of type T, we use methodexpr(T,M)(x, args).
We just need to move x from the receiver slot to the first argument, and
use the dictionary entry to implement the polymorphism. This works because
we stencil by shape, so we know how to marshal x for the call even though
we don't know its exact type.

We should be able to revert CL 378178 after this one, as that optimization
will no longer be necssary as we're not converting values to interfaces
to implement this language construct anymore.

Update #50182

Change-Id: I813de4510e41ab63626e58bd1167f9ae93016202
Reviewed-on: https://go-review.googlesource.com/c/go/+/385274
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>

src/cmd/compile/internal/noder/stencil.go

index ee71a698e177dd9a88f00b01ce3c5244ac2bd270..477259f734b84e1a87d8382c0f8a2b6c5c87cf5d 100644 (file)
@@ -1217,8 +1217,14 @@ func (g *genInst) dictPass(info *instInfo) {
        savef := ir.CurFunc
        ir.CurFunc = info.fun
 
+       callMap := make(map[ir.Node]bool)
+
        var edit func(ir.Node) ir.Node
        edit = func(m ir.Node) ir.Node {
+               if m.Op() == ir.OCALL && m.(*ir.CallExpr).X.Op() == ir.OXDOT {
+                       callMap[m.(*ir.CallExpr).X] = true
+               }
+
                ir.EditChildren(m, edit)
 
                switch m.Op() {
@@ -1257,10 +1263,17 @@ func (g *genInst) dictPass(info *instInfo) {
                                // dictionary lookups - transformDot() will convert to
                                // the desired direct field access.
                                if isBoundMethod(info.dictInfo, mse) {
+                                       if callMap[m] {
+                                               // The OCALL surrounding this XDOT will rewrite the call
+                                               // to use the method expression closure directly.
+                                               break
+                                       }
+                                       // Convert this method value to a closure.
+                                       // TODO: use method expression closure.
                                        dst := info.dictInfo.shapeToBound[mse.X.Type()]
                                        // Implement x.M as a conversion-to-bound-interface
                                        //  1) convert x to the bound interface
-                                       //  2) call M on that interface
+                                       //  2) select method value M on that interface
                                        if src.IsInterface() {
                                                // If type arg is an interface (unusual case),
                                                // we do a type assert to the type bound.
@@ -1278,6 +1291,25 @@ func (g *genInst) dictPass(info *instInfo) {
                case ir.OCALL:
                        call := m.(*ir.CallExpr)
                        op := call.X.Op()
+                       if op == ir.OXDOT {
+                               // This is a call of a method value where the value has a type parameter type.
+                               // We transform to a call of the appropriate method expression closure
+                               // in the dictionary.
+                               // So if x has a type parameter type:
+                               //   _ = x.m(a)
+                               // Rewrite to:
+                               //   _ = methexpr<m>(x, a)
+                               se := call.X.(*ir.SelectorExpr)
+                               call.SetOp(ir.OCALLFUNC)
+                               idx := findMethodExprClosure(info.dictInfo, se)
+                               c := getDictionaryEntryAddr(se.Pos(), info.dictParam, info.dictInfo.startMethodExprClosures+idx, info.dictInfo.dictLen)
+                               t := typecheck.NewMethodType(se.Type(), se.X.Type())
+                               call.X = ir.NewConvExpr(se.Pos(), ir.OCONVNOP, t, c)
+                               typed(t, call.X)
+                               call.Args.Prepend(se.X)
+                               break
+                               // TODO: deref case?
+                       }
                        if op == ir.OMETHVALUE {
                                // Redo the transformation of OXDOT, now that we
                                // know the method value is being called.
@@ -1955,14 +1987,21 @@ 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() {
+                               // Method expression.
                                addMethodExprClosure(info, se)
                                break
                        }
                        if isBoundMethod(info, se) {
-                               // TODO: handle these using method expression closures also.
+                               if callMap[n] {
+                                       // Method value called directly. Use method expression closure.
+                                       addMethodExprClosure(info, se)
+                                       break
+                               }
+                               // Method value not called directly. Still doing the old way.
                                infoPrint("  Itab for bound call: %v\n", n)
                                info.itabConvs = append(info.itabConvs, n)
                        }
+
                case ir.ODOTTYPE, ir.ODOTTYPE2:
                        if !n.(*ir.TypeAssertExpr).Type().IsInterface() && !n.(*ir.TypeAssertExpr).X.Type().IsEmptyInterface() {
                                infoPrint("  Itab for dot type: %v\n", n)
@@ -2046,6 +2085,7 @@ func addMethodExprClosure(info *dictInfo, se *ir.SelectorExpr) {
                        return
                }
        }
+       infoPrint("  Method expression closure for %v.%s\n", info.shapeParams[idx], name)
        info.methodExprClosures = append(info.methodExprClosures, methodExprClosure{idx: idx, name: name})
 }