From 2140975ebde164ea1eaa70fc72775c03567f2bc9 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 27 Nov 2018 12:40:16 -0800 Subject: [PATCH] cmd/compile: eliminate write barriers when writing non-heap ptrs We don't need a write barrier if: 1) The location we're writing to doesn't hold a heap pointer, and 2) The value we're writing isn't a heap pointer. The freshly returned value from runtime.newobject satisfies (1). Pointers to globals, and the contents of the read-only data section satisfy (2). This is particularly helpful for code like: p := []string{"abc", "def", "ghi"} Where the compiler generates: a := new([3]string) move(a, statictmp_) // eliminates write barriers here p := a[:] For big slice literals, this makes the code a smaller and faster to compile. Update #13554. Reduces the compile time by ~10% and RSS by ~30%. Change-Id: Icab81db7591c8777f68e5d528abd48c7e44c87eb Reviewed-on: https://go-review.googlesource.com/c/151498 Run-TryBot: Keith Randall TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements Reviewed-by: Cherry Zhang --- src/cmd/compile/internal/ssa/writebarrier.go | 57 +++++++++++++++++++- test/writebarrier.go | 11 ++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/ssa/writebarrier.go b/src/cmd/compile/internal/ssa/writebarrier.go index 2366e0bfbf3..95816d2bdaa 100644 --- a/src/cmd/compile/internal/ssa/writebarrier.go +++ b/src/cmd/compile/internal/ssa/writebarrier.go @@ -24,6 +24,14 @@ func needwb(v *Value) bool { if IsStackAddr(v.Args[0]) { return false // write on stack doesn't need write barrier } + if v.Op == OpStore && IsGlobalAddr(v.Args[1]) && IsNewObject(v.Args[0], v.MemoryArg()) { + // Storing pointers to non-heap locations into a fresh object doesn't need a write barrier. + return false + } + if v.Op == OpMove && IsReadOnlyGlobalAddr(v.Args[1]) && IsNewObject(v.Args[0], v.MemoryArg()) { + // Copying data from readonly memory into a fresh object doesn't need a write barrier. + return false + } return true } @@ -353,7 +361,7 @@ func round(o int64, r int64) int64 { return (o + r - 1) &^ (r - 1) } -// IsStackAddr returns whether v is known to be an address of a stack slot +// IsStackAddr reports whether v is known to be an address of a stack slot. func IsStackAddr(v *Value) bool { for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { v = v.Args[0] @@ -365,6 +373,51 @@ func IsStackAddr(v *Value) bool { return false } +// IsGlobalAddr reports whether v is known to be an address of a global. +func IsGlobalAddr(v *Value) bool { + return v.Op == OpAddr && v.Args[0].Op == OpSB +} + +// IsReadOnlyGlobalAddr reports whether v is known to be an address of a read-only global. +func IsReadOnlyGlobalAddr(v *Value) bool { + if !IsGlobalAddr(v) { + return false + } + // See TODO in OpAddr case in IsSanitizerSafeAddr below. + return strings.HasPrefix(v.Aux.(*obj.LSym).Name, `"".statictmp_`) +} + +// IsNewObject reports whether v is a pointer to a freshly allocated & zeroed object at memory state mem. +// TODO: Be more precise. We really want "IsNilPointer" for the particular field in question. +// Right now, we can only detect a new object before any writes have been done to it. +// We could ignore non-pointer writes, writes to offsets which +// are known not to overlap the write in question, etc. +func IsNewObject(v *Value, mem *Value) bool { + if v.Op != OpLoad { + return false + } + if v.MemoryArg() != mem { + return false + } + if mem.Op != OpStaticCall { + return false + } + if !isSameSym(mem.Aux, "runtime.newobject") { + return false + } + if v.Args[0].Op != OpOffPtr { + return false + } + if v.Args[0].Args[0].Op != OpSP { + return false + } + c := v.Block.Func.Config + if v.Args[0].AuxInt != c.ctxt.FixedFrameSize()+c.RegSize { // offset of return value + return false + } + return true +} + // IsSanitizerSafeAddr reports whether v is known to be an address // that doesn't need instrumentation. func IsSanitizerSafeAddr(v *Value) bool { @@ -393,7 +446,7 @@ func IsSanitizerSafeAddr(v *Value) bool { return false } -// isVolatile returns whether v is a pointer to argument region on stack which +// isVolatile reports whether v is a pointer to argument region on stack which // will be clobbered by a function call. func isVolatile(v *Value) bool { for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy { diff --git a/test/writebarrier.go b/test/writebarrier.go index 55ba81e764e..8d262dd2037 100644 --- a/test/writebarrier.go +++ b/test/writebarrier.go @@ -250,3 +250,14 @@ func f23c() { // also test partial assignments t23 = T23{p: &i23} // ERROR "write barrier" } + +var g int + +func f24() **int { + p := new(*int) + *p = &g // no write barrier here + return p +} +func f25() []string { + return []string{"abc", "def", "ghi"} // no write barrier here +}