| // 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 |
| } |