Commit d4663e13 authored by Keith Randall's avatar Keith Randall

cmd/compile: don't write back unchanged slice results

Don't write back parts of a slicing operation if they
are unchanged from the source of the slice.  For example:

x.s = x.s[0:5]         // don't write back pointer or cap
x.s = x.s[:5]          // don't write back pointer or cap
x.s = x.s[:5:7]        // don't write back pointer

There is more to be done here, for example:

x.s = x.s[:len(x.s):7] // don't write back ptr or len

This CL can't handle that one yet.

Fixes #14855

Change-Id: Id1e1a4fa7f3076dc1a76924a7f1cd791b81909bb
Reviewed-on: https://go-review.googlesource.com/20954Reviewed-by: default avatarAustin Clements <austin@google.com>
Run-TryBot: Keith Randall <khr@golang.org>
parent 9549c06c
...@@ -562,8 +562,8 @@ func (s *state) stmt(n *Node) { ...@@ -562,8 +562,8 @@ func (s *state) stmt(n *Node) {
case OAS2DOTTYPE: case OAS2DOTTYPE:
res, resok := s.dottype(n.Rlist.First(), true) res, resok := s.dottype(n.Rlist.First(), true)
s.assign(n.List.First(), res, needwritebarrier(n.List.First(), n.Rlist.First()), false, n.Lineno) s.assign(n.List.First(), res, needwritebarrier(n.List.First(), n.Rlist.First()), false, n.Lineno, 0)
s.assign(n.List.Second(), resok, false, false, n.Lineno) s.assign(n.List.Second(), resok, false, false, n.Lineno, 0)
return return
case ODCL: case ODCL:
...@@ -584,7 +584,7 @@ func (s *state) stmt(n *Node) { ...@@ -584,7 +584,7 @@ func (s *state) stmt(n *Node) {
prealloc[n.Left] = palloc prealloc[n.Left] = palloc
} }
r := s.expr(palloc) r := s.expr(palloc)
s.assign(n.Left.Name.Heapaddr, r, false, false, n.Lineno) s.assign(n.Left.Name.Heapaddr, r, false, false, n.Lineno, 0)
case OLABEL: case OLABEL:
sym := n.Left.Sym sym := n.Left.Sym
...@@ -698,7 +698,44 @@ func (s *state) stmt(n *Node) { ...@@ -698,7 +698,44 @@ func (s *state) stmt(n *Node) {
needwb = true needwb = true
} }
s.assign(n.Left, r, needwb, deref, n.Lineno) var skip skipMask
if rhs != nil && (rhs.Op == OSLICE || rhs.Op == OSLICE3 || rhs.Op == OSLICESTR) && samesafeexpr(rhs.Left, n.Left) {
// We're assigning a slicing operation back to its source.
// Don't write back fields we aren't changing. See issue #14855.
i := rhs.Right.Left
var j, k *Node
if rhs.Op == OSLICE3 {
j = rhs.Right.Right.Left
k = rhs.Right.Right.Right
} else {
j = rhs.Right.Right
}
if i != nil && (i.Op == OLITERAL && i.Val().Ctype() == CTINT && i.Val().U.(*Mpint).Int64() == 0) {
// [0:...] is the same as [:...]
i = nil
}
// TODO: detect defaults for len/cap also.
// Currently doesn't really work because (*p)[:len(*p)] appears here as:
// tmp = len(*p)
// (*p)[:tmp]
//if j != nil && (j.Op == OLEN && samesafeexpr(j.Left, n.Left)) {
// j = nil
//}
//if k != nil && (k.Op == OCAP && samesafeexpr(k.Left, n.Left)) {
// k = nil
//}
if i == nil {
skip |= skipPtr
if j == nil {
skip |= skipLen
}
if k == nil {
skip |= skipCap
}
}
}
s.assign(n.Left, r, needwb, deref, n.Lineno, skip)
case OIF: case OIF:
bThen := s.f.NewBlock(ssa.BlockPlain) bThen := s.f.NewBlock(ssa.BlockPlain)
...@@ -2091,7 +2128,7 @@ func (s *state) expr(n *Node) *ssa.Value { ...@@ -2091,7 +2128,7 @@ func (s *state) expr(n *Node) *ssa.Value {
addr := s.newValue2(ssa.OpPtrIndex, pt, p2, s.constInt(Types[TINT], int64(i))) addr := s.newValue2(ssa.OpPtrIndex, pt, p2, s.constInt(Types[TINT], int64(i)))
if store[i] { if store[i] {
if haspointers(et) { if haspointers(et) {
s.insertWBstore(et, addr, arg, n.Lineno) s.insertWBstore(et, addr, arg, n.Lineno, 0)
} else { } else {
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, et.Size(), addr, arg, s.mem()) s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, et.Size(), addr, arg, s.mem())
} }
...@@ -2159,12 +2196,21 @@ func (s *state) condBranch(cond *Node, yes, no *ssa.Block, likely int8) { ...@@ -2159,12 +2196,21 @@ func (s *state) condBranch(cond *Node, yes, no *ssa.Block, likely int8) {
b.AddEdgeTo(no) b.AddEdgeTo(no)
} }
type skipMask uint8
const (
skipPtr skipMask = 1 << iota
skipLen
skipCap
)
// assign does left = right. // assign does left = right.
// Right has already been evaluated to ssa, left has not. // Right has already been evaluated to ssa, left has not.
// If deref is true, then we do left = *right instead (and right has already been nil-checked). // If deref is true, then we do left = *right instead (and right has already been nil-checked).
// If deref is true and right == nil, just do left = 0. // If deref is true and right == nil, just do left = 0.
// Include a write barrier if wb is true. // Include a write barrier if wb is true.
func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32) { // skip indicates assignments (at the top level) that can be avoided.
func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32, skip skipMask) {
if left.Op == ONAME && isblank(left) { if left.Op == ONAME && isblank(left) {
return return
} }
...@@ -2205,7 +2251,7 @@ func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32) ...@@ -2205,7 +2251,7 @@ func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32)
} }
// Recursively assign the new value we've made to the base of the dot op. // Recursively assign the new value we've made to the base of the dot op.
s.assign(left.Left, new, false, false, line) s.assign(left.Left, new, false, false, line, 0)
// TODO: do we need to update named values here? // TODO: do we need to update named values here?
return return
} }
...@@ -2234,7 +2280,20 @@ func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32) ...@@ -2234,7 +2280,20 @@ func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32)
} }
// Treat as a store. // Treat as a store.
if wb { if wb {
s.insertWBstore(t, addr, right, line) if skip&skipPtr != 0 {
// Special case: if we don't write back the pointers, don't bother
// doing the write barrier check.
s.storeTypeScalars(t, addr, right, skip)
return
}
s.insertWBstore(t, addr, right, line, skip)
return
}
if skip != 0 {
if skip&skipPtr == 0 {
s.storeTypePtrs(t, addr, right)
}
s.storeTypeScalars(t, addr, right, skip)
return return
} }
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, t.Size(), addr, right, s.mem()) s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, t.Size(), addr, right, s.mem())
...@@ -2830,7 +2889,7 @@ func (s *state) insertWBmove(t *Type, left, right *ssa.Value, line int32) { ...@@ -2830,7 +2889,7 @@ func (s *state) insertWBmove(t *Type, left, right *ssa.Value, line int32) {
// insertWBstore inserts the assignment *left = right including a write barrier. // insertWBstore inserts the assignment *left = right including a write barrier.
// t is the type being assigned. // t is the type being assigned.
func (s *state) insertWBstore(t *Type, left, right *ssa.Value, line int32) { func (s *state) insertWBstore(t *Type, left, right *ssa.Value, line int32, skip skipMask) {
// store scalar fields // store scalar fields
// if writeBarrier.enabled { // if writeBarrier.enabled {
// writebarrierptr for pointer fields // writebarrierptr for pointer fields
...@@ -2844,7 +2903,7 @@ func (s *state) insertWBstore(t *Type, left, right *ssa.Value, line int32) { ...@@ -2844,7 +2903,7 @@ func (s *state) insertWBstore(t *Type, left, right *ssa.Value, line int32) {
if s.WBLineno == 0 { if s.WBLineno == 0 {
s.WBLineno = left.Line s.WBLineno = left.Line
} }
s.storeTypeScalars(t, left, right) s.storeTypeScalars(t, left, right, skip)
bThen := s.f.NewBlock(ssa.BlockPlain) bThen := s.f.NewBlock(ssa.BlockPlain)
bElse := s.f.NewBlock(ssa.BlockPlain) bElse := s.f.NewBlock(ssa.BlockPlain)
...@@ -2881,23 +2940,30 @@ func (s *state) insertWBstore(t *Type, left, right *ssa.Value, line int32) { ...@@ -2881,23 +2940,30 @@ func (s *state) insertWBstore(t *Type, left, right *ssa.Value, line int32) {
} }
// do *left = right for all scalar (non-pointer) parts of t. // do *left = right for all scalar (non-pointer) parts of t.
func (s *state) storeTypeScalars(t *Type, left, right *ssa.Value) { func (s *state) storeTypeScalars(t *Type, left, right *ssa.Value, skip skipMask) {
switch { switch {
case t.IsBoolean() || t.IsInteger() || t.IsFloat() || t.IsComplex(): case t.IsBoolean() || t.IsInteger() || t.IsFloat() || t.IsComplex():
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, t.Size(), left, right, s.mem()) s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, t.Size(), left, right, s.mem())
case t.IsPtr() || t.IsMap() || t.IsChan(): case t.IsPtr() || t.IsMap() || t.IsChan():
// no scalar fields. // no scalar fields.
case t.IsString(): case t.IsString():
if skip&skipLen != 0 {
return
}
len := s.newValue1(ssa.OpStringLen, Types[TINT], right) len := s.newValue1(ssa.OpStringLen, Types[TINT], right)
lenAddr := s.newValue1I(ssa.OpOffPtr, Ptrto(Types[TINT]), s.config.IntSize, left) lenAddr := s.newValue1I(ssa.OpOffPtr, Ptrto(Types[TINT]), s.config.IntSize, left)
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, s.config.IntSize, lenAddr, len, s.mem()) s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, s.config.IntSize, lenAddr, len, s.mem())
case t.IsSlice(): case t.IsSlice():
len := s.newValue1(ssa.OpSliceLen, Types[TINT], right) if skip&skipLen == 0 {
cap := s.newValue1(ssa.OpSliceCap, Types[TINT], right) len := s.newValue1(ssa.OpSliceLen, Types[TINT], right)
lenAddr := s.newValue1I(ssa.OpOffPtr, Ptrto(Types[TINT]), s.config.IntSize, left) lenAddr := s.newValue1I(ssa.OpOffPtr, Ptrto(Types[TINT]), s.config.IntSize, left)
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, s.config.IntSize, lenAddr, len, s.mem()) s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, s.config.IntSize, lenAddr, len, s.mem())
capAddr := s.newValue1I(ssa.OpOffPtr, Ptrto(Types[TINT]), 2*s.config.IntSize, left) }
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, s.config.IntSize, capAddr, cap, s.mem()) if skip&skipCap == 0 {
cap := s.newValue1(ssa.OpSliceCap, Types[TINT], right)
capAddr := s.newValue1I(ssa.OpOffPtr, Ptrto(Types[TINT]), 2*s.config.IntSize, left)
s.vars[&memVar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, s.config.IntSize, capAddr, cap, s.mem())
}
case t.IsInterface(): case t.IsInterface():
// itab field doesn't need a write barrier (even though it is a pointer). // itab field doesn't need a write barrier (even though it is a pointer).
itab := s.newValue1(ssa.OpITab, Ptrto(Types[TUINT8]), right) itab := s.newValue1(ssa.OpITab, Ptrto(Types[TUINT8]), right)
...@@ -2908,7 +2974,7 @@ func (s *state) storeTypeScalars(t *Type, left, right *ssa.Value) { ...@@ -2908,7 +2974,7 @@ func (s *state) storeTypeScalars(t *Type, left, right *ssa.Value) {
ft := t.FieldType(i) ft := t.FieldType(i)
addr := s.newValue1I(ssa.OpOffPtr, ft.PtrTo(), t.FieldOff(i), left) addr := s.newValue1I(ssa.OpOffPtr, ft.PtrTo(), t.FieldOff(i), left)
val := s.newValue1I(ssa.OpStructSelect, ft, int64(i), right) val := s.newValue1I(ssa.OpStructSelect, ft, int64(i), right)
s.storeTypeScalars(ft.(*Type), addr, val) s.storeTypeScalars(ft.(*Type), addr, val, 0)
} }
default: default:
s.Fatalf("bad write barrier type %s", t) s.Fatalf("bad write barrier type %s", t)
......
...@@ -97,3 +97,5 @@ func TestCopy(t *testing.T) { runTest(t, "copy_ssa.go") } ...@@ -97,3 +97,5 @@ func TestCopy(t *testing.T) { runTest(t, "copy_ssa.go") }
func TestUnsafe(t *testing.T) { runTest(t, "unsafe_ssa.go") } func TestUnsafe(t *testing.T) { runTest(t, "unsafe_ssa.go") }
func TestPhi(t *testing.T) { runTest(t, "phi_ssa.go") } func TestPhi(t *testing.T) { runTest(t, "phi_ssa.go") }
func TestSlice(t *testing.T) { runTest(t, "slice.go") }
// run
// This test makes sure that t.s = t.s[0:x] doesn't write
// either the slice pointer or the capacity.
// See issue #14855.
package main
import "fmt"
const N = 1000000
type T struct {
s []int
}
func main() {
done := make(chan struct{})
a := make([]int, N+10)
t := &T{a}
go func() {
for i := 0; i < N; i++ {
t.s = t.s[1:9]
}
done <- struct{}{}
}()
go func() {
for i := 0; i < N; i++ {
t.s = t.s[0:8] // should only write len
}
done <- struct{}{}
}()
<-done
<-done
ok := true
if cap(t.s) != cap(a)-N {
fmt.Printf("wanted cap=%d, got %d\n", cap(a)-N, cap(t.s))
ok = false
}
if &t.s[0] != &a[N] {
fmt.Printf("wanted ptr=%p, got %p\n", &a[N], &t.s[0])
ok = false
}
if !ok {
panic("bad")
}
}
...@@ -2144,13 +2144,6 @@ func needwritebarrier(l *Node, r *Node) bool { ...@@ -2144,13 +2144,6 @@ func needwritebarrier(l *Node, r *Node) bool {
return false return false
} }
// No write barrier for writing a sliced slice back to its
// original location.
if (r.Op == OSLICE || r.Op == OSLICE3 || r.Op == OSLICESTR) &&
samesafeexpr(r.Left, l) {
return false
}
// Otherwise, be conservative and use write barrier. // Otherwise, be conservative and use write barrier.
return true return true
} }
......
...@@ -176,8 +176,9 @@ type T18 struct { ...@@ -176,8 +176,9 @@ type T18 struct {
func f18(p *T18, x *[]int) { func f18(p *T18, x *[]int) {
p.a = p.a[:5] // no barrier p.a = p.a[:5] // no barrier
p.a = p.a[3:5] // no barrier *x = (*x)[0:5] // no barrier
p.a = p.a[1:2:3] // no barrier p.a = p.a[3:5] // ERROR "write barrier"
p.s = p.s[8:9] // no barrier p.a = p.a[1:2:3] // ERROR "write barrier"
*x = (*x)[3:5] // no barrier p.s = p.s[8:9] // ERROR "write barrier"
*x = (*x)[3:5] // ERROR "write barrier"
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment