diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go index 4bad490a641..a2fdf0449ba 100644 --- a/src/cmd/compile/internal/gc/reflect.go +++ b/src/cmd/compile/internal/gc/reflect.go @@ -1307,7 +1307,7 @@ ok: // we want be able to find. if t.Sym == nil { switch t.Etype { - case TPTR32, TPTR64, TARRAY, TCHAN, TFUNC, TMAP: + case TPTR32, TPTR64, TARRAY, TCHAN, TFUNC, TMAP, TSTRUCT: slink := typelinksym(t) dsymptr(slink, 0, s, 0) ggloblsym(slink, int32(Widthptr), int16(dupok|obj.RODATA)) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 0ce6588e988..4a76ef86084 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -21,6 +21,8 @@ import ( "sync" "testing" "time" + "unicode" + "unicode/utf8" "unsafe" ) @@ -3958,6 +3960,579 @@ func TestSliceOfGC(t *testing.T) { } } +func TestStructOf(t *testing.T) { + // check construction and use of type not in binary + fields := []StructField{ + StructField{ + Name: "S", + Tag: "s", + Type: TypeOf(""), + }, + StructField{ + Name: "X", + Tag: "x", + Type: TypeOf(byte(0)), + }, + StructField{ + Name: "Y", + Type: TypeOf(uint64(0)), + }, + StructField{ + Name: "Z", + Type: TypeOf([3]uint16{}), + }, + } + + st := StructOf(fields) + v := New(st).Elem() + runtime.GC() + v.FieldByName("X").Set(ValueOf(byte(2))) + v.FieldByIndex([]int{1}).Set(ValueOf(byte(1))) + runtime.GC() + + s := fmt.Sprint(v.Interface()) + want := `{ 1 0 [0 0 0]}` + if s != want { + t.Errorf("constructed struct = %s, want %s", s, want) + } + + // check the size, alignment and field offsets + stt := TypeOf(struct { + String string + X byte + Y uint64 + Z [3]uint16 + }{}) + if st.Size() != stt.Size() { + t.Errorf("constructed struct size = %v, want %v", st.Size(), stt.Size()) + } + if st.Align() != stt.Align() { + t.Errorf("constructed struct align = %v, want %v", st.Align(), stt.Align()) + } + if st.FieldAlign() != stt.FieldAlign() { + t.Errorf("constructed struct field align = %v, want %v", st.FieldAlign(), stt.FieldAlign()) + } + for i := 0; i < st.NumField(); i++ { + o1 := st.Field(i).Offset + o2 := stt.Field(i).Offset + if o1 != o2 { + t.Errorf("constructed struct field %v offset = %v, want %v", i, o1, o2) + } + } + + // check duplicate names + shouldPanic(func() { + StructOf([]StructField{ + StructField{Name: "string", Type: TypeOf("")}, + StructField{Name: "string", Type: TypeOf("")}, + }) + }) + shouldPanic(func() { + StructOf([]StructField{ + StructField{Type: TypeOf("")}, + StructField{Name: "string", Type: TypeOf("")}, + }) + }) + shouldPanic(func() { + StructOf([]StructField{ + StructField{Type: TypeOf("")}, + StructField{Type: TypeOf("")}, + }) + }) + // check that type already in binary is found + checkSameType(t, Zero(StructOf(fields[2:3])).Interface(), struct{ Y uint64 }{}) +} + +func TestStructOfExportRules(t *testing.T) { + type S1 struct{} + type s2 struct{} + type ΦType struct{} + type φType struct{} + + testPanic := func(i int, mustPanic bool, f func()) { + defer func() { + err := recover() + if err == nil && mustPanic { + t.Errorf("test-%d did not panic", i) + } + if err != nil && !mustPanic { + t.Errorf("test-%d panicked: %v\n", i, err) + } + }() + f() + } + + for i, test := range []struct { + field StructField + mustPanic bool + exported bool + }{ + { + field: StructField{Name: "", Type: TypeOf(S1{})}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf((*S1)(nil))}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf(s2{})}, + mustPanic: false, + exported: false, + }, + { + field: StructField{Name: "", Type: TypeOf((*s2)(nil))}, + mustPanic: false, + exported: false, + }, + { + field: StructField{Name: "", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, + mustPanic: true, + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "S", Type: TypeOf(S1{})}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf((*S1)(nil))}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf(s2{})}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "S", Type: TypeOf((*s2)(nil))}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "s", Type: TypeOf(S1{})}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf((*S1)(nil))}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf(s2{})}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf((*s2)(nil))}, + mustPanic: true, + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf(S1{}), PkgPath: "other/pkg"}, + mustPanic: true, // TODO(sbinet): creating a name with a package path + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf((*S1)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, // TODO(sbinet): creating a name with a package path + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf(s2{}), PkgPath: "other/pkg"}, + mustPanic: true, // TODO(sbinet): creating a name with a package path + exported: false, + }, + { + field: StructField{Name: "s", Type: TypeOf((*s2)(nil)), PkgPath: "other/pkg"}, + mustPanic: true, // TODO(sbinet): creating a name with a package path + exported: false, + }, + { + field: StructField{Name: "", Type: TypeOf(ΦType{})}, + mustPanic: true, // TODO(sbinet): creating a struct with UTF-8 fields not supported + exported: true, + }, + { + field: StructField{Name: "", Type: TypeOf(φType{})}, + mustPanic: true, // TODO(sbinet): creating a struct with UTF-8 fields not supported + exported: false, + }, + { + field: StructField{Name: "Φ", Type: TypeOf(0)}, + mustPanic: false, + exported: true, + }, + { + field: StructField{Name: "φ", Type: TypeOf(0)}, + mustPanic: false, + exported: false, + }, + } { + testPanic(i, test.mustPanic, func() { + typ := StructOf([]StructField{test.field}) + if typ == nil { + t.Errorf("test-%d: error creating struct type", i) + return + } + field := typ.Field(0) + n := field.Name + if n == "" { + n = field.Type.Name() + } + exported := isExported(n) + if exported != test.exported { + t.Errorf("test-%d: got exported=%v want exported=%v", exported, test.exported) + } + }) + } +} + +// isExported reports whether name is an exported Go symbol +// (that is, whether it begins with an upper-case letter). +// +func isExported(name string) bool { + ch, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(ch) +} + +func TestStructOfGC(t *testing.T) { + type T *uintptr + tt := TypeOf(T(nil)) + fields := []StructField{ + {Name: "X", Type: tt}, + {Name: "Y", Type: tt}, + } + st := StructOf(fields) + + const n = 10000 + var x []interface{} + for i := 0; i < n; i++ { + v := New(st).Elem() + for j := 0; j < v.NumField(); j++ { + p := new(uintptr) + *p = uintptr(i*n + j) + v.Field(j).Set(ValueOf(p).Convert(tt)) + } + x = append(x, v.Interface()) + } + runtime.GC() + + for i, xi := range x { + v := ValueOf(xi) + for j := 0; j < v.NumField(); j++ { + k := v.Field(j).Elem().Interface() + if k != uintptr(i*n+j) { + t.Errorf("lost x[%d].%c = %d, want %d", i, "XY"[j], k, i*n+j) + } + } + } +} + +func TestStructOfAlg(t *testing.T) { + st := StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf(int(0))}}) + v1 := New(st).Elem() + v2 := New(st).Elem() + if !DeepEqual(v1.Interface(), v1.Interface()) { + t.Errorf("constructed struct %v not equal to itself", v1.Interface()) + } + v1.FieldByName("X").Set(ValueOf(int(1))) + if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should not be equal", i1, i2) + } + + st = StructOf([]StructField{{Name: "X", Tag: "x", Type: TypeOf([]int(nil))}}) + v1 = New(st).Elem() + shouldPanic(func() { _ = v1.Interface() == v1.Interface() }) +} + +func TestStructOfGenericAlg(t *testing.T) { + st1 := StructOf([]StructField{ + {Name: "X", Tag: "x", Type: TypeOf(int64(0))}, + {Name: "Y", Type: TypeOf(string(""))}, + }) + st := StructOf([]StructField{ + {Name: "S0", Type: st1}, + {Name: "S1", Type: st1}, + }) + + for _, table := range []struct { + rt Type + idx []int + }{ + { + rt: st, + idx: []int{0, 1}, + }, + { + rt: st1, + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([0]int{})}, + {Name: "YY", Type: TypeOf("")}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([0]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([2]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([1]int{})}, + {Name: "YY", Type: TypeOf("")}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([1]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([1]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf([2]int{})}, + {Name: "YY", Type: TypeOf("")}, + {Name: "ZZ", Type: TypeOf([2]int{})}, + }, + ), + idx: []int{1}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf(int64(0))}, + {Name: "YY", Type: TypeOf(byte(0))}, + {Name: "ZZ", Type: TypeOf("")}, + }, + ), + idx: []int{2}, + }, + { + rt: StructOf( + []StructField{ + {Name: "XX", Type: TypeOf(int64(0))}, + {Name: "YY", Type: TypeOf(int64(0))}, + {Name: "ZZ", Type: TypeOf("")}, + {Name: "AA", Type: TypeOf([1]int64{})}, + }, + ), + idx: []int{2}, + }, + } { + v1 := New(table.rt).Elem() + v2 := New(table.rt).Elem() + + if !DeepEqual(v1.Interface(), v1.Interface()) { + t.Errorf("constructed struct %v not equal to itself", v1.Interface()) + } + + v1.FieldByIndex(table.idx).Set(ValueOf("abc")) + v2.FieldByIndex(table.idx).Set(ValueOf("def")) + if i1, i2 := v1.Interface(), v2.Interface(); DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should not be equal", i1, i2) + } + + abc := "abc" + v1.FieldByIndex(table.idx).Set(ValueOf(abc)) + val := "+" + abc + "-" + v2.FieldByIndex(table.idx).Set(ValueOf(val[1:4])) + if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should be equal", i1, i2) + } + + // Test hash + m := MakeMap(MapOf(table.rt, TypeOf(int(0)))) + m.SetMapIndex(v1, ValueOf(1)) + if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { + t.Errorf("constructed structs %#v and %#v have different hashes", i1, i2) + } + + v2.FieldByIndex(table.idx).Set(ValueOf("abc")) + if i1, i2 := v1.Interface(), v2.Interface(); !DeepEqual(i1, i2) { + t.Errorf("constructed structs %v and %v should be equal", i1, i2) + } + + if i1, i2 := v1.Interface(), v2.Interface(); !m.MapIndex(v2).IsValid() { + t.Errorf("constructed structs %v and %v have different hashes", i1, i2) + } + } +} + +func TestStructOfDirectIface(t *testing.T) { + { + type T struct{ X [1]*byte } + i1 := Zero(TypeOf(T{})).Interface() + v1 := ValueOf(&i1).Elem() + p1 := v1.InterfaceData()[1] + + i2 := Zero(StructOf([]StructField{ + { + Name: "X", + Type: ArrayOf(1, TypeOf((*int8)(nil))), + }, + })).Interface() + v2 := ValueOf(&i2).Elem() + p2 := v2.InterfaceData()[1] + + if p1 != 0 { + t.Errorf("got p1=%v. want=%v", p1, nil) + } + + if p2 != 0 { + t.Errorf("got p2=%v. want=%v", p2, nil) + } + } + { + type T struct{ X [0]*byte } + i1 := Zero(TypeOf(T{})).Interface() + v1 := ValueOf(&i1).Elem() + p1 := v1.InterfaceData()[1] + + i2 := Zero(StructOf([]StructField{ + { + Name: "X", + Type: ArrayOf(0, TypeOf((*int8)(nil))), + }, + })).Interface() + v2 := ValueOf(&i2).Elem() + p2 := v2.InterfaceData()[1] + + if p1 == 0 { + t.Errorf("got p1=%v. want=not-%v", p1, nil) + } + + if p2 == 0 { + t.Errorf("got p2=%v. want=not-%v", p2, nil) + } + } +} + +type StructI int + +func (i StructI) Get() int { return int(i) } + +type StructIPtr int + +func (i *StructIPtr) Get() int { return int(*i) } + +func TestStructOfWithInterface(t *testing.T) { + const want = 42 + type Iface interface { + Get() int + } + for i, table := range []struct { + typ Type + val Value + impl bool + }{ + { + typ: TypeOf(StructI(want)), + val: ValueOf(StructI(want)), + impl: true, + }, + { + typ: PtrTo(TypeOf(StructI(want))), + val: ValueOf(func() interface{} { + v := StructI(want) + return &v + }()), + impl: true, + }, + { + typ: PtrTo(TypeOf(StructIPtr(want))), + val: ValueOf(func() interface{} { + v := StructIPtr(want) + return &v + }()), + impl: true, + }, + { + typ: TypeOf(StructIPtr(want)), + val: ValueOf(StructIPtr(want)), + impl: false, + }, + // { + // typ: TypeOf((*Iface)(nil)).Elem(), // FIXME(sbinet): fix method.ifn/tfn + // val: ValueOf(StructI(want)), + // impl: true, + // }, + } { + rt := StructOf( + []StructField{ + { + Name: "", + PkgPath: "", + Type: table.typ, + }, + }, + ) + rv := New(rt).Elem() + rv.Field(0).Set(table.val) + + if _, ok := rv.Interface().(Iface); ok != table.impl { + if table.impl { + t.Errorf("test-%d: type=%v fails to implement Iface.\n", i, table.typ) + } else { + t.Errorf("test-%d: type=%v should NOT implement Iface\n", table.typ) + } + continue + } + + if !table.impl { + continue + } + + v := rv.Interface().(Iface).Get() + if v != want { + t.Errorf("test-%d: x.Get()=%v. want=%v\n", i, v, want) + } + + fct := rv.MethodByName("Get") + out := fct.Call(nil) + if !DeepEqual(out[0].Interface(), want) { + t.Errorf("test-%d: x.Get()=%v. want=%v\n", i, out[0].Interface(), want) + } + } +} + func TestChanOf(t *testing.T) { // check construction and use of type not in binary type T string diff --git a/src/reflect/type.go b/src/reflect/type.go index c7a03138096..8f13acf26ee 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -484,6 +484,46 @@ func round(n, a uintptr) uintptr { return (n + a - 1) &^ (a - 1) } +func newName(n, tag, pkgPath string, exported bool) name { + if len(n) > 1<<16-1 { + panic("reflect.nameFrom: name too long: " + n) + } + if len(tag) > 1<<16-1 { + panic("reflect.nameFrom: tag too long: " + tag) + } + + var bits byte + l := 1 + 2 + len(n) + if exported { + bits |= 1 << 0 + } + if len(tag) > 0 { + l += 2 + len(tag) + bits |= 1 << 1 + } + if pkgPath != "" { + bits |= 1 << 2 + } + + b := make([]byte, l) + b[0] = bits + b[1] = uint8(len(n) >> 8) + b[2] = uint8(len(n)) + copy(b[3:], n) + if len(tag) > 0 { + tb := b[3+len(n):] + tb[0] = uint8(len(tag) >> 8) + tb[1] = uint8(len(tag)) + copy(tb[2:], tag) + } + + if pkgPath != "" { + panic("reflect: creating a name with a package path is not supported") + } + + return name{bytes: &b[0]} +} + /* * The compiler knows the exact layout of all the data structures above. * The compiler does not know about the data structures and methods below. @@ -562,11 +602,7 @@ func (t *rtype) uncommon() *uncommonType { } switch t.Kind() { case Struct: - type u struct { - structType - u uncommonType - } - return &(*u)(unsafe.Pointer(t)).u + return &(*structTypeWithMethods)(unsafe.Pointer(t)).u case Ptr: type u struct { ptrType @@ -1572,7 +1608,7 @@ func typesByString(s string) []*rtype { return ret } -// The lookupCache caches ChanOf, MapOf, and SliceOf lookups. +// The lookupCache caches ArrayOf, ChanOf, MapOf and SliceOf lookups. var lookupCache struct { sync.RWMutex m map[cacheKey]*rtype @@ -2112,6 +2148,431 @@ func SliceOf(t Type) Type { return cachePut(ckey, &slice.rtype) } +// structTypeWithMethods is a structType created at runtime with StructOf. +// It is needed to pin the []method slice from its associated uncommonType struct. +// Keep in sync with the memory layout of structType. +type structTypeWithMethods struct { + structType + u uncommonType +} + +// The structLookupCache caches StructOf lookups. +// StructOf does not share the common lookupCache since we need to pin +// the *structType and its associated *uncommonType (especially the +// []method slice field of that uncommonType.) +var structLookupCache struct { + sync.RWMutex + m map[uint32][]*structTypeWithMethods // keyed by hash calculated in StructOf +} + +// StructOf returns the struct type containing fields. +// The Offset and Index fields are ignored and computed as they would be +// by the compiler. +// +// StructOf does not support creating structs with UTF-8 field names or +// UTF-8 (embedded) type names. +// This limitation may be lifted eventually. +func StructOf(fields []StructField) Type { + var ( + hash = fnv1(0, []byte("struct {")...) + size uintptr + typalign uint8 + comparable = true + hashable = true + typ = new(structTypeWithMethods) + + fs = make([]structField, len(fields)) + repr = make([]byte, 0, 64) + fset = map[string]struct{}{} // fields' names + + hasPtr = false // records whether at least one struct-field is a pointer + hasGCProg = false // records whether a struct-field type has a GCProg + ) + + repr = append(repr, "struct {"...) + for i, field := range fields { + if field.Type == nil { + panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") + } + f := runtimeStructField(field) + ft := f.typ + if ft.kind&kindGCProg != 0 { + hasGCProg = true + } + if ft.pointers() { + hasPtr = true + } + + name := "" + // Update string and hash + if f.name.nameLen() > 0 { + hash = fnv1(hash, []byte(f.name.name())...) + repr = append(repr, (" " + f.name.name())...) + name = f.name.name() + } else { + // Embedded field + if f.typ.Kind() == Ptr { + // Embedded ** and *interface{} are illegal + elem := ft.Elem() + if k := elem.Kind(); k == Ptr || k == Interface { + panic("reflect.StructOf: illegal anonymous field type " + ft.string) + } + name = elem.String() + } else { + name = ft.string + } + // TODO(sbinet) check for syntactically impossible type names? + + switch f.typ.Kind() { + case Interface: + ift := (*interfaceType)(unsafe.Pointer(ft)) + for im, m := range ift.methods { + if m.name.pkgPath() != nil { + // TODO(sbinet) + panic("reflect: embedded interface with unexported method(s) not implemented") + } + + var ( + ifield = i + imethod = im + ifn Value + tfn Value + ) + + if ft.kind&kindDirectIface != 0 { + tfn = MakeFunc(m.typ, func(in []Value) []Value { + var args []Value + var recv = in[0] + if len(in) > 1 { + args = in[1:] + } + return recv.Field(ifield).Method(imethod).Call(args) + }) + ifn = MakeFunc(m.typ, func(in []Value) []Value { + var args []Value + var recv = in[0] + if len(in) > 1 { + args = in[1:] + } + return recv.Field(ifield).Method(imethod).Call(args) + }) + + } else { + tfn = MakeFunc(m.typ, func(in []Value) []Value { + var args []Value + var recv = in[0] + if len(in) > 1 { + args = in[1:] + } + return recv.Field(ifield).Method(imethod).Call(args) + }) + ifn = MakeFunc(m.typ, func(in []Value) []Value { + var args []Value + var recv = Indirect(in[0]) + if len(in) > 1 { + args = in[1:] + } + return recv.Field(ifield).Method(imethod).Call(args) + }) + + } + + typ.u.methods = append( + typ.u.methods, + method{ + name: m.name, + mtyp: m.typ, + ifn: unsafe.Pointer(&ifn), + tfn: unsafe.Pointer(&tfn), + }, + ) + } + case Ptr: + ptr := (*ptrType)(unsafe.Pointer(ft)) + if unt := ptr.uncommon(); unt != nil { + for _, m := range unt.methods { + if m.name.pkgPath() != nil { + // TODO(sbinet) + panic("reflect: embedded interface with unexported method(s) not implemented") + } + typ.u.methods = append(typ.u.methods, m) + } + } + if unt := ptr.elem.uncommon(); unt != nil { + for _, m := range unt.methods { + if m.name.pkgPath() != nil { + // TODO(sbinet) + panic("reflect: embedded interface with unexported method(s) not implemented") + } + typ.u.methods = append(typ.u.methods, m) + } + } + default: + if unt := ft.uncommon(); unt != nil { + for _, m := range unt.methods { + if m.name.pkgPath() != nil { + // TODO(sbinet) + panic("reflect: embedded interface with unexported method(s) not implemented") + } + typ.u.methods = append(typ.u.methods, m) + } + } + } + } + if _, dup := fset[name]; dup { + panic("reflect.StructOf: duplicate field " + name) + } + fset[name] = struct{}{} + + hash = fnv1(hash, byte(ft.hash>>24), byte(ft.hash>>16), byte(ft.hash>>8), byte(ft.hash)) + + repr = append(repr, (" " + ft.string)...) + if f.name.tagLen() > 0 { + hash = fnv1(hash, []byte(f.name.tag())...) + repr = append(repr, (" " + strconv.Quote(f.name.tag()))...) + } + if i < len(fields)-1 { + repr = append(repr, ';') + } + + comparable = comparable && (ft.alg.equal != nil) + hashable = hashable && (ft.alg.hash != nil) + + f.offset = align(size, uintptr(ft.align)) + if ft.align > typalign { + typalign = ft.align + } + size = f.offset + ft.size + + fs[i] = f + } + if len(fs) > 0 { + repr = append(repr, ' ') + } + repr = append(repr, '}') + hash = fnv1(hash, '}') + str := string(repr) + + // Round the size up to be a multiple of the alignment. + size = align(size, uintptr(typalign)) + + // Make the struct type. + var istruct interface{} = struct{}{} + prototype := *(**structType)(unsafe.Pointer(&istruct)) + typ.structType = *prototype + typ.structType.fields = fs + + // Look in cache + structLookupCache.RLock() + for _, t := range structLookupCache.m[hash] { + if haveIdenticalUnderlyingType(&typ.rtype, &t.rtype) { + structLookupCache.RUnlock() + return &t.rtype + } + } + structLookupCache.RUnlock() + + // not in cache, lock and retry + structLookupCache.Lock() + defer structLookupCache.Unlock() + if structLookupCache.m == nil { + structLookupCache.m = make(map[uint32][]*structTypeWithMethods) + } + for _, t := range structLookupCache.m[hash] { + if haveIdenticalUnderlyingType(&typ.rtype, &t.rtype) { + return &t.rtype + } + } + + // Look in known types. + for _, t := range typesByString(str) { + if haveIdenticalUnderlyingType(&typ.rtype, t) { + // even if 't' wasn't a structType with methods, we should be ok + // as the 'u uncommonType' field won't be accessed except when + // tflag&tflagUncommon is set. + tt := (*structTypeWithMethods)(unsafe.Pointer(t)) + structLookupCache.m[hash] = append(structLookupCache.m[hash], tt) + return &tt.rtype + } + } + + typ.string = str + typ.hash = hash + typ.size = size + typ.align = typalign + typ.fieldAlign = typalign + if len(typ.u.methods) > 0 { + typ.tflag |= tflagUncommon + } + if !hasPtr { + typ.kind |= kindNoPointers + } else { + typ.kind &^= kindNoPointers + } + + if hasGCProg { + lastPtrField := 0 + for i, ft := range fs { + if ft.typ.pointers() { + lastPtrField = i + } + } + prog := []byte{0, 0, 0, 0} // will be length of prog + for i, ft := range fs { + if i > lastPtrField { + // gcprog should not include anything for any field after + // the last field that contains pointer data + break + } + // FIXME(sbinet) handle padding, fields smaller than a word + elemGC := (*[1 << 30]byte)(unsafe.Pointer(ft.typ.gcdata))[:] + elemPtrs := ft.typ.ptrdata / ptrSize + switch { + case ft.typ.kind&kindGCProg == 0 && ft.typ.ptrdata != 0: + // Element is small with pointer mask; use as literal bits. + mask := elemGC + // Emit 120-bit chunks of full bytes (max is 127 but we avoid using partial bytes). + var n uintptr + for n := elemPtrs; n > 120; n -= 120 { + prog = append(prog, 120) + prog = append(prog, mask[:15]...) + mask = mask[15:] + } + prog = append(prog, byte(n)) + prog = append(prog, mask[:(n+7)/8]...) + case ft.typ.kind&kindGCProg != 0: + // Element has GC program; emit one element. + elemProg := elemGC[4 : 4+*(*uint32)(unsafe.Pointer(&elemGC[0]))-1] + prog = append(prog, elemProg...) + } + // Pad from ptrdata to size. + elemWords := ft.typ.size / ptrSize + if elemPtrs < elemWords { + // Emit literal 0 bit, then repeat as needed. + prog = append(prog, 0x01, 0x00) + if elemPtrs+1 < elemWords { + prog = append(prog, 0x81) + prog = appendVarint(prog, elemWords-elemPtrs-1) + } + } + } + *(*uint32)(unsafe.Pointer(&prog[0])) = uint32(len(prog) - 4) + typ.kind |= kindGCProg + typ.gcdata = &prog[0] + } else { + typ.kind &^= kindGCProg + bv := new(bitVector) + addTypeBits(bv, 0, typ.common()) + if len(bv.data) > 0 { + typ.gcdata = &bv.data[0] + } + } + typ.ptrdata = typeptrdata(typ.common()) + typ.alg = new(typeAlg) + if hashable { + typ.alg.hash = func(p unsafe.Pointer, seed uintptr) uintptr { + o := seed + for _, ft := range typ.fields { + pi := unsafe.Pointer(uintptr(p) + ft.offset) + o = ft.typ.alg.hash(pi, o) + } + return o + } + } + + if comparable { + typ.alg.equal = func(p, q unsafe.Pointer) bool { + for _, ft := range typ.fields { + pi := unsafe.Pointer(uintptr(p) + ft.offset) + qi := unsafe.Pointer(uintptr(q) + ft.offset) + if !ft.typ.alg.equal(pi, qi) { + return false + } + } + return true + } + } + + switch { + case len(fs) == 1 && !ifaceIndir(fs[0].typ): + // structs of 1 direct iface type can be direct + typ.kind |= kindDirectIface + default: + typ.kind &^= kindDirectIface + } + + structLookupCache.m[hash] = append(structLookupCache.m[hash], typ) + return &typ.rtype +} + +func runtimeStructField(field StructField) structField { + exported := field.PkgPath == "" + if field.Name == "" { + t := field.Type + if t.Kind() == Ptr { + t = t.Elem() + } + exported = isExported(t.Name()) + } else if exported { + b0 := field.Name[0] + if ('a' <= b0 && b0 <= 'z') || b0 == '_' { + panic("reflect.StructOf: field \"" + field.Name + "\" is unexported but has no PkgPath") + } + } + + return structField{ + name: newName(field.Name, string(field.Tag), field.PkgPath, exported), + typ: field.Type.common(), + offset: 0, + } +} + +func isExported(s string) bool { + if s == "" { + return false + } + // FIXME(sbinet): handle utf8/runes (see https://golang.org/issue/15064) + // TODO: turn rtype.string into a reflect.name type, and put the exported + // bit on there which can be checked here with field.Type.(*rtype).string.isExported() + // When done, remove the documented limitation of StructOf. + r := s[0] + switch { + case 'A' <= r && r <= 'Z': + return true + case r == '_' || 'a' <= r && r <= 'z': + return false + default: + panic("reflect.StructOf: creating a struct with UTF-8 fields is not supported yet") + } +} + +// typeptrdata returns the length in bytes of the prefix of t +// containing pointer data. Anything after this offset is scalar data. +// keep in sync with ../cmd/compile/internal/gc/reflect.go +func typeptrdata(t *rtype) uintptr { + if !t.pointers() { + return 0 + } + switch t.Kind() { + case Struct: + st := (*structType)(unsafe.Pointer(t)) + // find the last field that has pointers. + field := 0 + for i := range st.fields { + ft := st.fields[i].typ + if ft.pointers() { + field = i + } + } + f := st.fields[field] + return f.offset + f.typ.ptrdata + + default: + panic("reflect.typeptrdata: unexpected type, " + t.String()) + } + return 0 +} + // See cmd/compile/internal/gc/reflect.go for derivation of constant. const maxPtrmaskBytes = 2048