]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: eliminate write barriers when writing non-heap ptrs
authorKeith Randall <khr@google.com>
Tue, 27 Nov 2018 20:40:16 +0000 (12:40 -0800)
committerKeith Randall <khr@golang.org>
Thu, 29 Nov 2018 22:23:02 +0000 (22:23 +0000)
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 <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
src/cmd/compile/internal/ssa/writebarrier.go
test/writebarrier.go

index 2366e0bfbf358c2757891bb64abfa9925cdf39cd..95816d2bdaa949c11a9904dc01172af824e5789f 100644 (file)
@@ -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 {
index 55ba81e764e08cdb4dbbf859d55019090dbf4915..8d262dd203783131d6389e023d03fc881c62dd30 100644 (file)
@@ -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
+}