Skip to content

Commit a5fbdbe

Browse files
committed
WIP: Use MethodByName only with constants
We would like to enable dead-code-elimination, as unblocked by https://go-review.googlesource.com/c/go/+/522436
1 parent 1dc08db commit a5fbdbe

File tree

3 files changed

+86
-22
lines changed

3 files changed

+86
-22
lines changed

upup/pkg/fi/cloudup/awstasks/render_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/kops/upup/pkg/fi"
2727
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
2828
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
29+
"k8s.io/kops/util/pkg/reflectutils"
2930
)
3031

3132
type renderTest struct {
@@ -59,14 +60,14 @@ func doRenderTests(t *testing.T, method string, cases []*renderTest) {
5960

6061
err := func() error {
6162
// @step: invoke the rendering method of the target
62-
resp := reflect.ValueOf(c.Resource).MethodByName(method).Call(inputs)
63+
resp := reflectutils.GetMethodByName(reflect.ValueOf(c.Resource), method).Call(inputs)
6364
if err := resp[0].Interface(); err != nil {
6465
return err.(error)
6566
}
6667

6768
// @step: invoke the target finish up
6869
in := []reflect.Value{reflect.ValueOf(make(map[string]fi.CloudupTask))}
69-
resp = reflect.ValueOf(target).MethodByName("Finish").Call(in)
70+
resp = reflectutils.GetMethodByName(reflect.ValueOf(target), "Finish").Call(in)
7071
if err := resp[0].Interface(); err != nil {
7172
return err.(error)
7273
}

upup/pkg/fi/context.go

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"k8s.io/klog/v2"
2828
"k8s.io/kops/pkg/apis/kops"
2929
"k8s.io/kops/pkg/apis/nodeup"
30+
"k8s.io/kops/util/pkg/reflectutils"
3031
"k8s.io/kops/util/pkg/vfs"
3132
)
3233

@@ -196,19 +197,44 @@ func (c *Context[T]) Render(a, e, changes Task[T]) error {
196197

197198
targetType := reflect.ValueOf(c.Target).Type()
198199

199-
var renderer *reflect.Method
200+
var renderer *reflect.Value
200201
var rendererArgs []reflect.Value
201-
202-
for i := 0; i < vType.NumMethod(); i++ {
203-
method := vType.Method(i)
204-
if !strings.HasPrefix(method.Name, "Render") {
202+
rendererName := ""
203+
204+
renderMethodNames := []string{"Render"}
205+
206+
targetTypeName := fmt.Sprintf("%T", c.Target)
207+
switch targetTypeName {
208+
case "*awsup.AWSAPITarget":
209+
renderMethodNames = append(renderMethodNames, "RenderAWS")
210+
case "*azure.AzureAPITarget":
211+
renderMethodNames = append(renderMethodNames, "RenderAzure")
212+
case "*do.DOAPITarget":
213+
renderMethodNames = append(renderMethodNames, "RenderDO")
214+
case "*gce.GCEAPITarget":
215+
renderMethodNames = append(renderMethodNames, "RenderGCE")
216+
case "*hetzner.HetznerAPITarget":
217+
renderMethodNames = append(renderMethodNames, "RenderHetzner")
218+
case "*openstack.OpenstackAPITarget":
219+
renderMethodNames = append(renderMethodNames, "RenderOpenstack")
220+
case "*scaleway.ScwAPITarget":
221+
renderMethodNames = append(renderMethodNames, "RenderScw")
222+
case "*terraform.TerraformTarget":
223+
renderMethodNames = append(renderMethodNames, "RenderTerraform")
224+
default:
225+
panic(fmt.Sprintf("targetType %q is not recognized", targetTypeName))
226+
}
227+
for _, methodName := range renderMethodNames {
228+
method := reflectutils.GetMethodByName(v, methodName)
229+
if !method.IsValid() {
205230
continue
206231
}
207232
match := true
208233

234+
methodType := method.Type()
209235
var args []reflect.Value
210-
for j := 0; j < method.Type.NumIn(); j++ {
211-
arg := method.Type.In(j)
236+
for j := 0; j < methodType.NumIn(); j++ {
237+
arg := methodType.In(j)
212238
if arg.ConvertibleTo(vType) {
213239
continue
214240
}
@@ -225,27 +251,29 @@ func (c *Context[T]) Render(a, e, changes Task[T]) error {
225251
}
226252
if match {
227253
if renderer != nil {
228-
if method.Name == "Render" {
254+
if methodName == "Render" {
229255
continue
230256
}
231-
if renderer.Name != "Render" {
232-
return fmt.Errorf("found multiple Render methods that could be involved on %T", e)
257+
if rendererName != "Render" {
258+
return fmt.Errorf("found multiple Render methods that could be invoked on %T", e)
233259
}
234260
}
235261
renderer = &method
262+
rendererName = methodName
236263
rendererArgs = args
237264
}
238-
239265
}
266+
240267
if renderer == nil {
241268
return fmt.Errorf("could not find Render method on type %T (target %T)", e, c.Target)
242269
}
270+
243271
rendererArgs = append(rendererArgs, reflect.ValueOf(a))
244272
rendererArgs = append(rendererArgs, reflect.ValueOf(e))
245273
rendererArgs = append(rendererArgs, reflect.ValueOf(changes))
246-
klog.V(11).Infof("Calling method %s on %T", renderer.Name, e)
247-
m := v.MethodByName(renderer.Name)
248-
rv := m.Call(rendererArgs)
274+
klog.V(11).Infof("Calling method %s on %T", rendererName, e)
275+
// m := v.MethodByName(renderer.Name)
276+
rv := (*renderer).Call(rendererArgs)
249277
var rvErr error
250278
if !rv[0].IsNil() {
251279
rvErr = rv[0].Interface().(error)

util/pkg/reflectutils/walk.go

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,50 @@ func JSONMergeStruct(dest, src interface{}) {
5757
}
5858
}
5959

60+
// GetMethodByName implements a switch statement so that we can help the compiler with dead-code-elimination.
61+
// For background: https://github.com/golang/protobuf/issues/1561
62+
func GetMethodByName(reflectValue reflect.Value, methodName string) reflect.Value {
63+
switch methodName {
64+
case "CheckChanges":
65+
return reflectValue.MethodByName("CheckChanges")
66+
case "Find":
67+
return reflectValue.MethodByName("Find")
68+
case "ShouldCreate":
69+
return reflectValue.MethodByName("ShouldCreate")
70+
case "Finish":
71+
return reflectValue.MethodByName("Finish")
72+
case "Render":
73+
return reflectValue.MethodByName("Render")
74+
case "RenderAWS":
75+
return reflectValue.MethodByName("RenderAWS")
76+
case "RenderAzure":
77+
return reflectValue.MethodByName("RenderAzure")
78+
case "RenderDO":
79+
return reflectValue.MethodByName("RenderDO")
80+
case "RenderGCE":
81+
return reflectValue.MethodByName("RenderGCE")
82+
case "RenderHetzner":
83+
return reflectValue.MethodByName("RenderHetzner")
84+
case "RenderOpenstack":
85+
return reflectValue.MethodByName("RenderOpenstack")
86+
case "RenderScw":
87+
return reflectValue.MethodByName("RenderScw")
88+
case "RenderTerraform":
89+
return reflectValue.MethodByName("RenderTerraform")
90+
91+
default:
92+
panic(fmt.Sprintf("need to add %q to getMethodByName helper", methodName))
93+
}
94+
}
95+
6096
// InvokeMethod calls the specified method by reflection
61-
func InvokeMethod(target interface{}, name string, args ...interface{}) ([]reflect.Value, error) {
97+
func InvokeMethod(target interface{}, methodName string, args ...interface{}) ([]reflect.Value, error) {
6298
v := reflect.ValueOf(target)
6399

64-
method, found := v.Type().MethodByName(name)
65-
if !found {
100+
m := GetMethodByName(v, methodName)
101+
if !m.IsValid() {
66102
return nil, &MethodNotFoundError{
67-
Name: name,
103+
Name: methodName,
68104
Target: target,
69105
}
70106
}
@@ -73,8 +109,7 @@ func InvokeMethod(target interface{}, name string, args ...interface{}) ([]refle
73109
for _, a := range args {
74110
argValues = append(argValues, reflect.ValueOf(a))
75111
}
76-
klog.V(12).Infof("Calling method %s on %T", method.Name, target)
77-
m := v.MethodByName(method.Name)
112+
klog.V(12).Infof("Calling method %s on %T", methodName, target)
78113
rv := m.Call(argValues)
79114
return rv, nil
80115
}

0 commit comments

Comments
 (0)