diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index a4a701c9a2..4202ff3358 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -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]) } diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules index 8ad246830e..9188eff2ec 100644 --- a/src/cmd/compile/internal/ssa/_gen/generic.rules +++ b/src/cmd/compile/internal/ssa/_gen/generic.rules @@ -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) diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index 5630bfd729..383cb23dae 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -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 +} diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index fa771bf27d..b3161ad50d 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -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 { diff --git a/src/cmd/compile/internal/walk/compare.go b/src/cmd/compile/internal/walk/compare.go index 25160008ee..d3a91f30b9 100644 --- a/src/cmd/compile/internal/walk/compare.go +++ b/src/cmd/compile/internal/walk/compare.go @@ -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 { diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index 1b2d344eaf..6d6a5fd44d 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -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. diff --git a/test/codegen/ifaces.go b/test/codegen/ifaces.go index 2be3fa5146..cc67a04740 100644 --- a/test/codegen/ifaces.go +++ b/test/codegen/ifaces.go @@ -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 +}