Commit f38f43d0 authored by Josh Bleecher Snyder's avatar Josh Bleecher Snyder

cmd/compile: shrink gc.Type in half

Many of Type's fields are etype-specific.
This CL organizes them into their own auxiliary types,
duplicating a few fields as necessary,
and adds an Extra field to hold them.
It also sorts the remaining fields for better struct packing.
It also improves documentation for most fields.

This reduces the size of Type at the cost of some extra allocations.
There's no CPU impact; memory impact below.
It also makes the natural structure of Type clearer.

Passes toolstash -cmp on all architectures.

Ideas for future work in this vein:

(1) Width and Align probably only need to be
stored for Struct and Array types.
The refactoring to accomplish this would hopefully
also eliminate TFUNCARGS and TCHANARGS entirely.

(2) Maplineno is sparsely used and could probably better be
stored in a separate map[*Type]int32, with mapqueue updated
to store both a Node and a line number.

(3) The Printed field may be removable once the old (non-binary)
importer/exported has been removed.

(4) StructType's fields field could be changed from *[]*Field to []*Field,
which would remove a common allocation.

(5) I believe that Type.Nod can be moved to ForwardType. Separate CL.

name       old alloc/op     new alloc/op     delta
Template       57.9MB ± 0%      55.9MB ± 0%  -3.43%        (p=0.000 n=50+50)
Unicode        38.3MB ± 0%      37.8MB ± 0%  -1.39%        (p=0.000 n=50+50)
GoTypes         185MB ± 0%       180MB ± 0%  -2.56%        (p=0.000 n=50+50)
Compiler        824MB ± 0%       806MB ± 0%  -2.19%        (p=0.000 n=50+50)

name       old allocs/op    new allocs/op    delta
Template         486k ± 0%        497k ± 0%  +2.25%        (p=0.000 n=50+50)
Unicode          377k ± 0%        379k ± 0%  +0.55%        (p=0.000 n=50+50)
GoTypes         1.39M ± 0%       1.42M ± 0%  +1.63%        (p=0.000 n=50+50)
Compiler        5.52M ± 0%       5.57M ± 0%  +0.84%        (p=0.000 n=47+50)

Change-Id: I828488eeb74902b013d5ae4cf844de0b6c0dfc87
Reviewed-on: https://go-review.googlesource.com/21611Reviewed-by: default avatarMatthew Dempsky <mdempsky@google.com>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent a25e368f
......@@ -198,11 +198,11 @@ func dowidth(t *Type) {
// make fake type to check later to
// trigger channel argument check.
t1 := typWrapper(TCHANARGS, t)
t1 := typChanArgs(t)
checkwidth(t1)
case TCHANARGS:
t1 := t.Wrapped()
t1 := t.ChanArgs()
dowidth(t1) // just in case
if t1.Elem().Width >= 1<<16 {
Yyerror("channel element type too large (>64kB)")
......@@ -271,18 +271,18 @@ func dowidth(t *Type) {
// make fake type to check later to
// trigger function argument computation.
case TFUNC:
t1 := typWrapper(TFUNCARGS, t)
t1 := typFuncArgs(t)
checkwidth(t1)
w = int64(Widthptr) // width of func type is pointer
// function is 3 cated structures;
// compute their widths as side-effect.
case TFUNCARGS:
t1 := t.Wrapped()
t1 := t.FuncArgs()
w = widstruct(t1, t1.Recvs(), 0, 0)
w = widstruct(t1, t1.Params(), w, Widthreg)
w = widstruct(t1, t1.Results(), w, Widthreg)
t1.Argwid = w
t1.Extra.(*FuncType).Argwid = w
if w%int64(Widthreg) != 0 {
Warn("bad type %v %d\n", t1, w)
}
......@@ -386,7 +386,7 @@ func Argsize(t *Type) int {
}
}
w = (w + int64(Widthptr) - 1) &^ (int64(Widthptr) - 1)
w = Rnd(w, int64(Widthptr))
if int64(int(w)) != w {
Fatalf("argsize too big")
}
......
......@@ -602,7 +602,7 @@ func (p *exporter) typ(t *Type) {
case TDDDFIELD:
// see p.param use of TDDDFIELD
p.tag(dddTag)
p.typ(t.Wrapped())
p.typ(t.DDDField())
case TSTRUCT:
p.tag(structTag)
......@@ -768,7 +768,7 @@ func (p *exporter) param(q *Field, n int, numbered bool) {
t := q.Type
if q.Isddd {
// create a fake type to encode ... just for the p.typ call
t = typWrapper(TDDDFIELD, t.Elem())
t = typDDDField(t.Elem())
}
p.typ(t)
if n > 0 {
......
......@@ -359,16 +359,20 @@ func (p *importer) typ() *Type {
case arrayTag, sliceTag:
t = p.newtyp(TARRAY)
var bound int64
if i == arrayTag {
t.SetNumElem(p.int64())
bound = p.int64()
}
elem := p.typ()
if i == arrayTag {
t.Extra = &ArrayType{Elem: elem, Bound: bound}
} else {
t.SetNumElem(sliceBound)
t.Extra = SliceType{Elem: elem}
}
t.Type = p.typ()
case dddTag:
t = p.newtyp(TDDDFIELD)
t.Type = p.typ()
t.Extra = DDDFieldType{T: p.typ()}
case structTag:
t = p.newtyp(TSTRUCT)
......@@ -376,7 +380,7 @@ func (p *importer) typ() *Type {
case pointerTag:
t = p.newtyp(Tptr)
t.Type = p.typ()
t.Extra = PtrType{Elem: p.typ()}
case signatureTag:
t = p.newtyp(TFUNC)
......@@ -393,13 +397,15 @@ func (p *importer) typ() *Type {
case mapTag:
t = p.newtyp(TMAP)
t.Down = p.typ() // key
t.Type = p.typ() // val
mt := t.MapType()
mt.Key = p.typ()
mt.Val = p.typ()
case chanTag:
t = p.newtyp(TCHAN)
t.Chan = ChanDir(p.int())
t.Type = p.typ()
ct := t.ChanType()
ct.Dir = ChanDir(p.int())
ct.Elem = p.typ()
default:
Fatalf("importer: unexpected type (tag = %d)", i)
......@@ -444,7 +450,7 @@ func (p *importer) field() *Node {
// anonymous field - typ must be T or *T and T must be a type name
s := typ.Sym
if s == nil && typ.IsPtr() {
s = typ.Type.Sym // deref
s = typ.Elem().Sym // deref
}
pkg := importpkg
if sym != nil {
......@@ -531,7 +537,7 @@ func (p *importer) param(named bool) *Node {
isddd := false
if typ.Etype == TDDDFIELD {
// TDDDFIELD indicates wrapped ... slice type
typ = typSlice(typ.Wrapped())
typ = typSlice(typ.DDDField())
isddd = true
}
......
......@@ -743,8 +743,8 @@ func checkembeddedtype(t *Type) {
if t.IsPtr() {
Yyerror("embedded type cannot be a pointer")
} else if t.Etype == TFORW && t.Embedlineno == 0 {
t.Embedlineno = lineno
} else if t.Etype == TFORW && t.ForwardType().Embedlineno == 0 {
t.ForwardType().Embedlineno = lineno
}
}
......@@ -855,7 +855,7 @@ func tostruct0(t *Type, l []*Node) {
func tofunargs(l []*Node) *Type {
t := typ(TSTRUCT)
t.Funarg = true
t.StructType().Funarg = true
fields := make([]*Field, len(l))
for i, n := range l {
......@@ -1061,11 +1061,11 @@ func functype0(t *Type, this *Node, in, out []*Node) {
t.Broke = true
}
t.Outnamed = false
t.FuncType().Outnamed = false
if len(out) > 0 && out[0].Left != nil && out[0].Left.Orig != nil {
s := out[0].Left.Orig.Sym
if s != nil && (s.Name[0] != '~' || s.Name[1] != 'r') { // ~r%d is the name invented for an unnamed result
t.Outnamed = true
t.FuncType().Outnamed = true
}
}
}
......
......@@ -592,7 +592,7 @@ func dumpasmhdr() {
case OTYPE:
t := n.Type
if !t.IsStruct() || t.Map != nil || t.IsFuncArgStruct() {
if !t.IsStruct() || t.StructType().Map != nil || t.IsFuncArgStruct() {
break
}
fmt.Fprintf(b, "#define %s__size %d\n", t.Sym.Name, int(t.Width))
......
......@@ -671,19 +671,20 @@ func typefmt(t *Type, flag FmtFlag) string {
return buf.String()
case TSTRUCT:
if t.Map != nil {
if m := t.StructType().Map; m != nil {
mt := m.MapType()
// Format the bucket struct for map[x]y as map.bucket[x]y.
// This avoids a recursive print that generates very long names.
if t.Map.Bucket == t {
return "map.bucket[" + t.Map.Key().String() + "]" + t.Map.Val().String()
if mt.Bucket == t {
return "map.bucket[" + m.Key().String() + "]" + m.Val().String()
}
if t.Map.Hmap == t {
return "map.hdr[" + t.Map.Key().String() + "]" + t.Map.Val().String()
if mt.Hmap == t {
return "map.hdr[" + m.Key().String() + "]" + m.Val().String()
}
if t.Map.Hiter == t {
return "map.iter[" + t.Map.Key().String() + "]" + t.Map.Val().String()
if mt.Hiter == t {
return "map.iter[" + m.Key().String() + "]" + m.Val().String()
}
Yyerror("unknown internal map type")
......@@ -735,7 +736,7 @@ func typefmt(t *Type, flag FmtFlag) string {
if fmtmode == FExp {
Fatalf("cannot use TDDDFIELD with old exporter")
}
return fmt.Sprintf("%v <%v> %v", Econv(t.Etype), t.Sym, t.Wrapped())
return fmt.Sprintf("%v <%v> %v", Econv(t.Etype), t.Sym, t.DDDField())
}
if fmtmode == FExp {
......
......@@ -375,7 +375,7 @@ func compile(fn *Node) {
// set up domain for labels
clearlabels()
if Curfn.Type.Outnamed {
if Curfn.Type.FuncType().Outnamed {
// add clearing of the output parameters
for _, t := range Curfn.Type.Results().Fields().Slice() {
if t.Nname != nil {
......
......@@ -10,6 +10,14 @@ import (
"testing"
)
func typeWithoutPointers() *Type {
return &Type{Etype: TSTRUCT, Extra: &StructType{Haspointers: 1}} // haspointers -> false
}
func typeWithPointers() *Type {
return &Type{Etype: TSTRUCT, Extra: &StructType{Haspointers: 2}} // haspointers -> true
}
// Test all code paths for cmpstackvarlt.
func TestCmpstackvar(t *testing.T) {
testdata := []struct {
......@@ -62,13 +70,13 @@ func TestCmpstackvar(t *testing.T) {
false,
},
{
Node{Class: PAUTO, Type: &Type{Haspointers: 1}}, // haspointers -> false
Node{Class: PAUTO, Type: &Type{Haspointers: 2}}, // haspointers -> true
Node{Class: PAUTO, Type: typeWithoutPointers()},
Node{Class: PAUTO, Type: typeWithPointers()},
false,
},
{
Node{Class: PAUTO, Type: &Type{Haspointers: 2}}, // haspointers -> true
Node{Class: PAUTO, Type: &Type{Haspointers: 1}}, // haspointers -> false
Node{Class: PAUTO, Type: typeWithPointers()},
Node{Class: PAUTO, Type: typeWithoutPointers()},
true,
},
{
......@@ -127,7 +135,7 @@ func TestStackvarSort(t *testing.T) {
{Class: PFUNC, Xoffset: 10, Type: &Type{}, Name: &Name{}, Sym: &Sym{}},
{Class: PFUNC, Xoffset: 20, Type: &Type{}, Name: &Name{}, Sym: &Sym{}},
{Class: PAUTO, Used: true, Type: &Type{}, Name: &Name{}, Sym: &Sym{}},
{Class: PAUTO, Type: &Type{Haspointers: 1}, Name: &Name{}, Sym: &Sym{}}, // haspointers -> false
{Class: PAUTO, Type: typeWithoutPointers(), Name: &Name{}, Sym: &Sym{}},
{Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{}},
{Class: PAUTO, Type: &Type{}, Name: &Name{Needzero: true}, Sym: &Sym{}},
{Class: PAUTO, Type: &Type{Width: 1}, Name: &Name{}, Sym: &Sym{}},
......@@ -148,7 +156,7 @@ func TestStackvarSort(t *testing.T) {
{Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{}},
{Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{Name: "abc"}},
{Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{Name: "xyz"}},
{Class: PAUTO, Type: &Type{Haspointers: 1}, Name: &Name{}, Sym: &Sym{}}, // haspointers -> false
{Class: PAUTO, Type: typeWithoutPointers(), Name: &Name{}, Sym: &Sym{}},
}
// haspointers updates Type.Haspointers as a side effect, so
// exercise this function on all inputs so that reflect.DeepEqual
......
......@@ -86,8 +86,8 @@ func makefield(name string, t *Type) *Field {
}
func mapbucket(t *Type) *Type {
if t.Bucket != nil {
return t.Bucket
if t.MapType().Bucket != nil {
return t.MapType().Bucket
}
bucket := typ(TSTRUCT)
......@@ -157,17 +157,17 @@ func mapbucket(t *Type) *Type {
Yyerror("bad math in mapbucket for %v", t)
}
t.Bucket = bucket
t.MapType().Bucket = bucket
bucket.Map = t
bucket.StructType().Map = t
return bucket
}
// Builds a type representing a Hmap structure for the given map type.
// Make sure this stays in sync with ../../../../runtime/hashmap.go!
func hmap(t *Type) *Type {
if t.Hmap != nil {
return t.Hmap
if t.MapType().Hmap != nil {
return t.MapType().Hmap
}
bucket := mapbucket(t)
......@@ -186,14 +186,14 @@ func hmap(t *Type) *Type {
h.Local = t.Local
h.SetFields(field[:])
dowidth(h)
t.Hmap = h
h.Map = t
t.MapType().Hmap = h
h.StructType().Map = t
return h
}
func hiter(t *Type) *Type {
if t.Hiter != nil {
return t.Hiter
if t.MapType().Hiter != nil {
return t.MapType().Hiter
}
// build a struct:
......@@ -234,8 +234,8 @@ func hiter(t *Type) *Type {
if i.Width != int64(12*Widthptr) {
Yyerror("hash_iter size not correct %d %d", i.Width, 12*Widthptr)
}
t.Hiter = i
i.Map = t
t.MapType().Hiter = i
i.StructType().Map = t
return i
}
......@@ -664,67 +664,47 @@ var kinds = []int{
}
func haspointers(t *Type) bool {
if t.Haspointers != 0 {
return t.Haspointers-1 != 0
}
var ret bool
switch t.Etype {
case TINT,
TUINT,
TINT8,
TUINT8,
TINT16,
TUINT16,
TINT32,
TUINT32,
TINT64,
TUINT64,
TUINTPTR,
TFLOAT32,
TFLOAT64,
TCOMPLEX64,
TCOMPLEX128,
TBOOL:
ret = false
case TINT, TUINT, TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64,
TUINT64, TUINTPTR, TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128, TBOOL:
return false
case TARRAY:
if t.IsSlice() {
ret = true
break
return true
}
if t.NumElem() == 0 { // empty array
ret = false
break
at := t.Extra.(*ArrayType)
if at.Haspointers != 0 {
return at.Haspointers-1 != 0
}
ret := false
if t.NumElem() != 0 { // non-empty array
ret = haspointers(t.Elem())
}
at.Haspointers = 1 + uint8(obj.Bool2int(ret))
return ret
case TSTRUCT:
ret = false
st := t.StructType()
if st.Haspointers != 0 {
return st.Haspointers-1 != 0
}
ret := false
for _, t1 := range t.Fields().Slice() {
if haspointers(t1.Type) {
ret = true
break
}
}
case TSTRING,
TPTR32,
TPTR64,
TUNSAFEPTR,
TINTER,
TCHAN,
TMAP,
TFUNC:
fallthrough
default:
ret = true
st.Haspointers = 1 + uint8(obj.Bool2int(ret))
return ret
}
t.Haspointers = 1 + uint8(obj.Bool2int(ret))
return ret
return true
}
// typeptrdata returns the length in bytes of the prefix of t
......
......@@ -27,7 +27,21 @@ func TestSizeof(t *testing.T) {
{Name{}, 52, 80},
{Node{}, 92, 144},
{Sym{}, 60, 112},
{Type{}, 116, 184},
{Type{}, 56, 88},
{MapType{}, 20, 40},
{ForwardType{}, 16, 32},
{FuncType{}, 28, 48},
{StructType{}, 12, 24},
{InterType{}, 4, 8},
{ChanType{}, 8, 16},
{ArrayType{}, 16, 24},
{InterMethType{}, 4, 8},
{DDDFieldType{}, 4, 8},
{FuncArgsType{}, 4, 8},
{ChanArgsType{}, 4, 8},
{PtrType{}, 4, 8},
{SliceType{}, 4, 8},
{DDDArrayType{}, 4, 8},
}
for _, tt := range tests {
......
......@@ -4218,7 +4218,7 @@ func (e *ssaExport) SplitInterface(name ssa.LocalSlot) (ssa.LocalSlot, ssa.Local
func (e *ssaExport) SplitSlice(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot, ssa.LocalSlot) {
n := name.N.(*Node)
ptrType := Ptrto(n.Type.Type)
ptrType := Ptrto(n.Type.Elem())
lenType := Types[TINT]
if n.Class == PAUTO && !n.Addrtaken {
// Split this slice up into three separate variables.
......
This diff is collapsed.
......@@ -2103,7 +2103,7 @@ OpSwitch:
return n
}
if Curfn.Type.Outnamed && n.List.Len() == 0 {
if Curfn.Type.FuncType().Outnamed && n.List.Len() == 0 {
break OpSwitch
}
typecheckaste(ORETURN, nil, false, Curfn.Type.Results(), n.List, func() string { return "return argument" })
......@@ -2161,12 +2161,8 @@ OpSwitch:
t := n.Type
if t != nil && !t.IsFuncArgStruct() && n.Op != OTYPE {
switch t.Etype {
case TFUNC, // might have TANY; wait until its called
TANY,
TFORW,
TIDEAL,
TNIL,
TBLANK:
case TFUNC, // might have TANY; wait until it's called
TANY, TFORW, TIDEAL, TNIL, TBLANK:
break
default:
......@@ -3522,13 +3518,13 @@ var mapqueue []*Node
func copytype(n *Node, t *Type) {
if t.Etype == TFORW {
// This type isn't computed yet; when it is, update n.
t.Copyto = append(t.Copyto, n)
t.ForwardType().Copyto = append(t.ForwardType().Copyto, n)
return
}
maplineno := n.Type.Maplineno
embedlineno := n.Type.Embedlineno
l := n.Type.Copyto
embedlineno := n.Type.ForwardType().Embedlineno
l := n.Type.ForwardType().Copyto
// TODO(mdempsky): Fix Type rekinding.
*n.Type = *t
......@@ -3544,7 +3540,6 @@ func copytype(n *Node, t *Type) {
t.Nod = nil
t.Printed = false
t.Deferwidth = false
t.Copyto = nil
// Update nodes waiting on this type.
for _, n := range l {
......
......@@ -359,16 +359,16 @@ func lexinit1() {
// t = interface { Error() string }
rcvr := typ(TSTRUCT)
rcvr.Funarg = true
rcvr.StructType().Funarg = true
field := newField()
field.Type = Ptrto(typ(TSTRUCT))
rcvr.SetFields([]*Field{field})
in := typ(TSTRUCT)
in.Funarg = true
in.StructType().Funarg = true
out := typ(TSTRUCT)
out.Funarg = true
out.StructType().Funarg = true
field = newField()
field.Type = Types[TSTRING]
out.SetFields([]*Field{field})
......
......@@ -287,7 +287,7 @@ func walkstmt(n *Node) *Node {
if n.List.Len() == 0 {
break
}
if (Curfn.Type.Outnamed && n.List.Len() > 1) || paramoutheap(Curfn) {
if (Curfn.Type.FuncType().Outnamed && n.List.Len() > 1) || paramoutheap(Curfn) {
// assign to the function out parameters,
// so that reorder3 can fix up conflicts
var rl []*Node
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment