1
0
mirror of https://github.com/golang/go synced 2024-11-11 20:01:37 -07:00

reflect: add tests for reflect.Value.Call for the new ABI

This change adds tests for reflect.Value.Call for calling functions
using the new register-based ABI.

For #40724.

Change-Id: Ia9afd43e26dd80c7e36dd135a5b71acce8074801
Reviewed-on: https://go-review.googlesource.com/c/go/+/299269
Trust: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
Michael Anthony Knyszek 2021-03-05 19:47:34 +00:00 committed by Michael Knyszek
parent 79d03ad739
commit bdbba22404
2 changed files with 461 additions and 1 deletions

447
src/reflect/abi_test.go Normal file
View File

@ -0,0 +1,447 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build goexperiment.regabi
//go:build goexperiment.regabi
package reflect_test
import (
"internal/abi"
"math/rand"
"reflect"
"runtime"
"testing"
"testing/quick"
)
func TestReflectValueCallABI(t *testing.T) {
// Enable register-based reflect.Call and ensure we don't
// use potentially incorrect cached versions by clearing
// the cache before we start and after we're done.
var oldRegs struct {
ints, floats int
floatSize uintptr
}
oldRegs.ints = *reflect.IntArgRegs
oldRegs.floats = *reflect.FloatArgRegs
oldRegs.floatSize = *reflect.FloatRegSize
*reflect.IntArgRegs = abi.IntArgRegs
*reflect.FloatArgRegs = abi.FloatArgRegs
*reflect.FloatRegSize = uintptr(abi.EffectiveFloatRegSize)
reflect.ClearLayoutCache()
defer func() {
*reflect.IntArgRegs = oldRegs.ints
*reflect.FloatArgRegs = oldRegs.floats
*reflect.FloatRegSize = oldRegs.floatSize
reflect.ClearLayoutCache()
}()
// Execute the functions defined below which all have the
// same form and perform the same function: pass all arguments
// to return values. The purpose is to test the call boundary
// and make sure it works.
r := rand.New(rand.NewSource(genValueRandSeed))
for _, fn := range []interface{}{
passNone,
passInt,
passInt8,
passInt16,
passInt32,
passInt64,
passUint,
passUint8,
passUint16,
passUint32,
passUint64,
passFloat32,
passFloat64,
passComplex64,
passComplex128,
passManyInt,
passManyFloat64,
passArray1,
passArray,
passArray1Mix,
passString,
// TODO(mknyszek): Test passing interface values.
passSlice,
passPointer,
passStruct1,
passStruct2,
passStruct3,
passStruct4,
passStruct5,
passStruct6,
passStruct7,
passStruct8,
passStruct9,
passStruct10,
// TODO(mknyszek): Test passing unsafe.Pointer values.
// TODO(mknyszek): Test passing chan values.
passStruct11,
passStruct12,
passStruct13,
pass2Struct1,
passEmptyStruct,
} {
fn := reflect.ValueOf(fn)
t.Run(runtime.FuncForPC(fn.Pointer()).Name(), func(t *testing.T) {
typ := fn.Type()
if typ.Kind() != reflect.Func {
t.Fatalf("test case is not a function, has type: %s", typ.String())
}
if typ.NumIn() != typ.NumOut() {
t.Fatalf("test case has different number of inputs and outputs: %d in, %d out", typ.NumIn(), typ.NumOut())
}
var args []reflect.Value
for i := 0; i < typ.NumIn(); i++ {
args = append(args, genValue(t, typ.In(i), r))
}
results := fn.Call(args)
for i := range results {
x, y := args[i].Interface(), results[i].Interface()
if reflect.DeepEqual(x, y) {
continue
}
t.Errorf("arg and result %d differ: got %+v, want %+v", i, x, y)
}
})
}
}
// Functions for testing reflect.Value.Call.
//go:registerparams
//go:noinline
func passNone() {}
//go:registerparams
//go:noinline
func passInt(a int) int {
return a
}
//go:registerparams
//go:noinline
func passInt8(a int8) int8 {
return a
}
//go:registerparams
//go:noinline
func passInt16(a int16) int16 {
return a
}
//go:registerparams
//go:noinline
func passInt32(a int32) int32 {
return a
}
//go:registerparams
//go:noinline
func passInt64(a int64) int64 {
return a
}
//go:registerparams
//go:noinline
func passUint(a uint) uint {
return a
}
//go:registerparams
//go:noinline
func passUint8(a uint8) uint8 {
return a
}
//go:registerparams
//go:noinline
func passUint16(a uint16) uint16 {
return a
}
//go:registerparams
//go:noinline
func passUint32(a uint32) uint32 {
return a
}
//go:registerparams
//go:noinline
func passUint64(a uint64) uint64 {
return a
}
//go:registerparams
//go:noinline
func passFloat32(a float32) float32 {
return a
}
//go:registerparams
//go:noinline
func passFloat64(a float64) float64 {
return a
}
//go:registerparams
//go:noinline
func passComplex64(a complex64) complex64 {
return a
}
//go:registerparams
//go:noinline
func passComplex128(a complex128) complex128 {
return a
}
//go:registerparams
//go:noinline
func passArray1(a [1]uint32) [1]uint32 {
return a
}
//go:registerparams
//go:noinline
func passArray(a [2]uintptr) [2]uintptr {
return a
}
//go:registerparams
//go:noinline
func passArray1Mix(a int, b [1]uint32, c float64) (int, [1]uint32, float64) {
return a, b, c
}
//go:registerparams
//go:noinline
func passString(a string) string {
return a
}
//go:registerparams
//go:noinline
func passSlice(a []byte) []byte {
return a
}
//go:registerparams
//go:noinline
func passPointer(a *byte) *byte {
return a
}
//go:registerparams
//go:noinline
func passManyInt(a, b, c, d, e, f, g, h, i, j int) (int, int, int, int, int, int, int, int, int, int) {
return a, b, c, d, e, f, g, h, i, j
}
//go:registerparams
//go:noinline
func passManyFloat64(a, b, c, d, e, f, g, h, i, j, l, m, n, o, p, q, r, s, t float64) (float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64, float64) {
return a, b, c, d, e, f, g, h, i, j, l, m, n, o, p, q, r, s, t
}
//go:registerparams
//go:noinline
func passStruct1(a Struct1) Struct1 {
return a
}
//go:registerparams
//go:noinline
func passStruct2(a Struct2) Struct2 {
return a
}
//go:registerparams
//go:noinline
func passStruct3(a Struct3) Struct3 {
return a
}
//go:registerparams
//go:noinline
func passStruct4(a Struct4) Struct4 {
return a
}
//go:registerparams
//go:noinline
func passStruct5(a Struct5) Struct5 {
return a
}
//go:registerparams
//go:noinline
func passStruct6(a Struct6) Struct6 {
return a
}
//go:registerparams
//go:noinline
func passStruct7(a Struct7) Struct7 {
return a
}
//go:registerparams
//go:noinline
func passStruct8(a Struct8) Struct8 {
return a
}
//go:registerparams
//go:noinline
func passStruct9(a Struct9) Struct9 {
return a
}
//go:registerparams
//go:noinline
func passStruct10(a Struct10) Struct10 {
return a
}
//go:registerparams
//go:noinline
func passStruct11(a Struct11) Struct11 {
return a
}
//go:registerparams
//go:noinline
func passStruct12(a Struct12) Struct12 {
return a
}
//go:registerparams
//go:noinline
func passStruct13(a Struct13) Struct13 {
return a
}
//go:registerparams
//go:noinline
func pass2Struct1(a, b Struct1) (x, y Struct1) {
return a, b
}
//go:registerparams
//go:noinline
func passEmptyStruct(a int, b struct{}, c float64) (int, struct{}, float64) {
return a, b, c
}
// Struct1 is a simple integer-only aggregate struct.
type Struct1 struct {
A, B, C uint
}
// Struct2 is Struct1 but with an array-typed field that will
// force it to get passed on the stack.
type Struct2 struct {
A, B, C uint
D [2]uint32
}
// Struct3 is Struct2 but with an anonymous array-typed field.
// This should act identically to Struct2.
type Struct3 struct {
A, B, C uint
D [2]uint32
}
// Struct4 has byte-length fields that should
// each use up a whole registers.
type Struct4 struct {
A, B int8
C, D uint8
E bool
}
// Struct5 is a relatively large struct
// with both integer and floating point values.
type Struct5 struct {
A uint16
B int16
C, D uint32
E int32
F, G, H, I, J float32
}
// Struct6 has a nested struct.
type Struct6 struct {
Struct1
}
// Struct7 is a struct with a nested array-typed field
// that cannot be passed in registers as a result.
type Struct7 struct {
Struct1
Struct2
}
// Struct8 is large aggregate struct type that may be
// passed in registers.
type Struct8 struct {
Struct5
Struct1
}
// Struct9 is a type that has an array type nested
// 2 layers deep, and as a result needs to be passed
// on the stack.
type Struct9 struct {
Struct1
Struct7
}
// Struct10 is a struct type that is too large to be
// passed in registers.
type Struct10 struct {
Struct5
Struct8
}
// Struct11 is a struct type that has several reference
// types in it.
type Struct11 struct {
X map[string]int
}
// Struct12 has Struct11 embedded into it to test more
// paths.
type Struct12 struct {
A int
Struct11
}
// Struct13 tests an empty field.
type Struct13 struct {
A int
X struct{}
B int
}
const genValueRandSeed = 0
// genValue generates a pseudorandom reflect.Value with type t.
// The reflect.Value produced by this function is always the same
// for the same type.
func genValue(t *testing.T, typ reflect.Type, r *rand.Rand) reflect.Value {
// Re-seed and reset the PRNG because we want each value with the
// same type to be the same random value.
r.Seed(genValueRandSeed)
v, ok := quick.Value(typ, r)
if !ok {
t.Fatal("failed to generate value")
}
return v
}

View File

@ -4,7 +4,10 @@
package reflect
import "unsafe"
import (
"sync"
"unsafe"
)
// MakeRO returns a copy of v with the read-only flag set.
func MakeRO(v Value) Value {
@ -17,6 +20,12 @@ func IsRO(v Value) bool {
return v.flag&flagStickyRO != 0
}
var (
IntArgRegs = &intArgRegs
FloatArgRegs = &floatArgRegs
FloatRegSize = &floatRegSize
)
var CallGC = &callGC
const PtrSize = ptrSize
@ -122,3 +131,7 @@ func ResolveReflectName(s string) {
type Buffer struct {
buf []byte
}
func ClearLayoutCache() {
layoutCache = sync.Map{}
}