]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: avoid ifaceeq call if we know the interface is direct
authorKeith Randall <khr@golang.org>
Mon, 9 Dec 2024 20:55:33 +0000 (12:55 -0800)
committerGopher Robot <gobot@golang.org>
Mon, 10 Feb 2025 21:28:41 +0000 (13:28 -0800)
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>

src/cmd/compile/internal/reflectdata/reflect.go
src/cmd/compile/internal/ssa/_gen/generic.rules
src/cmd/compile/internal/ssa/rewrite.go
src/cmd/compile/internal/ssa/rewritegeneric.go
src/cmd/compile/internal/walk/compare.go
src/cmd/internal/obj/link.go
test/codegen/ifaces.go

index a4a701c9a2e54d4f9bd4c2beb885fabb59c69555..4202ff3358f51dbbca3a90cfc152261a5fddf428 100644 (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])
 }
 
index 8ad246830e386f6d5a52d7db3063ce3a6bfdd5ed..9188eff2ecc44896da7c25e6843556792bd09355 100644 (file)
 (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
 // 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)
index 5630bfd72934d74a9d1ce014bd9f3e020cf19203..383cb23dae4982f1ce39c84fbeea80842328eebb 100644 (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
+}
index fa771bf27ddea040f9647d21f9b1ff95d776710f..b3161ad50d444899af23ca1dcf349faab031539e 100644 (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 {
index 25160008ee90ba3c0ed3ee0e3534d88f64bd3780..d3a91f30b9e8d4528f16fad6a447f76ed2987242 100644 (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 {
index 1b2d344eaf8311c2e4017903e4d6e31f16993e64..6d6a5fd44df1a556e8577d9dc33bb7010091e017 100644 (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.
index 2be3fa5146a7acfc3ab1b82e9a4641f27f6c955f..cc67a047405445abb7bf47b1d294c6fd232bbe3b 100644 (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
+}