diff --git a/src/cmd/compile/internal/ir/symtab.go b/src/cmd/compile/internal/ir/symtab.go index 00b07cb45c..e2da710f02 100644 --- a/src/cmd/compile/internal/ir/symtab.go +++ b/src/cmd/compile/internal/ir/symtab.go @@ -59,6 +59,7 @@ type symsStruct struct { Udiv *obj.LSym WriteBarrier *obj.LSym Zerobase *obj.LSym + ZeroVal *obj.LSym ARM64HasATOMICS *obj.LSym ARMHasVFPv4 *obj.LSym Loong64HasLAMCAS *obj.LSym diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 984dd138c3..542ad823ab 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -166,6 +166,7 @@ func InitConfig() { ir.Syms.Udiv = typecheck.LookupRuntimeVar("udiv") // asm func with special ABI ir.Syms.WriteBarrier = typecheck.LookupRuntimeVar("writeBarrier") // struct { bool; ... } ir.Syms.Zerobase = typecheck.LookupRuntimeVar("zerobase") + ir.Syms.ZeroVal = typecheck.LookupRuntimeVar("zeroVal") if Arch.LinkArch.Family == sys.Wasm { BoundsCheckFunc[ssa.BoundsIndex] = typecheck.LookupRuntimeFunc("goPanicIndex") diff --git a/src/cmd/compile/internal/walk/convert.go b/src/cmd/compile/internal/walk/convert.go index 4c443f71b9..beef6634a5 100644 --- a/src/cmd/compile/internal/walk/convert.go +++ b/src/cmd/compile/internal/walk/convert.go @@ -175,6 +175,11 @@ func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node { xe := ir.NewIndexExpr(base.Pos, staticuint64s, index) xe.SetBounded(true) value = xe + case n.Op() == ir.OLINKSYMOFFSET && n.(*ir.LinksymOffsetExpr).Linksym == ir.Syms.ZeroVal && n.(*ir.LinksymOffsetExpr).Offset_ == 0: + // n is using zeroVal, so we can use n directly. + // (Note that n does not have a proper pos in this case, so using conv for the diagnostic instead.) + diagnose("using global for zero value interface value", conv) + value = n case n.Op() == ir.ONAME && n.(*ir.Name).Class == ir.PEXTERN && n.(*ir.Name).Readonly(): // n is a readonly global; use it directly. diagnose("using global for interface value", n) diff --git a/src/cmd/compile/internal/walk/order.go b/src/cmd/compile/internal/walk/order.go index af3bfcbac6..77322286c7 100644 --- a/src/cmd/compile/internal/walk/order.go +++ b/src/cmd/compile/internal/walk/order.go @@ -7,6 +7,7 @@ package walk import ( "fmt" "go/constant" + "internal/abi" "internal/buildcfg" "cmd/compile/internal/base" @@ -240,19 +241,26 @@ func (o *orderState) addrTemp(n ir.Node) ir.Node { return vstat } - // Check now for a composite literal to possibly store - // in the read-only data section. + // Check now for a composite literal to possibly store in the read-only data section. v := staticValue(n) if v == nil { v = n } - if (v.Op() == ir.OSTRUCTLIT || v.Op() == ir.OARRAYLIT) && isStaticCompositeLiteral(v) && !base.Ctxt.IsFIPS() { - // v can be directly represented in the read-only data section. - lit := v.(*ir.CompLitExpr) - vstat := readonlystaticname(lit.Type()) - fixedlit(inInitFunction, initKindStatic, lit, vstat, nil) // nil init - vstat = typecheck.Expr(vstat).(*ir.Name) - return vstat + if (v.Op() == ir.OSTRUCTLIT || v.Op() == ir.OARRAYLIT) && !base.Ctxt.IsFIPS() { + if ir.IsZero(v) && 0 < v.Type().Size() && v.Type().Size() <= abi.ZeroValSize { + // This zero value can be represented by the read-only zeroVal. + zeroVal := ir.NewLinksymExpr(v.Pos(), ir.Syms.ZeroVal, v.Type()) + vstat := typecheck.Expr(zeroVal).(*ir.LinksymOffsetExpr) + return vstat + } + if isStaticCompositeLiteral(v) { + // v can be directly represented in the read-only data section. + lit := v.(*ir.CompLitExpr) + vstat := readonlystaticname(lit.Type()) + fixedlit(inInitFunction, initKindStatic, lit, vstat, nil) // nil init + vstat = typecheck.Expr(vstat).(*ir.Name) + return vstat + } } // Prevent taking the address of an SSA-able local variable (#63332). diff --git a/src/reflect/benchmark_test.go b/src/reflect/benchmark_test.go index 2e701b062e..6b2f9ce7a0 100644 --- a/src/reflect/benchmark_test.go +++ b/src/reflect/benchmark_test.go @@ -9,6 +9,7 @@ import ( . "reflect" "strconv" "testing" + "time" ) var sourceAll = struct { @@ -196,6 +197,57 @@ func BenchmarkSetZero(b *testing.B) { } } +// BenchmarkZero overlaps some with BenchmarkSetZero, +// but the inputs are set up differently to exercise +// different optimizations. +func BenchmarkZero(b *testing.B) { + type bm struct { + name string + zero Value + nonZero Value + size int + } + type Small struct { + A int64 + B, C bool + } + type Big struct { + A int64 + B, C bool + D [1008]byte + } + entry := func(name string, zero any, nonZero any) bm { + return bm{name, ValueOf(zero), ValueOf(nonZero).Elem(), int(TypeOf(zero).Size())} + } + nonZeroTime := func() *time.Time { t := time.Now(); return &t } + + bms := []bm{ + entry("ByteArray", [16]byte{}, &[16]byte{1}), + entry("ByteArray", [64]byte{}, &[64]byte{1}), + entry("ByteArray", [1024]byte{}, &[1024]byte{1}), + entry("BigStruct", Big{}, &Big{A: 1}), + entry("SmallStruct", Small{}, &Small{A: 1}), + entry("SmallStructArray", [4]Small{}, &[4]Small{0: {A: 1}}), + entry("SmallStructArray", [64]Small{}, &[64]Small{0: {A: 1}}), + entry("Time", time.Time{}, nonZeroTime()), + } + + for _, bm := range bms { + b.Run(fmt.Sprintf("IsZero/%s/size=%d", bm.name, bm.size), func(b *testing.B) { + for i := 0; i < b.N; i++ { + bm.zero.IsZero() + } + }) + } + for _, bm := range bms { + b.Run(fmt.Sprintf("SetZero/%s/size=%d", bm.name, bm.size), func(b *testing.B) { + for i := 0; i < b.N; i++ { + bm.nonZero.Set(bm.zero) + } + }) + } +} + func BenchmarkSelect(b *testing.B) { channel := make(chan int) close(channel) diff --git a/test/escape_iface_data.go b/test/escape_iface_data.go index fd993fb892..b42974c486 100644 --- a/test/escape_iface_data.go +++ b/test/escape_iface_data.go @@ -217,12 +217,12 @@ func struct2() { } func struct3() { - sink = S{} // ERROR "using global for interface value" + sink = S{} // ERROR "using global for zero value interface value" } func struct4() { v := S{} - sink = v // ERROR "using global for interface value" + sink = v // ERROR "using global for zero value interface value" } func struct5() {