[go] cmd/compile: add missing usemethod checks for reflect method iterators

1 view
Skip to first unread message

Gerrit Bot (Gerrit)

unread,
Jan 17, 2026, 6:08:05 PM (15 hours ago) Jan 17
to goph...@pubsubhelper.golang.org, Quentin Quaadgras, golang-co...@googlegroups.com

Gerrit Bot has uploaded the change for review

Commit message

cmd/compile: add missing usemethod checks for reflect method iterators

Required for dead-code elimination.

Fixes #77222
Change-Id: I34447352deba19d17611bfe89f4cd22a939543d8
GitHub-Last-Rev: 5f25334b95f26696036c16139037d41440f107fc
GitHub-Pull-Request: golang/go#77225

Change diff

diff --git a/src/cmd/compile/internal/walk/expr.go b/src/cmd/compile/internal/walk/expr.go
index 2794671..76eb7e8 100644
--- a/src/cmd/compile/internal/walk/expr.go
+++ b/src/cmd/compile/internal/walk/expr.go
@@ -1017,7 +1017,7 @@
return false
}

-// usemethod checks calls for uses of Method and MethodByName of reflect.Value,
+// usemethod checks calls for uses of Method, Methods and MethodByName of reflect.Value,
// reflect.Type, reflect.(*rtype), and reflect.(*interfaceType).
func usemethod(n *ir.CallExpr) {
// Don't mark reflect.(*rtype).Method, etc. themselves in the reflect package.
@@ -1025,12 +1025,12 @@
// alive. We only want to mark their callers.
if base.Ctxt.Pkgpath == "reflect" {
// TODO: is there a better way than hardcoding the names?
- switch fn := ir.CurFunc.Nname.Sym().Name; {
- case fn == "(*rtype).Method", fn == "(*rtype).MethodByName":
+ switch ir.CurFunc.Nname.Sym().Name {
+ case "(*rtype).Method", "(*rtype).MethodByName", "(*rtype).Methods.func1":
return
- case fn == "(*interfaceType).Method", fn == "(*interfaceType).MethodByName":
+ case "(*interfaceType).Method", "(*interfaceType).MethodByName":
return
- case fn == "Value.Method", fn == "Value.MethodByName":
+ case "Value.Method", "Value.MethodByName", "Value.Methods.func1", "(*Value).Methods.Value.Methods.func1":
return
}
}
@@ -1042,13 +1042,53 @@

// looking for either direct method calls and interface method calls of:
// reflect.Type.Method - func(int) reflect.Method
+ // reflect.Type.Methods - func() iter.Seq[reflect.Method]
// reflect.Type.MethodByName - func(string) (reflect.Method, bool)
//
// reflect.Value.Method - func(int) reflect.Value
+ // reflect.Value.Methods - func() iter.Seq2[reflect.Method, reflect.Value]
// reflect.Value.MethodByName - func(string) reflect.Value
methodName := dot.Sel.Name
t := dot.Selection.Type

+ if methodName == "Methods" && t.NumParams() == 0 && t.NumResults() == 1 {
+ // iter.Seq[V] is defined as: func(yield func(V) bool)
+ // iter.Seq2[K, V] is defined as: func(yield func(K, V) bool)
+ res := t.Result(0).Type
+ sym := res.Sym()
+ if sym == nil || sym.Pkg == nil || sym.Pkg.Path != "iter" {
+ return
+ }
+ underlying := res.Underlying()
+ if underlying.Kind() != types.TFUNC || underlying.NumParams() != 1 {
+ return
+ }
+ yieldParam := underlying.Param(0).Type
+ if yieldParam.Kind() != types.TFUNC {
+ return
+ }
+ if yieldParam.NumResults() != 1 || yieldParam.Result(0).Type.Kind() != types.TBOOL {
+ return
+ }
+ switch yieldParam.NumParams() {
+ case 1:
+ if ps := yieldParam.Param(0).Type.Sym(); ps == nil || types.ReflectSymName(ps) != "Method" {
+ return
+ }
+ case 2:
+ if ps := yieldParam.Param(0).Type.Sym(); ps == nil || types.ReflectSymName(ps) != "Method" {
+ return
+ }
+ if ps := yieldParam.Param(1).Type.Sym(); ps == nil || types.ReflectSymName(ps) != "Value" {
+ return
+ }
+ default:
+ return
+ }
+ ir.CurFunc.LSym.Set(obj.AttrReflectMethod, true)
+ return
+ }
+
// Check the number of arguments and return values.
if t.NumParams() != 1 || (t.NumResults() != 1 && t.NumResults() != 2) {
return

Change information

Files:
  • M src/cmd/compile/internal/walk/expr.go
Change size: M
Delta: 1 file changed, 45 insertions(+), 5 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I34447352deba19d17611bfe89f4cd22a939543d8
Gerrit-Change-Number: 737123
Gerrit-PatchSet: 1
Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
Gerrit-CC: Quentin Quaadgras <que...@splizard.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Gopher Robot (Gerrit)

unread,
Jan 17, 2026, 6:11:21 PM (15 hours ago) Jan 17
to Quentin Quaadgras, Gerrit Bot, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Message from Gopher Robot

Congratulations on opening your first change. Thank you for your contribution!

Next steps:
A maintainer will review your change and provide feedback. See
https://go.dev/doc/contribute#review for more info and tips to get your
patch through code review.

Most changes in the Go project go through a few rounds of revision. This can be
surprising to people new to the project. The careful, iterative review process
is our way of helping mentor contributors and ensuring that their contributions
have a lasting impact.

During May-July and Nov-Jan the Go project is in a code freeze, during which
little code gets reviewed or merged. If a reviewer responds with a comment like
R=go1.11 or adds a tag like "wait-release", it means that this CL will be
reviewed as part of the next development cycle. See https://go.dev/s/release
for more details.

Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I34447352deba19d17611bfe89f4cd22a939543d8
Gerrit-Change-Number: 737123
Gerrit-PatchSet: 1
Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-CC: Quentin Quaadgras <que...@splizard.com>
Gerrit-Comment-Date: Sat, 17 Jan 2026 23:11:15 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: No
unsatisfied_requirement
satisfied_requirement
open
diffy

Cherry Mui (Gerrit)

unread,
Jan 17, 2026, 7:48:12 PM (13 hours ago) Jan 17
to Quentin Quaadgras, Gerrit Bot, goph...@pubsubhelper.golang.org, Robert Griesemer, Matthew Dempsky, Martin Möhrmann, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Martin Möhrmann, Matthew Dempsky and Robert Griesemer

Cherry Mui added 1 comment

File src/cmd/compile/internal/walk/expr.go
Line 1020, Patchset 1 (Latest):// usemethod checks calls for uses of Method, Methods and MethodByName of reflect.Value,
Cherry Mui . unresolved

`Methods` calls `Method`. So if the former is reachable, so is the latter. The check of `Method` should suffice, and this shouldn't be necessary.

Open in Gerrit

Related details

Attention is currently required from:
  • Martin Möhrmann
  • Matthew Dempsky
  • Robert Griesemer
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: I34447352deba19d17611bfe89f4cd22a939543d8
    Gerrit-Change-Number: 737123
    Gerrit-PatchSet: 1
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-Reviewer: Martin Möhrmann <moeh...@google.com>
    Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
    Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
    Gerrit-CC: Cherry Mui <cher...@google.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-CC: Quentin Quaadgras <que...@splizard.com>
    Gerrit-Attention: Martin Möhrmann <moeh...@google.com>
    Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
    Gerrit-Attention: Robert Griesemer <g...@golang.org>
    Gerrit-Comment-Date: Sun, 18 Jan 2026 00:48:09 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    open
    diffy

    Quentin Quaadgras (Gerrit)

    unread,
    Jan 17, 2026, 8:09:56 PM (13 hours ago) Jan 17
    to Gerrit Bot, goph...@pubsubhelper.golang.org, Cherry Mui, Robert Griesemer, Matthew Dempsky, Martin Möhrmann, Gopher Robot, golang-co...@googlegroups.com
    Attention needed from Martin Möhrmann, Matthew Dempsky and Robert Griesemer

    Quentin Quaadgras added 1 comment

    File src/cmd/compile/internal/walk/expr.go
    Line 1020, Patchset 1 (Latest):// usemethod checks calls for uses of Method, Methods and MethodByName of reflect.Value,
    Cherry Mui . unresolved

    `Methods` calls `Method`. So if the former is reachable, so is the latter. The check of `Method` should suffice, and this shouldn't be necessary.

    Quentin Quaadgras

    That's the issue isn't it? If Type.Methods is alive in the itab and never called, this causes all methods on all types to be kept by the linker. Type.Methods is being marked as <ReflectMethod>, when it shouldn't be (only external callers should be marked).

    "Those functions may be alive via the itab, which should not cause all methods


    alive. We only want to mark their callers."

    Why wouldn't Methods need to be treated the same way? Note that Methods returns a function that calls Method (it doesn't call Method itself).

    I've confirmed that after this change, -dumpdep no longer shows <ReflectMethod> when building the reproduction sample from https://go.dev/issue/77222

    Gerrit-Comment-Date: Sun, 18 Jan 2026 01:09:45 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    Comment-In-Reply-To: Cherry Mui <cher...@google.com>
    unsatisfied_requirement
    open
    diffy

    Cherry Mui (Gerrit)

    unread,
    Jan 17, 2026, 9:00:57 PM (12 hours ago) Jan 17
    to Quentin Quaadgras, Gerrit Bot, goph...@pubsubhelper.golang.org, Robert Griesemer, Matthew Dempsky, Martin Möhrmann, Gopher Robot, golang-co...@googlegroups.com
    Attention needed from Martin Möhrmann, Matthew Dempsky and Robert Griesemer

    Cherry Mui added 1 comment

    File src/cmd/compile/internal/walk/expr.go
    Line 1020, Patchset 1 (Latest):// usemethod checks calls for uses of Method, Methods and MethodByName of reflect.Value,
    Cherry Mui . unresolved

    `Methods` calls `Method`. So if the former is reachable, so is the latter. The check of `Method` should suffice, and this shouldn't be necessary.

    Quentin Quaadgras

    That's the issue isn't it? If Type.Methods is alive in the itab and never called, this causes all methods on all types to be kept by the linker. Type.Methods is being marked as <ReflectMethod>, when it shouldn't be (only external callers should be marked).

    "Those functions may be alive via the itab, which should not cause all methods
    alive. We only want to mark their callers."

    Why wouldn't Methods need to be treated the same way? Note that Methods returns a function that calls Method (it doesn't call Method itself).

    I've confirmed that after this change, -dumpdep no longer shows <ReflectMethod> when building the reproduction sample from https://go.dev/issue/77222

    Cherry Mui

    Type.Methods is being marked as <ReflectMethod>. when it shouldn't be

    It should. Because using `Type.Methods` does make it possible to reach all methods through reflection.

    (only external callers should be marked)

    Why? It does not have to be "external". Call from the reflect package is can have the same effect as a call in user code. `Type.Methods` is listed in the itab doesn't necessarily make it alive (otherwise all methods on all types are alive anyway). It is only alive if it is used directly, used through an interface call, r there is a `MethodByName("Methods")` that is reachable in the program, or through some other mechanism which causes all methods alive.

    E.g. https://go.dev/play/p/aHXTWkPORWV?v=gotip breaks by the CL. This example is a bit contrived, but it shows that `Methods` does need to be treated as `<ReflectMethod>`.

    Note that Methods returns a function that calls Method (it doesn't call Method itself).

    Sorry, I should be more precise in my previous comment. From a reachability perspective, there is no difference: if `Methods` is reachable, so is `Method`. The linker does reachability analysis.

    Gerrit-Comment-Date: Sun, 18 Jan 2026 02:00:53 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    Comment-In-Reply-To: Cherry Mui <cher...@google.com>
    Comment-In-Reply-To: Quentin Quaadgras <que...@splizard.com>
    unsatisfied_requirement
    open
    diffy

    Gerrit Bot (Gerrit)

    unread,
    Jan 17, 2026, 11:14:00 PM (10 hours ago) Jan 17
    to Quentin Quaadgras, goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
    Attention needed from Martin Möhrmann, Matthew Dempsky and Robert Griesemer

    Gerrit Bot uploaded new patchset

    Gerrit Bot uploaded patch set #2 to this change.
    Open in Gerrit

    Related details

    Attention is currently required from:
    • Martin Möhrmann
    • Matthew Dempsky
    • Robert Griesemer
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: newpatchset
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: I34447352deba19d17611bfe89f4cd22a939543d8
    Gerrit-Change-Number: 737123
    Gerrit-PatchSet: 2
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-Reviewer: Martin Möhrmann <moeh...@google.com>
    Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
    Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
    Gerrit-CC: Cherry Mui <cher...@google.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-CC: Quentin Quaadgras <que...@splizard.com>
    Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
    Gerrit-Attention: Martin Möhrmann <moeh...@google.com>
    Gerrit-Attention: Robert Griesemer <g...@golang.org>
    unsatisfied_requirement
    open
    diffy

    Quentin Quaadgras (Gerrit)

    unread,
    Jan 17, 2026, 11:20:21 PM (9 hours ago) Jan 17
    to Gerrit Bot, goph...@pubsubhelper.golang.org, Cherry Mui, Robert Griesemer, Matthew Dempsky, Martin Möhrmann, Gopher Robot, golang-co...@googlegroups.com
    Attention needed from Martin Möhrmann, Matthew Dempsky and Robert Griesemer

    Quentin Quaadgras added 1 comment

    File src/cmd/compile/internal/walk/expr.go
    Line 1020, Patchset 1:// usemethod checks calls for uses of Method, Methods and MethodByName of reflect.Value,
    Cherry Mui . unresolved

    `Methods` calls `Method`. So if the former is reachable, so is the latter. The check of `Method` should suffice, and this shouldn't be necessary.

    Quentin Quaadgras

    That's the issue isn't it? If Type.Methods is alive in the itab and never called, this causes all methods on all types to be kept by the linker. Type.Methods is being marked as <ReflectMethod>, when it shouldn't be (only external callers should be marked).

    "Those functions may be alive via the itab, which should not cause all methods
    alive. We only want to mark their callers."

    Why wouldn't Methods need to be treated the same way? Note that Methods returns a function that calls Method (it doesn't call Method itself).

    I've confirmed that after this change, -dumpdep no longer shows <ReflectMethod> when building the reproduction sample from https://go.dev/issue/77222

    Cherry Mui

    Type.Methods is being marked as <ReflectMethod>. when it shouldn't be

    It should. Because using `Type.Methods` does make it possible to reach all methods through reflection.

    (only external callers should be marked)

    Why? It does not have to be "external". Call from the reflect package is can have the same effect as a call in user code. `Type.Methods` is listed in the itab doesn't necessarily make it alive (otherwise all methods on all types are alive anyway). It is only alive if it is used directly, used through an interface call, r there is a `MethodByName("Methods")` that is reachable in the program, or through some other mechanism which causes all methods alive.

    E.g. https://go.dev/play/p/aHXTWkPORWV?v=gotip breaks by the CL. This example is a bit contrived, but it shows that `Methods` does need to be treated as `<ReflectMethod>`.

    Note that Methods returns a function that calls Method (it doesn't call Method itself).

    Sorry, I should be more precise in my previous comment. From a reachability perspective, there is no difference: if `Methods` is reachable, so is `Method`. The linker does reachability analysis.

    Quentin Quaadgras

    Taking a look at your comment on https://go.dev/issue/77222, it does seem like this may be more of an unfortunate coincidence than a bug in e8fdfeb7 (where I introduced the iterator methods). Whew!

    Why? It does not have to be "external".

    Sorry, I was just meaning to say that only callers outside of this exceptional group of functions should be marked.

    It should. Because using Type.Methods does make it possible to reach all methods through reflection.

    The same goes for the other exceptions here, doesn't it? Are you suggesting that the entire switch case is redundant? (`TestDeadcode/ifacemethod6` and `TestDeadcode/structof_funcof` fail without the switch).

    Typically, yes, that's possible but I think it should also be possible to have the expression `reflect.TypeFor[T]().Methods()` only keep alive the methods of T. In order to implement such an optimization, wouldn't this require these exceptions?

    After learning about this code, I'm keen to implement something like this (which I would expect needs to go into a new CR). Do I need to open an issue on GH to discuss this change first?

    Regardless, I added your example as a test-case and fixed it by specifically checking for `"Methods"` & `"Method"` constants for `MethodByName`. It's now clearer in the `-dumpdep` output that the cause of the issue is the protobuf package calling `MethodByName`.

    `google.golang.org/protobuf/internal/descfmt.formatDescOpt <ReflectMethod>`
    https://github.com/protocolbuffers/protobuf-go/blob/96a179180f0ad6bba9b1e7b6e38d0affb0168e9a/internal/descfmt/stringer.go#L265

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Martin Möhrmann
    • Matthew Dempsky
    • Robert Griesemer
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement is not satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-Change-Id: I34447352deba19d17611bfe89f4cd22a939543d8
    Gerrit-Change-Number: 737123
    Gerrit-PatchSet: 1
    Gerrit-Owner: Gerrit Bot <letsus...@gmail.com>
    Gerrit-Reviewer: Martin Möhrmann <moeh...@google.com>
    Gerrit-Reviewer: Matthew Dempsky <mat...@go.dev>
    Gerrit-Reviewer: Robert Griesemer <g...@golang.org>
    Gerrit-CC: Cherry Mui <cher...@google.com>
    Gerrit-CC: Gopher Robot <go...@golang.org>
    Gerrit-CC: Quentin Quaadgras <que...@splizard.com>
    Gerrit-Attention: Matthew Dempsky <mat...@go.dev>
    Gerrit-Attention: Martin Möhrmann <moeh...@google.com>
    Gerrit-Attention: Robert Griesemer <g...@golang.org>
    Gerrit-Comment-Date: Sun, 18 Jan 2026 04:20:12 +0000
    unsatisfied_requirement
    open
    diffy
    Reply all
    Reply to author
    Forward
    0 new messages