cmd/compile: avoid ifaceeq call if we know the interface is direct

We can just use == if the interface is direct.

Fixes #70738

Change-Id: Ia9a644791a370fec969c04c42d28a9b58f16911f
Reviewed-on: https://go-review.googlesource.com/c/go/+/635435
Auto-Submit: Keith Randall <khr@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Keith Randall 2024-12-09 12:55:33 -08:00 committed by Gopher Robot
parent c8664ced4e
commit 072eea9b3b
7 changed files with 230 additions and 8 deletions

View File

@ -592,11 +592,21 @@ func TypePtrAt(pos src.XPos, t *types.Type) *ir.AddrExpr {
// it may sometimes, but not always, be a type that can't implement the specified
// interface.
func ITabLsym(typ, iface *types.Type) *obj.LSym {
return itabLsym(typ, iface, true)
}
func itabLsym(typ, iface *types.Type, allowNonImplement bool) *obj.LSym {
s, existed := ir.Pkgs.Itab.LookupOK(typ.LinkString() + "," + iface.LinkString())
lsym := s.Linksym()
signatmu.Lock()
if lsym.Extra == nil {
ii := lsym.NewItabInfo()
ii.Type = typ
}
signatmu.Unlock()
if !existed {
writeITab(lsym, typ, iface, true)
writeITab(lsym, typ, iface, allowNonImplement)
}
return lsym
}
@ -605,13 +615,7 @@ func ITabLsym(typ, iface *types.Type) *obj.LSym {
// *runtime.itab value for concrete type typ implementing interface
// iface.
func ITabAddrAt(pos src.XPos, typ, iface *types.Type) *ir.AddrExpr {
s, existed := ir.Pkgs.Itab.LookupOK(typ.LinkString() + "," + iface.LinkString())
lsym := s.Linksym()
if !existed {
writeITab(lsym, typ, iface, false)
}
lsym := itabLsym(typ, iface, false)
return typecheck.LinksymAddr(pos, lsym, types.Types[types.TUINT8])
}

View File

@ -2072,6 +2072,11 @@
(NilCheck ptr:(Addr {_} (SB)) _) => ptr
(NilCheck ptr:(Convert (Addr {_} (SB)) _) _) => ptr
// Addresses of locals are always non-nil.
(NilCheck ptr:(LocalAddr _ _) _)
&& warnRule(fe.Debug_checknil(), v, "removed nil check")
=> ptr
// Nil checks of nil checks are redundant.
// See comment at the end of https://go-review.googlesource.com/c/go/+/537775.
(NilCheck ptr:(NilCheck _ _) _ ) => ptr
@ -2774,3 +2779,19 @@
// If we don't use the result of cmpstring, might as well not call it.
// Note that this could pretty easily generalize to any pure function.
(SelectN [1] c:(StaticLECall {f} _ _ mem)) && c.Uses == 1 && isSameCall(f, "runtime.cmpstring") && clobber(c) => mem
// We can easily compute the result of efaceeq if
// we know the underlying type is pointer-ish.
(StaticLECall {f} typ_ x y mem)
&& isSameCall(f, "runtime.efaceeq")
&& isDirectType(typ_)
&& clobber(v)
=> (MakeResult (EqPtr x y) mem)
// We can easily compute the result of ifaceeq if
// we know the underlying type is pointer-ish.
(StaticLECall {f} itab x y mem)
&& isSameCall(f, "runtime.ifaceeq")
&& isDirectIface(itab)
&& clobber(v)
=> (MakeResult (EqPtr x y) mem)

View File

@ -2424,3 +2424,86 @@ func rewriteStructStore(v *Value) *Value {
return mem
}
// isDirectType reports whether v represents a type
// (a *runtime._type) whose value is stored directly in an
// interface (i.e., is pointer or pointer-like).
func isDirectType(v *Value) bool {
return isDirectType1(v)
}
// v is a type
func isDirectType1(v *Value) bool {
switch v.Op {
case OpITab:
return isDirectType2(v.Args[0])
case OpAddr:
lsym := v.Aux.(*obj.LSym)
if lsym.Extra == nil {
return false
}
if ti, ok := (*lsym.Extra).(*obj.TypeInfo); ok {
return types.IsDirectIface(ti.Type.(*types.Type))
}
}
return false
}
// v is an empty interface
func isDirectType2(v *Value) bool {
switch v.Op {
case OpIMake:
return isDirectType1(v.Args[0])
}
return false
}
// isDirectIface reports whether v represents an itab
// (a *runtime._itab) for a type whose value is stored directly
// in an interface (i.e., is pointer or pointer-like).
func isDirectIface(v *Value) bool {
return isDirectIface1(v, 9)
}
// v is an itab
func isDirectIface1(v *Value, depth int) bool {
if depth == 0 {
return false
}
switch v.Op {
case OpITab:
return isDirectIface2(v.Args[0], depth-1)
case OpAddr:
lsym := v.Aux.(*obj.LSym)
if lsym.Extra == nil {
return false
}
if ii, ok := (*lsym.Extra).(*obj.ItabInfo); ok {
return types.IsDirectIface(ii.Type.(*types.Type))
}
case OpConstNil:
// We can treat this as direct, because if the itab is
// nil, the data field must be nil also.
return true
}
return false
}
// v is an interface
func isDirectIface2(v *Value, depth int) bool {
if depth == 0 {
return false
}
switch v.Op {
case OpIMake:
return isDirectIface1(v.Args[0], depth-1)
case OpPhi:
for _, a := range v.Args {
if !isDirectIface2(a, depth-1) {
return false
}
}
return true
}
return false
}

View File

@ -20678,6 +20678,17 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool {
v.copyOf(ptr)
return true
}
// match: (NilCheck ptr:(LocalAddr _ _) _)
// cond: warnRule(fe.Debug_checknil(), v, "removed nil check")
// result: ptr
for {
ptr := v_0
if ptr.Op != OpLocalAddr || !(warnRule(fe.Debug_checknil(), v, "removed nil check")) {
break
}
v.copyOf(ptr)
return true
}
// match: (NilCheck ptr:(NilCheck _ _) _ )
// result: ptr
for {
@ -30297,6 +30308,48 @@ func rewriteValuegeneric_OpStaticLECall(v *Value) bool {
v.AddArg2(v0, mem)
return true
}
// match: (StaticLECall {f} typ_ x y mem)
// cond: isSameCall(f, "runtime.efaceeq") && isDirectType(typ_) && clobber(v)
// result: (MakeResult (EqPtr x y) mem)
for {
if len(v.Args) != 4 {
break
}
f := auxToCall(v.Aux)
mem := v.Args[3]
typ_ := v.Args[0]
x := v.Args[1]
y := v.Args[2]
if !(isSameCall(f, "runtime.efaceeq") && isDirectType(typ_) && clobber(v)) {
break
}
v.reset(OpMakeResult)
v0 := b.NewValue0(v.Pos, OpEqPtr, typ.Bool)
v0.AddArg2(x, y)
v.AddArg2(v0, mem)
return true
}
// match: (StaticLECall {f} itab x y mem)
// cond: isSameCall(f, "runtime.ifaceeq") && isDirectIface(itab) && clobber(v)
// result: (MakeResult (EqPtr x y) mem)
for {
if len(v.Args) != 4 {
break
}
f := auxToCall(v.Aux)
mem := v.Args[3]
itab := v.Args[0]
x := v.Args[1]
y := v.Args[2]
if !(isSameCall(f, "runtime.ifaceeq") && isDirectIface(itab) && clobber(v)) {
break
}
v.reset(OpMakeResult)
v0 := b.NewValue0(v.Pos, OpEqPtr, typ.Bool)
v0.AddArg2(x, y)
v.AddArg2(v0, mem)
return true
}
return false
}
func rewriteValuegeneric_OpStore(v *Value) bool {

View File

@ -317,8 +317,17 @@ func walkCompare(n *ir.BinaryExpr, init *ir.Nodes) ir.Node {
}
func walkCompareInterface(n *ir.BinaryExpr, init *ir.Nodes) ir.Node {
swap := n.X.Op() != ir.OCONVIFACE && n.Y.Op() == ir.OCONVIFACE
n.Y = cheapExpr(n.Y, init)
n.X = cheapExpr(n.X, init)
if swap {
// Put the concrete type first in the comparison.
// This passes a constant type (itab) to efaceeq (ifaceeq)
// which is easier to match against in rewrite rules.
// See issue 70738.
n.X, n.Y = n.Y, n.X
}
eqtab, eqdata := compare.EqInterface(n.X, n.Y)
var cmp ir.Node
if n.Op() == ir.OEQ {

View File

@ -603,6 +603,22 @@ func (s *LSym) NewTypeInfo() *TypeInfo {
return t
}
// An ItabInfo contains information for a symbol
// that contains a runtime.itab.
type ItabInfo struct {
Type interface{} // a *cmd/compile/internal/types.Type
}
func (s *LSym) NewItabInfo() *ItabInfo {
if s.Extra != nil {
panic(fmt.Sprintf("invalid use of LSym - NewItabInfo with Extra of type %T", *s.Extra))
}
t := new(ItabInfo)
s.Extra = new(interface{})
*s.Extra = t
return t
}
// WasmImport represents a WebAssembly (WASM) imported function with
// parameters and results translated into WASM types based on the Go function
// declaration.

View File

@ -25,3 +25,39 @@ func ConvToM(x any) I {
// arm64:`CALL\truntime.typeAssert`,`LDAR`,`MOVWU`,`MOVD\t\(R.*\)\(R.*\)`
return x.(I)
}
func e1(x any, y *int) bool {
// amd64:-`.*faceeq`,`SETEQ`
// arm64:-`.*faceeq`,`CSET\tEQ`
return x == y
}
func e2(x any, y *int) bool {
// amd64:-`.*faceeq`,`SETEQ`
// arm64:-`.*faceeq`,`CSET\tEQ`
return y == x
}
type E *int
func e3(x any, y E) bool {
// amd64:-`.*faceeq`,`SETEQ`
// arm64:-`.*faceeq`,`CSET\tEQ`
return x == y
}
type T int
func (t *T) M() {}
func i1(x I, y *T) bool {
// amd64:-`.*faceeq`,`SETEQ`
// arm64:-`.*faceeq`,`CSET\tEQ`
return x == y
}
func i2(x I, y *T) bool {
// amd64:-`.*faceeq`,`SETEQ`
// arm64:-`.*faceeq`,`CSET\tEQ`
return y == x
}