diff --git a/src/reflect/value.go b/src/reflect/value.go index 7e89e68b..11a54279 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -780,6 +780,9 @@ func memcpy(dst, src unsafe.Pointer, size uintptr) //go:linkname alloc runtime.alloc func alloc(size uintptr) unsafe.Pointer +//go:linkname sliceAppend runtime.sliceAppend +func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) + // Copy copies the contents of src into dst until either // dst has been filled or src has been exhausted. func Copy(dst, src Value) int { @@ -792,6 +795,34 @@ func Append(s Value, x ...Value) Value { panic("unimplemented: reflect.Append()") } +// AppendSlice appends a slice t to a slice s and returns the resulting slice. +// The slices s and t must have the same element type. +func AppendSlice(s, t Value) Value { + if s.typecode.Kind() != Slice || t.typecode.Kind() != Slice || s.typecode != t.typecode { + // Not a very helpful error message, but shortened to just one error to + // keep code size down. + panic("reflect.AppendSlice: invalid types") + } + if !s.isExported() || !t.isExported() { + // One of the sides was not exported, so can't access the data. + panic("reflect.AppendSlice: unexported") + } + sSlice := (*sliceHeader)(s.value) + tSlice := (*sliceHeader)(t.value) + elemSize := s.typecode.elem().Size() + ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) + result := &sliceHeader{ + data: ptr, + len: len, + cap: cap, + } + return Value{ + typecode: s.typecode, + value: unsafe.Pointer(result), + flags: valueFlagExported, + } +} + func (v Value) SetMapIndex(key, elem Value) { panic("unimplemented: (reflect.Value).SetMapIndex()") } diff --git a/testdata/reflect.go b/testdata/reflect.go index cd71d984..63cd1675 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -280,6 +280,8 @@ func main() { panic("slice was changed while setting part of it") } + testAppendSlice() + // Test types that are created in reflect and never created elsewhere in a // value-to-interface conversion. v := reflect.ValueOf(new(unreferencedType)) @@ -409,6 +411,45 @@ func assertSize(ok bool, typ string) { } } +// Test whether appending to a slice is equivalent between reflect and native +// slice append. +func testAppendSlice() { + for i := 0; i < 100; i++ { + dst := makeRandomSlice(i) + src := makeRandomSlice(i) + result1 := append(dst, src...) + result2 := reflect.AppendSlice(reflect.ValueOf(dst), reflect.ValueOf(src)).Interface().([]uint32) + if !sliceEqual(result1, result2) { + println("slice: mismatch after runtime.SliceAppend with", len(dst), cap(dst), len(src), cap(src)) + } + } +} + +func makeRandomSlice(max int) []uint32 { + cap := randuint32() % uint32(max+1) + len := randuint32() % (cap + 1) + s := make([]uint32, len, cap) + for i := uint32(0); i < len; i++ { + s[i] = randuint32() + } + return s +} + +func sliceEqual(s1, s2 []uint32) bool { + if len(s1) != len(s2) { + return false + } + for i, val := range s1 { + if s2[i] != val { + return false + } + } + // Note: can't compare cap because the Go implementation has a different + // behavior between the built-in append function and + // reflect.AppendSlice. + return true +} + type unreferencedType int type totallyUnreferencedType int @@ -427,3 +468,18 @@ func TestStructTag() { field := st.Field(0) println(field.Tag.Get("color"), field.Tag.Get("species")) } + +var xorshift32State uint32 = 1 + +func xorshift32(x uint32) uint32 { + // Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" + x ^= x << 13 + x ^= x >> 17 + x ^= x << 5 + return x +} + +func randuint32() uint32 { + xorshift32State = xorshift32(xorshift32State) + return xorshift32State +}