Skip to content

Commit 61f0409

Browse files
committed
reflect: add Value.Grow
The Grow method is like the proposed slices.Grow function in that it ensures that the slice has enough capacity to append n elements without allocating. The implementation of Grow is a thin wrapper over runtime.growslice. This also changes Append and AppendSlice to use growslice under the hood. Fixes #48000 Change-Id: I992a58584a2ff1448c1c2bc0877fe76073609111 Reviewed-on: https://go-review.googlesource.com/c/go/+/389635 Run-TryBot: Joseph Tsai <[email protected]> Reviewed-by: Keith Randall <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 9ddb8ea commit 61f0409

File tree

4 files changed

+188
-58
lines changed

4 files changed

+188
-58
lines changed

api/next/48000.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg reflect, method (Value) Grow(int) #48000

src/reflect/all_test.go

+101-19
Original file line numberDiff line numberDiff line change
@@ -739,25 +739,88 @@ func TestFunctionValue(t *testing.T) {
739739
assert(t, v.Type().String(), "func()")
740740
}
741741

742+
func TestGrow(t *testing.T) {
743+
v := ValueOf([]int(nil))
744+
shouldPanic("reflect.Value.Grow using unaddressable value", func() { v.Grow(0) })
745+
v = ValueOf(new([]int)).Elem()
746+
v.Grow(0)
747+
if !v.IsNil() {
748+
t.Errorf("v.Grow(0) should still be nil")
749+
}
750+
v.Grow(1)
751+
if v.Cap() == 0 {
752+
t.Errorf("v.Cap = %v, want non-zero", v.Cap())
753+
}
754+
want := v.UnsafePointer()
755+
v.Grow(1)
756+
got := v.UnsafePointer()
757+
if got != want {
758+
t.Errorf("noop v.Grow should not change pointers")
759+
}
760+
761+
t.Run("Append", func(t *testing.T) {
762+
var got, want []T
763+
v := ValueOf(&got).Elem()
764+
appendValue := func(vt T) {
765+
v.Grow(1)
766+
v.SetLen(v.Len() + 1)
767+
v.Index(v.Len() - 1).Set(ValueOf(vt))
768+
}
769+
for i := 0; i < 10; i++ {
770+
vt := T{i, float64(i), strconv.Itoa(i), &i}
771+
appendValue(vt)
772+
want = append(want, vt)
773+
}
774+
if !DeepEqual(got, want) {
775+
t.Errorf("value mismatch:\ngot %v\nwant %v", got, want)
776+
}
777+
})
778+
779+
t.Run("Rate", func(t *testing.T) {
780+
var b []byte
781+
v := ValueOf(new([]byte)).Elem()
782+
for i := 0; i < 10; i++ {
783+
b = append(b[:cap(b)], make([]byte, 1)...)
784+
v.SetLen(v.Cap())
785+
v.Grow(1)
786+
if v.Cap() != cap(b) {
787+
t.Errorf("v.Cap = %v, want %v", v.Cap(), cap(b))
788+
}
789+
}
790+
})
791+
792+
t.Run("ZeroCapacity", func(t *testing.T) {
793+
for i := 0; i < 10; i++ {
794+
v := ValueOf(new([]byte)).Elem()
795+
v.Grow(61)
796+
b := v.Bytes()
797+
b = b[:cap(b)]
798+
for i, c := range b {
799+
if c != 0 {
800+
t.Fatalf("Value.Bytes[%d] = 0x%02x, want 0x00", i, c)
801+
}
802+
b[i] = 0xff
803+
}
804+
runtime.GC()
805+
}
806+
})
807+
}
808+
742809
var appendTests = []struct {
743810
orig, extra []int
744811
}{
812+
{nil, nil},
813+
{[]int{}, nil},
814+
{nil, []int{}},
815+
{[]int{}, []int{}},
816+
{nil, []int{22}},
817+
{[]int{}, []int{22}},
818+
{make([]int, 2, 4), nil},
819+
{make([]int, 2, 4), []int{}},
745820
{make([]int, 2, 4), []int{22}},
746821
{make([]int, 2, 4), []int{22, 33, 44}},
747822
}
748823

749-
func sameInts(x, y []int) bool {
750-
if len(x) != len(y) {
751-
return false
752-
}
753-
for i, xx := range x {
754-
if xx != y[i] {
755-
return false
756-
}
757-
}
758-
return true
759-
}
760-
761824
func TestAppend(t *testing.T) {
762825
for i, test := range appendTests {
763826
origLen, extraLen := len(test.orig), len(test.extra)
@@ -769,32 +832,51 @@ func TestAppend(t *testing.T) {
769832
}
770833
// Convert extra from []int to *SliceValue.
771834
e1 := ValueOf(test.extra)
835+
772836
// Test Append.
773-
a0 := ValueOf(test.orig)
774-
have0 := Append(a0, e0...).Interface().([]int)
775-
if !sameInts(have0, want) {
776-
t.Errorf("Append #%d: have %v, want %v (%p %p)", i, have0, want, test.orig, have0)
837+
a0 := ValueOf(&test.orig).Elem()
838+
have0 := Append(a0, e0...)
839+
if have0.CanAddr() {
840+
t.Errorf("Append #%d: have slice should not be addressable", i)
841+
}
842+
if !DeepEqual(have0.Interface(), want) {
843+
t.Errorf("Append #%d: have %v, want %v (%p %p)", i, have0, want, test.orig, have0.Interface())
777844
}
778845
// Check that the orig and extra slices were not modified.
846+
if a0.Len() != len(test.orig) {
847+
t.Errorf("Append #%d: a0.Len: have %d, want %d", i, a0.Len(), origLen)
848+
}
779849
if len(test.orig) != origLen {
780850
t.Errorf("Append #%d origLen: have %v, want %v", i, len(test.orig), origLen)
781851
}
782852
if len(test.extra) != extraLen {
783853
t.Errorf("Append #%d extraLen: have %v, want %v", i, len(test.extra), extraLen)
784854
}
855+
785856
// Test AppendSlice.
786-
a1 := ValueOf(test.orig)
787-
have1 := AppendSlice(a1, e1).Interface().([]int)
788-
if !sameInts(have1, want) {
857+
a1 := ValueOf(&test.orig).Elem()
858+
have1 := AppendSlice(a1, e1)
859+
if have1.CanAddr() {
860+
t.Errorf("AppendSlice #%d: have slice should not be addressable", i)
861+
}
862+
if !DeepEqual(have1.Interface(), want) {
789863
t.Errorf("AppendSlice #%d: have %v, want %v", i, have1, want)
790864
}
791865
// Check that the orig and extra slices were not modified.
866+
if a1.Len() != len(test.orig) {
867+
t.Errorf("AppendSlice #%d: a1.Len: have %d, want %d", i, a0.Len(), origLen)
868+
}
792869
if len(test.orig) != origLen {
793870
t.Errorf("AppendSlice #%d origLen: have %v, want %v", i, len(test.orig), origLen)
794871
}
795872
if len(test.extra) != extraLen {
796873
t.Errorf("AppendSlice #%d extraLen: have %v, want %v", i, len(test.extra), extraLen)
797874
}
875+
876+
// Test Append and AppendSlice with unexported value.
877+
ax := ValueOf(struct{ x []int }{test.orig}).Field(0)
878+
shouldPanic("using unexported field", func() { Append(ax, e0...) })
879+
shouldPanic("using unexported field", func() { AppendSlice(ax, e1) })
798880
}
799881
}
800882

src/reflect/value.go

+55-31
Original file line numberDiff line numberDiff line change
@@ -2780,42 +2780,61 @@ func arrayAt(p unsafe.Pointer, i int, eltSize uintptr, whySafe string) unsafe.Po
27802780
return add(p, uintptr(i)*eltSize, "i < len")
27812781
}
27822782

2783-
// grow grows the slice s so that it can hold extra more values, allocating
2784-
// more capacity if needed. It also returns the old and new slice lengths.
2785-
func grow(s Value, extra int) (Value, int, int) {
2786-
i0 := s.Len()
2787-
i1 := i0 + extra
2788-
if i1 < i0 {
2789-
panic("reflect.Append: slice overflow")
2790-
}
2791-
m := s.Cap()
2792-
if i1 <= m {
2793-
return s.Slice(0, i1), i0, i1
2794-
}
2795-
if m == 0 {
2796-
m = extra
2797-
} else {
2798-
const threshold = 256
2799-
for m < i1 {
2800-
if i0 < threshold {
2801-
m += m
2802-
} else {
2803-
m += (m + 3*threshold) / 4
2804-
}
2805-
}
2783+
// Grow increases the slice's capacity, if necessary, to guarantee space for
2784+
// another n elements. After Grow(n), at least n elements can be appended
2785+
// to the slice without another allocation.
2786+
//
2787+
// It panics if v's Kind is not a Slice or if n is negative or too large to
2788+
// allocate the memory.
2789+
func (v Value) Grow(n int) {
2790+
v.mustBeAssignable()
2791+
v.mustBe(Slice)
2792+
v.grow(n)
2793+
}
2794+
2795+
// grow is identical to Grow but does not check for assignability.
2796+
func (v Value) grow(n int) {
2797+
p := (*unsafeheader.Slice)(v.ptr)
2798+
switch {
2799+
case n < 0:
2800+
panic("reflect.Value.Grow: negative len")
2801+
case p.Len+n < 0:
2802+
panic("reflect.Value.Grow: slice overflow")
2803+
case p.Len+n > p.Cap:
2804+
t := v.typ.Elem().(*rtype)
2805+
*p = growslice(t, *p, n)
28062806
}
2807-
t := MakeSlice(s.Type(), i1, m)
2808-
Copy(t, s)
2809-
return t, i0, i1
2807+
}
2808+
2809+
// extendSlice extends a slice by n elements.
2810+
//
2811+
// Unlike Value.grow, which modifies the slice in place and
2812+
// does not change the length of the slice in place,
2813+
// extendSlice returns a new slice value with the length
2814+
// incremented by the number of specified elements.
2815+
func (v Value) extendSlice(n int) Value {
2816+
v.mustBeExported()
2817+
v.mustBe(Slice)
2818+
2819+
// Shallow copy the slice header to avoid mutating the source slice.
2820+
sh := *(*unsafeheader.Slice)(v.ptr)
2821+
s := &sh
2822+
v.ptr = unsafe.Pointer(s)
2823+
v.flag = flagIndir | flag(Slice) // equivalent flag to MakeSlice
2824+
2825+
v.grow(n) // fine to treat as assignable since we allocate a new slice header
2826+
s.Len += n
2827+
return v
28102828
}
28112829

28122830
// Append appends the values x to a slice s and returns the resulting slice.
28132831
// As in Go, each x's value must be assignable to the slice's element type.
28142832
func Append(s Value, x ...Value) Value {
28152833
s.mustBe(Slice)
2816-
s, i0, i1 := grow(s, len(x))
2817-
for i, j := i0, 0; i < i1; i, j = i+1, j+1 {
2818-
s.Index(i).Set(x[j])
2834+
n := s.Len()
2835+
s = s.extendSlice(len(x))
2836+
for i, v := range x {
2837+
s.Index(n + i).Set(v)
28192838
}
28202839
return s
28212840
}
@@ -2826,8 +2845,10 @@ func AppendSlice(s, t Value) Value {
28262845
s.mustBe(Slice)
28272846
t.mustBe(Slice)
28282847
typesMustMatch("reflect.AppendSlice", s.Type().Elem(), t.Type().Elem())
2829-
s, i0, i1 := grow(s, t.Len())
2830-
Copy(s.Slice(i0, i1), t)
2848+
ns := s.Len()
2849+
nt := t.Len()
2850+
s = s.extendSlice(nt)
2851+
Copy(s.Slice(ns, ns+nt), t)
28312852
return s
28322853
}
28332854

@@ -3764,6 +3785,9 @@ func typehash(t *rtype, p unsafe.Pointer, h uintptr) uintptr
37643785

37653786
func verifyNotInHeapPtr(p uintptr) bool
37663787

3788+
//go:noescape
3789+
func growslice(t *rtype, old unsafeheader.Slice, num int) unsafeheader.Slice
3790+
37673791
// Dummy annotation marking that the value x escapes,
37683792
// for use in cases where the reflect code is so clever that
37693793
// the compiler cannot follow.

src/runtime/slice.go

+31-8
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,18 @@ func mulUintptr(a, b uintptr) (uintptr, bool) {
126126
// growslice allocates new backing store for a slice.
127127
//
128128
// arguments:
129-
// oldPtr = pointer to the slice's backing array
130-
// newLen = new length (= oldLen + num)
131-
// oldCap = original slice's capacity.
132-
// num = number of elements being added
133-
// et = element type
129+
//
130+
// oldPtr = pointer to the slice's backing array
131+
// newLen = new length (= oldLen + num)
132+
// oldCap = original slice's capacity.
133+
// num = number of elements being added
134+
// et = element type
134135
//
135136
// return values:
136-
// newPtr = pointer to the new backing store
137-
// newLen = same value as the argument
138-
// newCap = capacity of the new backing store
137+
//
138+
// newPtr = pointer to the new backing store
139+
// newLen = same value as the argument
140+
// newCap = capacity of the new backing store
139141
//
140142
// Requires that uint(newLen) > uint(oldCap).
141143
// Assumes the original slice length is newLen - num
@@ -264,6 +266,8 @@ func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice
264266
p = mallocgc(capmem, nil, false)
265267
// The append() that calls growslice is going to overwrite from oldLen to newLen.
266268
// Only clear the part that will not be overwritten.
269+
// The reflect_growslice() that calls growslice will manually clear
270+
// the region not cleared here.
267271
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
268272
} else {
269273
// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
@@ -279,6 +283,25 @@ func growslice(oldPtr unsafe.Pointer, newLen, oldCap, num int, et *_type) slice
279283
return slice{p, newLen, newcap}
280284
}
281285

286+
//go:linkname reflect_growslice reflect.growslice
287+
func reflect_growslice(et *_type, old slice, num int) slice {
288+
// Semantically equivalent to slices.Grow, except that the caller
289+
// is responsible for ensuring that old.len+num > old.cap.
290+
num -= old.cap - old.len // preserve memory of old[old.len:old.cap]
291+
new := growslice(old.array, old.cap+num, old.cap, num, et)
292+
// growslice does not zero out new[old.cap:new.len] since it assumes that
293+
// the memory will be overwritten by an append() that called growslice.
294+
// Since the caller of reflect_growslice is not append(),
295+
// zero out this region before returning the slice to the reflect package.
296+
if et.ptrdata == 0 {
297+
oldcapmem := uintptr(old.cap) * et.size
298+
newlenmem := uintptr(new.len) * et.size
299+
memclrNoHeapPointers(add(new.array, oldcapmem), newlenmem-oldcapmem)
300+
}
301+
new.len = old.len // preserve the old length
302+
return new
303+
}
304+
282305
func isPowerOfTwo(x uintptr) bool {
283306
return x&(x-1) == 0
284307
}

0 commit comments

Comments
 (0)