]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: optimize append(x, make([]T, y)...) slice extension
authorMartin Möhrmann <moehrmann@google.com>
Thu, 26 Apr 2018 16:30:11 +0000 (18:30 +0200)
committerMartin Möhrmann <moehrmann@google.com>
Sun, 6 May 2018 04:28:23 +0000 (04:28 +0000)
Changes the compiler to recognize the slice extension pattern

  append(x, make([]T, y)...)

and replace it with growslice and an optional memclr to avoid an allocation for make([]T, y).

Memclr is not called in case growslice already allocated a new cleared backing array
when T contains pointers.

amd64:
name                      old time/op    new time/op    delta
ExtendSlice/IntSlice         103ns ± 4%      57ns ± 4%   -44.55%  (p=0.000 n=18+18)
ExtendSlice/PointerSlice     155ns ± 3%      77ns ± 3%   -49.93%  (p=0.000 n=20+20)
ExtendSlice/NoGrow          50.2ns ± 3%     5.2ns ± 2%   -89.67%  (p=0.000 n=18+18)

name                      old alloc/op   new alloc/op   delta
ExtendSlice/IntSlice         64.0B ± 0%     32.0B ± 0%   -50.00%  (p=0.000 n=20+20)
ExtendSlice/PointerSlice     64.0B ± 0%     32.0B ± 0%   -50.00%  (p=0.000 n=20+20)
ExtendSlice/NoGrow           32.0B ± 0%      0.0B       -100.00%  (p=0.000 n=20+20)

name                      old allocs/op  new allocs/op  delta
ExtendSlice/IntSlice          2.00 ± 0%      1.00 ± 0%   -50.00%  (p=0.000 n=20+20)
ExtendSlice/PointerSlice      2.00 ± 0%      1.00 ± 0%   -50.00%  (p=0.000 n=20+20)
ExtendSlice/NoGrow            1.00 ± 0%      0.00       -100.00%  (p=0.000 n=20+20)

Fixes #21266

Change-Id: Idc3077665f63cbe89762b590c5967a864fd1c07f
Reviewed-on: https://go-review.googlesource.com/109517
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
src/cmd/compile/internal/gc/builtin.go
src/cmd/compile/internal/gc/builtin/runtime.go
src/cmd/compile/internal/gc/order.go
src/cmd/compile/internal/gc/ssa.go
src/cmd/compile/internal/gc/walk.go
src/runtime/slice.go
src/runtime/slice_test.go
test/append.go
test/append1.go
test/codegen/slices.go
test/fixedbugs/issue4085b.go

index 4223a5e3fe2e7dd70491de8c0e0af49a4087b505..4259fb415305bacbf9e10351b161cc679e3a29c5 100644 (file)
@@ -13,6 +13,7 @@ var runtimeDecls = [...]struct {
        {"panicindex", funcTag, 5},
        {"panicslice", funcTag, 5},
        {"panicdivide", funcTag, 5},
+       {"panicmakeslicelen", funcTag, 5},
        {"throwinit", funcTag, 5},
        {"panicwrap", funcTag, 5},
        {"gopanic", funcTag, 7},
index 17bdf362e94d5fae55f1be449d16dfe7ae8ea32c..ae1850c72f46d1dcf32039410c7b165ef10c373b 100644 (file)
@@ -18,6 +18,7 @@ func newobject(typ *byte) *any
 func panicindex()
 func panicslice()
 func panicdivide()
+func panicmakeslicelen()
 func throwinit()
 func panicwrap()
 
index b62c2412a0d607a80736c24160c1c256335dbd4f..1a10587797c2d566d84570e3170e809c66f62413 100644 (file)
@@ -1104,7 +1104,14 @@ func (o *Order) expr(n, lhs *Node) *Node {
                }
 
        case OAPPEND:
-               o.callArgs(&n.List)
+               // Check for append(x, make([]T, y)...) .
+               if isAppendOfMake(n) {
+                       n.List.SetFirst(o.expr(n.List.First(), nil))             // order x
+                       n.List.Second().Left = o.expr(n.List.Second().Left, nil) // order y
+               } else {
+                       o.callArgs(&n.List)
+               }
+
                if lhs == nil || lhs.Op != ONAME && !samesafeexpr(lhs, n.List.First()) {
                        n = o.copyExpr(n, n.Type, false)
                }
index aa324984dc62557c556c863ef8cbec79c075313b..73fe7bdfeed050edeecb75545ba167d2c1498655 100644 (file)
@@ -703,7 +703,7 @@ func (s *state) stmt(n *Node) {
                s.call(n, callNormal)
                if n.Op == OCALLFUNC && n.Left.Op == ONAME && n.Left.Class() == PFUNC {
                        if fn := n.Left.Sym.Name; compiling_runtime && fn == "throw" ||
-                               n.Left.Sym.Pkg == Runtimepkg && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "block") {
+                               n.Left.Sym.Pkg == Runtimepkg && (fn == "throwinit" || fn == "gopanic" || fn == "panicwrap" || fn == "block" || fn == "panicmakeslicelen" || fn == "panicmakeslicecap") {
                                m := s.mem()
                                b := s.endBlock()
                                b.Kind = ssa.BlockExit
index a264bf340d34304c72f4b7c3fb2ae2c58bba21cf..3046b9dda872b6261097536866f5df58b024cd83 100644 (file)
@@ -728,9 +728,13 @@ opswitch:
                        if r.Type.Elem().NotInHeap() {
                                yyerror("%v is go:notinheap; heap allocation disallowed", r.Type.Elem())
                        }
-                       if r.Isddd() {
+                       switch {
+                       case isAppendOfMake(r):
+                               // x = append(y, make([]T, y)...)
+                               r = extendslice(r, init)
+                       case r.Isddd():
                                r = appendslice(r, init) // also works for append(slice, string).
-                       } else {
+                       default:
                                r = walkappend(r, init, n)
                        }
                        n.Right = r
@@ -2910,6 +2914,18 @@ func addstr(n *Node, init *Nodes) *Node {
        return r
 }
 
+func walkAppendArgs(n *Node, init *Nodes) {
+       walkexprlistsafe(n.List.Slice(), init)
+
+       // walkexprlistsafe will leave OINDEX (s[n]) alone if both s
+       // and n are name or literal, but those may index the slice we're
+       // modifying here. Fix explicitly.
+       ls := n.List.Slice()
+       for i1, n1 := range ls {
+               ls[i1] = cheapexpr(n1, init)
+       }
+}
+
 // expand append(l1, l2...) to
 //   init {
 //     s := l1
@@ -2925,15 +2941,7 @@ func addstr(n *Node, init *Nodes) *Node {
 //
 // l2 is allowed to be a string.
 func appendslice(n *Node, init *Nodes) *Node {
-       walkexprlistsafe(n.List.Slice(), init)
-
-       // walkexprlistsafe will leave OINDEX (s[n]) alone if both s
-       // and n are name or literal, but those may index the slice we're
-       // modifying here. Fix explicitly.
-       ls := n.List.Slice()
-       for i1, n1 := range ls {
-               ls[i1] = cheapexpr(n1, init)
-       }
+       walkAppendArgs(n, init)
 
        l1 := n.List.First()
        l2 := n.List.Second()
@@ -3027,6 +3035,174 @@ func appendslice(n *Node, init *Nodes) *Node {
        return s
 }
 
+// isAppendOfMake reports whether n is of the form append(x , make([]T, y)...).
+// isAppendOfMake assumes n has already been typechecked.
+func isAppendOfMake(n *Node) bool {
+       if Debug['N'] != 0 || instrumenting {
+               return false
+       }
+
+       if n.Typecheck() == 0 {
+               Fatalf("missing typecheck: %+v", n)
+       }
+
+       if n.Op != OAPPEND || !n.Isddd() || n.List.Len() != 2 {
+               return false
+       }
+
+       second := n.List.Second()
+       if second.Op != OMAKESLICE {
+               return false
+       }
+
+       if n.List.Second().Right != nil {
+               return false
+       }
+
+       // y must be either an integer constant or a variable of type int.
+       // typecheck checks that constant arguments to make are not negative and
+       // fit into an int.
+       // runtime.growslice uses int as type for the newcap argument.
+       // Constraining variables to be type int avoids the need for runtime checks
+       // that e.g. check if an int64 value fits into an int.
+       // TODO(moehrmann): support other integer types that always fit in an int
+       y := second.Left
+       if !Isconst(y, CTINT) && y.Type.Etype != TINT {
+               return false
+       }
+
+       return true
+}
+
+// extendslice rewrites append(l1, make([]T, l2)...) to
+//   init {
+//     if l2 < 0 {
+//       panicmakeslicelen()
+//     }
+//     s := l1
+//     n := len(s) + l2
+//     // Compare n and s as uint so growslice can panic on overflow of len(s) + l2.
+//     // cap is a positive int and n can become negative when len(s) + l2
+//     // overflows int. Interpreting n when negative as uint makes it larger
+//     // than cap(s). growslice will check the int n arg and panic if n is
+//     // negative. This prevents the overflow from being undetected.
+//     if uint(n) > uint(cap(s)) {
+//       s = growslice(T, s, n)
+//     }
+//     s = s[:n]
+//     lptr := &l1[0]
+//     sptr := &s[0]
+//     if lptr == sptr || !hasPointers(T) {
+//       // growslice did not clear the whole underlying array (or did not get called)
+//       hp := &s[len(l1)]
+//       hn := l2 * sizeof(T)
+//       memclr(hp, hn)
+//     }
+//   }
+//   s
+func extendslice(n *Node, init *Nodes) *Node {
+       // isAppendOfMake made sure l2 fits in an int.
+       l2 := conv(n.List.Second().Left, types.Types[TINT])
+       l2 = typecheck(l2, Erv)
+       n.List.SetSecond(l2) // walkAppendArgs expects l2 in n.List.Second().
+
+       walkAppendArgs(n, init)
+
+       l1 := n.List.First()
+       l2 = n.List.Second() // re-read l2, as it may have been updated by walkAppendArgs
+
+       var nodes []*Node
+
+       // if l2 < 0
+       nifneg := nod(OIF, nod(OLT, l2, nodintconst(0)), nil)
+       nifneg.SetLikely(false)
+
+       // panicmakeslicelen()
+       nifneg.Nbody.Set1(mkcall("panicmakeslicelen", nil, init))
+       nodes = append(nodes, nifneg)
+
+       // s := l1
+       s := temp(l1.Type)
+       nodes = append(nodes, nod(OAS, s, l1))
+
+       elemtype := s.Type.Elem()
+
+       // n := len(s) + l2
+       nn := temp(types.Types[TINT])
+       nodes = append(nodes, nod(OAS, nn, nod(OADD, nod(OLEN, s, nil), l2)))
+
+       // if uint(n) > uint(cap(s))
+       nuint := nod(OCONV, nn, nil)
+       nuint.Type = types.Types[TUINT]
+       capuint := nod(OCONV, nod(OCAP, s, nil), nil)
+       capuint.Type = types.Types[TUINT]
+       nif := nod(OIF, nod(OGT, nuint, capuint), nil)
+
+       // instantiate growslice(typ *type, old []any, newcap int) []any
+       fn := syslook("growslice")
+       fn = substArgTypes(fn, elemtype, elemtype)
+
+       // s = growslice(T, s, n)
+       nif.Nbody.Set1(nod(OAS, s, mkcall1(fn, s.Type, &nif.Ninit, typename(elemtype), s, nn)))
+       nodes = append(nodes, nif)
+
+       // s = s[:n]
+       nt := nod(OSLICE, s, nil)
+       nt.SetSliceBounds(nil, nn, nil)
+       nodes = append(nodes, nod(OAS, s, nt))
+
+       // lptr := &l1[0]
+       l1ptr := temp(l1.Type.Elem().PtrTo())
+       tmp := nod(OSPTR, l1, nil)
+       nodes = append(nodes, nod(OAS, l1ptr, tmp))
+
+       // sptr := &s[0]
+       sptr := temp(elemtype.PtrTo())
+       tmp = nod(OSPTR, s, nil)
+       nodes = append(nodes, nod(OAS, sptr, tmp))
+
+       var clr []*Node
+
+       // hp := &s[len(l1)]
+       hp := temp(types.Types[TUNSAFEPTR])
+
+       tmp = nod(OINDEX, s, nod(OLEN, l1, nil))
+       tmp.SetBounded(true)
+       tmp = nod(OADDR, tmp, nil)
+       tmp = nod(OCONVNOP, tmp, nil)
+       tmp.Type = types.Types[TUNSAFEPTR]
+       clr = append(clr, nod(OAS, hp, tmp))
+
+       // hn := l2 * sizeof(elem(s))
+       hn := temp(types.Types[TUINTPTR])
+
+       tmp = nod(OMUL, l2, nodintconst(elemtype.Width))
+       tmp = conv(tmp, types.Types[TUINTPTR])
+       clr = append(clr, nod(OAS, hn, tmp))
+
+       clrname := "memclrNoHeapPointers"
+       hasPointers := types.Haspointers(elemtype)
+       if hasPointers {
+               clrname = "memclrHasPointers"
+       }
+       clrfn := mkcall(clrname, nil, init, hp, hn)
+       clr = append(clr, clrfn)
+
+       if hasPointers {
+               // if l1ptr == sptr
+               nifclr := nod(OIF, nod(OEQ, l1ptr, sptr), nil)
+               nifclr.Nbody.Set(clr)
+               nodes = append(nodes, nifclr)
+       } else {
+               nodes = append(nodes, clr...)
+       }
+
+       typecheckslice(nodes, Etop)
+       walkstmtlist(nodes)
+       init.Append(nodes...)
+       return s
+}
+
 // Rewrite append(src, x, y, z) so that any side effects in
 // x, y, z (including runtime panics) are evaluated in
 // initialization statements before the append.
index 40c599515335ebdcd2bb4a8dc3ea968cc90c9f44..fd5d08b52c1031b21eafff66f63ada61493f708e 100644 (file)
@@ -44,6 +44,14 @@ func maxSliceCap(elemsize uintptr) uintptr {
        return maxAlloc / elemsize
 }
 
+func panicmakeslicelen() {
+       panic(errorString("makeslice: len out of range"))
+}
+
+func panicmakeslicecap() {
+       panic(errorString("makeslice: cap out of range"))
+}
+
 func makeslice(et *_type, len, cap int) slice {
        // NOTE: The len > maxElements check here is not strictly necessary,
        // but it produces a 'len out of range' error instead of a 'cap out of range' error
@@ -52,11 +60,11 @@ func makeslice(et *_type, len, cap int) slice {
        // See issue 4085.
        maxElements := maxSliceCap(et.size)
        if len < 0 || uintptr(len) > maxElements {
-               panic(errorString("makeslice: len out of range"))
+               panicmakeslicelen()
        }
 
        if cap < len || uintptr(cap) > maxElements {
-               panic(errorString("makeslice: cap out of range"))
+               panicmakeslicecap()
        }
 
        p := mallocgc(et.size*uintptr(cap), et, true)
@@ -66,12 +74,12 @@ func makeslice(et *_type, len, cap int) slice {
 func makeslice64(et *_type, len64, cap64 int64) slice {
        len := int(len64)
        if int64(len) != len64 {
-               panic(errorString("makeslice: len out of range"))
+               panicmakeslicelen()
        }
 
        cap := int(cap64)
        if int64(cap) != cap64 {
-               panic(errorString("makeslice: cap out of range"))
+               panicmakeslicecap()
        }
 
        return makeslice(et, len, cap)
index 46db071ebe019baea39c41de6a43bbaee75a00fc..c2dfb7afd19b8f0ad6940ee2aeef0ee9f8157018 100644 (file)
@@ -72,6 +72,36 @@ func BenchmarkGrowSlice(b *testing.B) {
        })
 }
 
+var (
+       SinkIntSlice        []int
+       SinkIntPointerSlice []*int
+)
+
+func BenchmarkExtendSlice(b *testing.B) {
+       var length = 4 // Use a variable to prevent stack allocation of slices.
+       b.Run("IntSlice", func(b *testing.B) {
+               s := make([]int, 0, length)
+               for i := 0; i < b.N; i++ {
+                       s = append(s[:0:length/2], make([]int, length)...)
+               }
+               SinkIntSlice = s
+       })
+       b.Run("PointerSlice", func(b *testing.B) {
+               s := make([]*int, 0, length)
+               for i := 0; i < b.N; i++ {
+                       s = append(s[:0:length/2], make([]*int, length)...)
+               }
+               SinkIntPointerSlice = s
+       })
+       b.Run("NoGrow", func(b *testing.B) {
+               s := make([]int, 0, length)
+               for i := 0; i < b.N; i++ {
+                       s = append(s[:0:length], make([]int, length)...)
+               }
+               SinkIntSlice = s
+       })
+}
+
 func BenchmarkAppend(b *testing.B) {
        b.StopTimer()
        x := make([]int, 0, N)
index 3f6251ee507668f6ce9828149417e628066472e3..3d160634060c87631652f8ac1c784e0cd947ef1c 100644 (file)
@@ -13,14 +13,12 @@ import (
        "reflect"
 )
 
-
 func verify(name string, result, expected interface{}) {
        if !reflect.DeepEqual(result, expected) {
                panic(name)
        }
 }
 
-
 func main() {
        for _, t := range tests {
                verify(t.name, t.result, t.expected)
@@ -30,6 +28,10 @@ func main() {
        verifyType()
 }
 
+var (
+       zero int = 0
+       one  int = 1
+)
 
 var tests = []struct {
        name             string
@@ -49,7 +51,6 @@ var tests = []struct {
        {"bool i", append([]bool{true, false, true}, []bool{true}...), []bool{true, false, true, true}},
        {"bool j", append([]bool{true, false, true}, []bool{true, true, true}...), []bool{true, false, true, true, true, true}},
 
-
        {"byte a", append([]byte{}), []byte{}},
        {"byte b", append([]byte{}, 0), []byte{0}},
        {"byte c", append([]byte{}, 0, 1, 2, 3), []byte{0, 1, 2, 3}},
@@ -84,7 +85,6 @@ var tests = []struct {
        {"int16 i", append([]int16{0, 1, 2}, []int16{3}...), []int16{0, 1, 2, 3}},
        {"int16 j", append([]int16{0, 1, 2}, []int16{3, 4, 5}...), []int16{0, 1, 2, 3, 4, 5}},
 
-
        {"uint32 a", append([]uint32{}), []uint32{}},
        {"uint32 b", append([]uint32{}, 0), []uint32{0}},
        {"uint32 c", append([]uint32{}, 0, 1, 2, 3), []uint32{0, 1, 2, 3}},
@@ -99,7 +99,6 @@ var tests = []struct {
        {"uint32 i", append([]uint32{0, 1, 2}, []uint32{3}...), []uint32{0, 1, 2, 3}},
        {"uint32 j", append([]uint32{0, 1, 2}, []uint32{3, 4, 5}...), []uint32{0, 1, 2, 3, 4, 5}},
 
-
        {"float64 a", append([]float64{}), []float64{}},
        {"float64 b", append([]float64{}, 0), []float64{0}},
        {"float64 c", append([]float64{}, 0, 1, 2, 3), []float64{0, 1, 2, 3}},
@@ -114,7 +113,6 @@ var tests = []struct {
        {"float64 i", append([]float64{0, 1, 2}, []float64{3}...), []float64{0, 1, 2, 3}},
        {"float64 j", append([]float64{0, 1, 2}, []float64{3, 4, 5}...), []float64{0, 1, 2, 3, 4, 5}},
 
-
        {"complex128 a", append([]complex128{}), []complex128{}},
        {"complex128 b", append([]complex128{}, 0), []complex128{0}},
        {"complex128 c", append([]complex128{}, 0, 1, 2, 3), []complex128{0, 1, 2, 3}},
@@ -129,7 +127,6 @@ var tests = []struct {
        {"complex128 i", append([]complex128{0, 1, 2}, []complex128{3}...), []complex128{0, 1, 2, 3}},
        {"complex128 j", append([]complex128{0, 1, 2}, []complex128{3, 4, 5}...), []complex128{0, 1, 2, 3, 4, 5}},
 
-
        {"string a", append([]string{}), []string{}},
        {"string b", append([]string{}, "0"), []string{"0"}},
        {"string c", append([]string{}, "0", "1", "2", "3"), []string{"0", "1", "2", "3"}},
@@ -143,8 +140,19 @@ var tests = []struct {
 
        {"string i", append([]string{"0", "1", "2"}, []string{"3"}...), []string{"0", "1", "2", "3"}},
        {"string j", append([]string{"0", "1", "2"}, []string{"3", "4", "5"}...), []string{"0", "1", "2", "3", "4", "5"}},
-}
 
+       {"make a", append([]string{}, make([]string, 0)...), []string{}},
+       {"make b", append([]string(nil), make([]string, 0)...), []string(nil)},
+
+       {"make c", append([]struct{}{}, make([]struct{}, 0)...), []struct{}{}},
+       {"make d", append([]struct{}{}, make([]struct{}, 2)...), make([]struct{}, 2)},
+
+       {"make e", append([]int{0, 1}, make([]int, 0)...), []int{0, 1}},
+       {"make f", append([]int{0, 1}, make([]int, 2)...), []int{0, 1, 0, 0}},
+
+       {"make g", append([]*int{&zero, &one}, make([]*int, 0)...), []*int{&zero, &one}},
+       {"make h", append([]*int{&zero, &one}, make([]*int, 2)...), []*int{&zero, &one, nil, nil}},
+}
 
 func verifyStruct() {
        type T struct {
@@ -185,7 +193,6 @@ func verifyStruct() {
        verify("struct m", append(s, e...), r)
 }
 
-
 func verifyInterface() {
        type T interface{}
        type S []T
index 6d42368e425aa9d358f0333e146eb46345f72218..0fe24c09564df019b8bce6f705f8ea6cb6bbb4f8 100644 (file)
@@ -17,4 +17,6 @@ func main() {
        _ = append(s...)       // ERROR "cannot use ... on first argument"
        _ = append(s, 2, s...) // ERROR "too many arguments to append"
 
+       _ = append(s, make([]int, 0))     // ERROR "cannot use make.* as type int in append"
+       _ = append(s, make([]int, -1)...) // ERROR "negative len argument in make"
 }
index a5fae7426d7f4a5b1082f4f8159badedd3bd51f5..15dbcee7372d89765dc27a9c70f079eeb49e273c 100644 (file)
@@ -30,3 +30,34 @@ func SliceClearPointers(s []*int) []*int {
        }
        return s
 }
+
+// ------------------ //
+//      Extension     //
+// ------------------ //
+
+// Issue #21266 - avoid makeslice in append(x, make([]T, y)...)
+
+func SliceExtensionConst(s []int) []int {
+       // amd64:`.*runtime\.memclrNoHeapPointers`
+       // amd64:-`.*runtime\.makeslice`
+       // amd64:-`.*runtime\.panicmakeslicelen`
+       return append(s, make([]int, 1<<2)...)
+}
+
+func SliceExtensionPointer(s []*int, l int) []*int {
+       // amd64:`.*runtime\.memclrHasPointers`
+       // amd64:-`.*runtime\.makeslice`
+       return append(s, make([]*int, l)...)
+}
+
+func SliceExtensionVar(s []byte, l int) []byte {
+       // amd64:`.*runtime\.memclrNoHeapPointers`
+       // amd64:-`.*runtime\.makeslice`
+       return append(s, make([]byte, l)...)
+}
+
+func SliceExtensionInt64(s []int, l64 int64) []int {
+       // 386:`.*runtime\.makeslice`
+       // 386:-`.*runtime\.memclr`
+       return append(s, make([]int, l64)...)
+}
index db9a15894ba3fba397998510933c3c9137950d10..6bf315fcc2f0603835911f4739969d751ec92655 100644 (file)
@@ -34,6 +34,12 @@ func main() {
                shouldPanic("len out of range", func() { _ = make(T, int64(n)) })
                shouldPanic("cap out of range", func() { _ = make(T, 0, int64(n)) })
        }
+
+       // Test make in append panics since the gc compiler optimizes makes in appends.
+       shouldPanic("len out of range", func() { _ = append(T{}, make(T, n)...) })
+       shouldPanic("cap out of range", func() { _ = append(T{}, make(T, 0, n)...) })
+       shouldPanic("len out of range", func() { _ = append(T{}, make(T, int64(n))...) })
+       shouldPanic("cap out of range", func() { _ = append(T{}, make(T, 0, int64(n))...) })
 }
 
 func shouldPanic(str string, f func()) {