1
0
mirror of https://github.com/golang/go synced 2024-10-01 15:58:33 -06:00

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/21611
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Josh Bleecher Snyder 2016-04-01 20:11:30 -07:00
parent a25e368f44
commit f38f43d029
15 changed files with 491 additions and 226 deletions

View File

@ -198,11 +198,11 @@ func dowidth(t *Type) {
// make fake type to check later to // make fake type to check later to
// trigger channel argument check. // trigger channel argument check.
t1 := typWrapper(TCHANARGS, t) t1 := typChanArgs(t)
checkwidth(t1) checkwidth(t1)
case TCHANARGS: case TCHANARGS:
t1 := t.Wrapped() t1 := t.ChanArgs()
dowidth(t1) // just in case dowidth(t1) // just in case
if t1.Elem().Width >= 1<<16 { if t1.Elem().Width >= 1<<16 {
Yyerror("channel element type too large (>64kB)") Yyerror("channel element type too large (>64kB)")
@ -271,18 +271,18 @@ func dowidth(t *Type) {
// make fake type to check later to // make fake type to check later to
// trigger function argument computation. // trigger function argument computation.
case TFUNC: case TFUNC:
t1 := typWrapper(TFUNCARGS, t) t1 := typFuncArgs(t)
checkwidth(t1) checkwidth(t1)
w = int64(Widthptr) // width of func type is pointer w = int64(Widthptr) // width of func type is pointer
// function is 3 cated structures; // function is 3 cated structures;
// compute their widths as side-effect. // compute their widths as side-effect.
case TFUNCARGS: case TFUNCARGS:
t1 := t.Wrapped() t1 := t.FuncArgs()
w = widstruct(t1, t1.Recvs(), 0, 0) w = widstruct(t1, t1.Recvs(), 0, 0)
w = widstruct(t1, t1.Params(), w, Widthreg) w = widstruct(t1, t1.Params(), w, Widthreg)
w = widstruct(t1, t1.Results(), w, Widthreg) w = widstruct(t1, t1.Results(), w, Widthreg)
t1.Argwid = w t1.Extra.(*FuncType).Argwid = w
if w%int64(Widthreg) != 0 { if w%int64(Widthreg) != 0 {
Warn("bad type %v %d\n", t1, w) 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 { if int64(int(w)) != w {
Fatalf("argsize too big") Fatalf("argsize too big")
} }

View File

@ -602,7 +602,7 @@ func (p *exporter) typ(t *Type) {
case TDDDFIELD: case TDDDFIELD:
// see p.param use of TDDDFIELD // see p.param use of TDDDFIELD
p.tag(dddTag) p.tag(dddTag)
p.typ(t.Wrapped()) p.typ(t.DDDField())
case TSTRUCT: case TSTRUCT:
p.tag(structTag) p.tag(structTag)
@ -768,7 +768,7 @@ func (p *exporter) param(q *Field, n int, numbered bool) {
t := q.Type t := q.Type
if q.Isddd { if q.Isddd {
// create a fake type to encode ... just for the p.typ call // create a fake type to encode ... just for the p.typ call
t = typWrapper(TDDDFIELD, t.Elem()) t = typDDDField(t.Elem())
} }
p.typ(t) p.typ(t)
if n > 0 { if n > 0 {

View File

@ -359,16 +359,20 @@ func (p *importer) typ() *Type {
case arrayTag, sliceTag: case arrayTag, sliceTag:
t = p.newtyp(TARRAY) t = p.newtyp(TARRAY)
var bound int64
if i == arrayTag { if i == arrayTag {
t.SetNumElem(p.int64()) bound = p.int64()
} else { }
t.SetNumElem(sliceBound) elem := p.typ()
if i == arrayTag {
t.Extra = &ArrayType{Elem: elem, Bound: bound}
} else {
t.Extra = SliceType{Elem: elem}
} }
t.Type = p.typ()
case dddTag: case dddTag:
t = p.newtyp(TDDDFIELD) t = p.newtyp(TDDDFIELD)
t.Type = p.typ() t.Extra = DDDFieldType{T: p.typ()}
case structTag: case structTag:
t = p.newtyp(TSTRUCT) t = p.newtyp(TSTRUCT)
@ -376,7 +380,7 @@ func (p *importer) typ() *Type {
case pointerTag: case pointerTag:
t = p.newtyp(Tptr) t = p.newtyp(Tptr)
t.Type = p.typ() t.Extra = PtrType{Elem: p.typ()}
case signatureTag: case signatureTag:
t = p.newtyp(TFUNC) t = p.newtyp(TFUNC)
@ -393,13 +397,15 @@ func (p *importer) typ() *Type {
case mapTag: case mapTag:
t = p.newtyp(TMAP) t = p.newtyp(TMAP)
t.Down = p.typ() // key mt := t.MapType()
t.Type = p.typ() // val mt.Key = p.typ()
mt.Val = p.typ()
case chanTag: case chanTag:
t = p.newtyp(TCHAN) t = p.newtyp(TCHAN)
t.Chan = ChanDir(p.int()) ct := t.ChanType()
t.Type = p.typ() ct.Dir = ChanDir(p.int())
ct.Elem = p.typ()
default: default:
Fatalf("importer: unexpected type (tag = %d)", i) 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 // anonymous field - typ must be T or *T and T must be a type name
s := typ.Sym s := typ.Sym
if s == nil && typ.IsPtr() { if s == nil && typ.IsPtr() {
s = typ.Type.Sym // deref s = typ.Elem().Sym // deref
} }
pkg := importpkg pkg := importpkg
if sym != nil { if sym != nil {
@ -531,7 +537,7 @@ func (p *importer) param(named bool) *Node {
isddd := false isddd := false
if typ.Etype == TDDDFIELD { if typ.Etype == TDDDFIELD {
// TDDDFIELD indicates wrapped ... slice type // TDDDFIELD indicates wrapped ... slice type
typ = typSlice(typ.Wrapped()) typ = typSlice(typ.DDDField())
isddd = true isddd = true
} }

View File

@ -743,8 +743,8 @@ func checkembeddedtype(t *Type) {
if t.IsPtr() { if t.IsPtr() {
Yyerror("embedded type cannot be a pointer") Yyerror("embedded type cannot be a pointer")
} else if t.Etype == TFORW && t.Embedlineno == 0 { } else if t.Etype == TFORW && t.ForwardType().Embedlineno == 0 {
t.Embedlineno = lineno t.ForwardType().Embedlineno = lineno
} }
} }
@ -855,7 +855,7 @@ func tostruct0(t *Type, l []*Node) {
func tofunargs(l []*Node) *Type { func tofunargs(l []*Node) *Type {
t := typ(TSTRUCT) t := typ(TSTRUCT)
t.Funarg = true t.StructType().Funarg = true
fields := make([]*Field, len(l)) fields := make([]*Field, len(l))
for i, n := range l { for i, n := range l {
@ -1061,11 +1061,11 @@ func functype0(t *Type, this *Node, in, out []*Node) {
t.Broke = true t.Broke = true
} }
t.Outnamed = false t.FuncType().Outnamed = false
if len(out) > 0 && out[0].Left != nil && out[0].Left.Orig != nil { if len(out) > 0 && out[0].Left != nil && out[0].Left.Orig != nil {
s := out[0].Left.Orig.Sym 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 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
} }
} }
} }

View File

@ -592,7 +592,7 @@ func dumpasmhdr() {
case OTYPE: case OTYPE:
t := n.Type t := n.Type
if !t.IsStruct() || t.Map != nil || t.IsFuncArgStruct() { if !t.IsStruct() || t.StructType().Map != nil || t.IsFuncArgStruct() {
break break
} }
fmt.Fprintf(b, "#define %s__size %d\n", t.Sym.Name, int(t.Width)) fmt.Fprintf(b, "#define %s__size %d\n", t.Sym.Name, int(t.Width))

View File

@ -671,19 +671,20 @@ func typefmt(t *Type, flag FmtFlag) string {
return buf.String() return buf.String()
case TSTRUCT: 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. // Format the bucket struct for map[x]y as map.bucket[x]y.
// This avoids a recursive print that generates very long names. // This avoids a recursive print that generates very long names.
if t.Map.Bucket == t { if mt.Bucket == t {
return "map.bucket[" + t.Map.Key().String() + "]" + t.Map.Val().String() return "map.bucket[" + m.Key().String() + "]" + m.Val().String()
} }
if t.Map.Hmap == t { if mt.Hmap == t {
return "map.hdr[" + t.Map.Key().String() + "]" + t.Map.Val().String() return "map.hdr[" + m.Key().String() + "]" + m.Val().String()
} }
if t.Map.Hiter == t { if mt.Hiter == t {
return "map.iter[" + t.Map.Key().String() + "]" + t.Map.Val().String() return "map.iter[" + m.Key().String() + "]" + m.Val().String()
} }
Yyerror("unknown internal map type") Yyerror("unknown internal map type")
@ -735,7 +736,7 @@ func typefmt(t *Type, flag FmtFlag) string {
if fmtmode == FExp { if fmtmode == FExp {
Fatalf("cannot use TDDDFIELD with old exporter") 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 { if fmtmode == FExp {

View File

@ -375,7 +375,7 @@ func compile(fn *Node) {
// set up domain for labels // set up domain for labels
clearlabels() clearlabels()
if Curfn.Type.Outnamed { if Curfn.Type.FuncType().Outnamed {
// add clearing of the output parameters // add clearing of the output parameters
for _, t := range Curfn.Type.Results().Fields().Slice() { for _, t := range Curfn.Type.Results().Fields().Slice() {
if t.Nname != nil { if t.Nname != nil {

View File

@ -10,6 +10,14 @@ import (
"testing" "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. // Test all code paths for cmpstackvarlt.
func TestCmpstackvar(t *testing.T) { func TestCmpstackvar(t *testing.T) {
testdata := []struct { testdata := []struct {
@ -62,13 +70,13 @@ func TestCmpstackvar(t *testing.T) {
false, false,
}, },
{ {
Node{Class: PAUTO, Type: &Type{Haspointers: 1}}, // haspointers -> false Node{Class: PAUTO, Type: typeWithoutPointers()},
Node{Class: PAUTO, Type: &Type{Haspointers: 2}}, // haspointers -> true Node{Class: PAUTO, Type: typeWithPointers()},
false, false,
}, },
{ {
Node{Class: PAUTO, Type: &Type{Haspointers: 2}}, // haspointers -> true Node{Class: PAUTO, Type: typeWithPointers()},
Node{Class: PAUTO, Type: &Type{Haspointers: 1}}, // haspointers -> false Node{Class: PAUTO, Type: typeWithoutPointers()},
true, true,
}, },
{ {
@ -127,7 +135,7 @@ func TestStackvarSort(t *testing.T) {
{Class: PFUNC, Xoffset: 10, Type: &Type{}, Name: &Name{}, Sym: &Sym{}}, {Class: PFUNC, Xoffset: 10, Type: &Type{}, Name: &Name{}, Sym: &Sym{}},
{Class: PFUNC, Xoffset: 20, 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, 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{}, Sym: &Sym{}},
{Class: PAUTO, Type: &Type{}, Name: &Name{Needzero: true}, Sym: &Sym{}}, {Class: PAUTO, Type: &Type{}, Name: &Name{Needzero: true}, Sym: &Sym{}},
{Class: PAUTO, Type: &Type{Width: 1}, Name: &Name{}, 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{}},
{Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{Name: "abc"}}, {Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{Name: "abc"}},
{Class: PAUTO, Type: &Type{}, Name: &Name{}, Sym: &Sym{Name: "xyz"}}, {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 // haspointers updates Type.Haspointers as a side effect, so
// exercise this function on all inputs so that reflect.DeepEqual // exercise this function on all inputs so that reflect.DeepEqual

View File

@ -86,8 +86,8 @@ func makefield(name string, t *Type) *Field {
} }
func mapbucket(t *Type) *Type { func mapbucket(t *Type) *Type {
if t.Bucket != nil { if t.MapType().Bucket != nil {
return t.Bucket return t.MapType().Bucket
} }
bucket := typ(TSTRUCT) bucket := typ(TSTRUCT)
@ -157,17 +157,17 @@ func mapbucket(t *Type) *Type {
Yyerror("bad math in mapbucket for %v", t) Yyerror("bad math in mapbucket for %v", t)
} }
t.Bucket = bucket t.MapType().Bucket = bucket
bucket.Map = t bucket.StructType().Map = t
return bucket return bucket
} }
// Builds a type representing a Hmap structure for the given map type. // Builds a type representing a Hmap structure for the given map type.
// Make sure this stays in sync with ../../../../runtime/hashmap.go! // Make sure this stays in sync with ../../../../runtime/hashmap.go!
func hmap(t *Type) *Type { func hmap(t *Type) *Type {
if t.Hmap != nil { if t.MapType().Hmap != nil {
return t.Hmap return t.MapType().Hmap
} }
bucket := mapbucket(t) bucket := mapbucket(t)
@ -186,14 +186,14 @@ func hmap(t *Type) *Type {
h.Local = t.Local h.Local = t.Local
h.SetFields(field[:]) h.SetFields(field[:])
dowidth(h) dowidth(h)
t.Hmap = h t.MapType().Hmap = h
h.Map = t h.StructType().Map = t
return h return h
} }
func hiter(t *Type) *Type { func hiter(t *Type) *Type {
if t.Hiter != nil { if t.MapType().Hiter != nil {
return t.Hiter return t.MapType().Hiter
} }
// build a struct: // build a struct:
@ -234,8 +234,8 @@ func hiter(t *Type) *Type {
if i.Width != int64(12*Widthptr) { if i.Width != int64(12*Widthptr) {
Yyerror("hash_iter size not correct %d %d", i.Width, 12*Widthptr) Yyerror("hash_iter size not correct %d %d", i.Width, 12*Widthptr)
} }
t.Hiter = i t.MapType().Hiter = i
i.Map = t i.StructType().Map = t
return i return i
} }
@ -664,67 +664,47 @@ var kinds = []int{
} }
func haspointers(t *Type) bool { func haspointers(t *Type) bool {
if t.Haspointers != 0 {
return t.Haspointers-1 != 0
}
var ret bool
switch t.Etype { switch t.Etype {
case TINT, case TINT, TUINT, TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64,
TUINT, TUINT64, TUINTPTR, TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128, TBOOL:
TINT8, return false
TUINT8,
TINT16,
TUINT16,
TINT32,
TUINT32,
TINT64,
TUINT64,
TUINTPTR,
TFLOAT32,
TFLOAT64,
TCOMPLEX64,
TCOMPLEX128,
TBOOL:
ret = false
case TARRAY: case TARRAY:
if t.IsSlice() { if t.IsSlice() {
ret = true return true
break
} }
if t.NumElem() == 0 { // empty array at := t.Extra.(*ArrayType)
ret = false if at.Haspointers != 0 {
break return at.Haspointers-1 != 0
} }
ret := false
if t.NumElem() != 0 { // non-empty array
ret = haspointers(t.Elem()) ret = haspointers(t.Elem())
}
at.Haspointers = 1 + uint8(obj.Bool2int(ret))
return ret
case TSTRUCT: case TSTRUCT:
ret = false st := t.StructType()
if st.Haspointers != 0 {
return st.Haspointers-1 != 0
}
ret := false
for _, t1 := range t.Fields().Slice() { for _, t1 := range t.Fields().Slice() {
if haspointers(t1.Type) { if haspointers(t1.Type) {
ret = true ret = true
break break
} }
} }
st.Haspointers = 1 + uint8(obj.Bool2int(ret))
case TSTRING, return ret
TPTR32,
TPTR64,
TUNSAFEPTR,
TINTER,
TCHAN,
TMAP,
TFUNC:
fallthrough
default:
ret = true
} }
t.Haspointers = 1 + uint8(obj.Bool2int(ret)) return true
return ret
} }
// typeptrdata returns the length in bytes of the prefix of t // typeptrdata returns the length in bytes of the prefix of t

View File

@ -27,7 +27,21 @@ func TestSizeof(t *testing.T) {
{Name{}, 52, 80}, {Name{}, 52, 80},
{Node{}, 92, 144}, {Node{}, 92, 144},
{Sym{}, 60, 112}, {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 { for _, tt := range tests {

View File

@ -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) { func (e *ssaExport) SplitSlice(name ssa.LocalSlot) (ssa.LocalSlot, ssa.LocalSlot, ssa.LocalSlot) {
n := name.N.(*Node) n := name.N.(*Node)
ptrType := Ptrto(n.Type.Type) ptrType := Ptrto(n.Type.Elem())
lenType := Types[TINT] lenType := Types[TINT]
if n.Class == PAUTO && !n.Addrtaken { if n.Class == PAUTO && !n.Addrtaken {
// Split this slice up into three separate variables. // Split this slice up into three separate variables.

View File

@ -122,55 +122,174 @@ var (
// A Type represents a Go type. // A Type represents a Go type.
type Type struct { type Type struct {
Etype EType // Extra contains extra etype-specific fields.
Noalg bool // As an optimization, those etype-specific structs which contain exactly
Chan ChanDir // one pointer-shaped field are stored as values rather than pointers when possible.
Trecur uint8 // to detect loops //
Printed bool // TMAP: *MapType
Funarg bool // TSTRUCT only: whether this struct represents function parameters // TFORW: *ForwardType
Local bool // created in this file // TFUNC: *FuncType
Deferwidth bool // TINTERMETHOD: InterMethType
Broke bool // broken type definition. // TSTRUCT: *StructType
Align uint8 // TINTER: *InterType
Haspointers uint8 // 0 unknown, 1 no, 2 yes // TDDDFIELD: DDDFieldType
Outnamed bool // on TFUNC // TFUNCARGS: FuncArgsType
// TCHANARGS: ChanArgsType
// TCHAN: *ChanType
// TPTR32, TPTR64: PtrType
// TARRAY: *ArrayType, SliceType, or DDDArrayType
Extra interface{}
Nod *Node // canonical OTYPE node // Width is the width of this Type in bytes.
Orig *Type // original type (type literal or predefined type) Width int64
methods Fields methods Fields
allMethods Fields allMethods Fields
Sym *Sym Nod *Node // canonical OTYPE node
Vargen int32 // unique name for OTYPE/ONAME Orig *Type // original type (type literal or predefined type)
Lineno int32
nname *Node Sym *Sym // symbol containing name, for named types
Vargen int32 // unique name for OTYPE/ONAME
Lineno int32 // line at which this type was declared, implicitly or explicitly
Maplineno int32 // first use of this type as a map key
Etype EType // kind of type
Noalg bool // suppress hash and eq algorithm generation
Trecur uint8 // to detect loops
Printed bool // prevent duplicate export printing
Local bool // created in this file
Deferwidth bool
Broke bool // broken type definition.
Align uint8 // the required alignment of this type, in bytes
}
// MapType contains Type fields specific to maps.
type MapType struct {
Key *Type // Key type
Val *Type // Val (elem) type
Bucket *Type // internal struct type representing a hash bucket
Hmap *Type // internal struct type representing the Hmap (map header object)
Hiter *Type // internal struct type representing hash iterator state
}
// MapType returns t's extra map-specific fields.
func (t *Type) MapType() *MapType {
t.wantEtype(TMAP)
return t.Extra.(*MapType)
}
// ForwardType contains Type fields specific to forward types.
type ForwardType struct {
Copyto []*Node // where to copy the eventual value to
Embedlineno int32 // first use of this type as an embedded type
}
// ForwardType returns t's extra forward-type-specific fields.
func (t *Type) ForwardType() *ForwardType {
t.wantEtype(TFORW)
return t.Extra.(*ForwardType)
}
// FuncType contains Type fields specific to func types.
type FuncType struct {
Receiver *Type // function receiver
Results *Type // function results
Params *Type // function params
Nname *Node
// Argwid is the total width of the function receiver, params, and results.
// It gets calculated via a temporary TFUNCARGS type.
// Note that TFUNC's Width is Widthptr.
Argwid int64 Argwid int64
// most nodes Outnamed bool
Type *Type // element type for TARRAY, TCHAN, TMAP, TPTRxx }
Width int64
// TSTRUCT // FuncType returns t's extra func-specific fields.
func (t *Type) FuncType() *FuncType {
t.wantEtype(TFUNC)
return t.Extra.(*FuncType)
}
// InterMethType contains Type fields specific to interface method psuedo-types.
type InterMethType struct {
Nname *Node
}
// StructType contains Type fields specific to struct types.
type StructType struct {
fields Fields fields Fields
Down *Type // key type in TMAP; next struct in Funarg TSTRUCT // Maps have three associated internal structs (see struct MapType).
// Map links such structs back to their map type.
Map *Type
// TARRAY Funarg bool // whether this struct represents function parameters
Bound int64 // negative is slice Haspointers uint8 // 0 unknown, 1 no, 2 yes
}
// TMAP // StructType returns t's extra struct-specific fields.
Bucket *Type // internal type representing a hash bucket func (t *Type) StructType() *StructType {
Hmap *Type // internal type representing a Hmap (map header object) t.wantEtype(TSTRUCT)
Hiter *Type // internal type representing hash iterator state return t.Extra.(*StructType)
Map *Type // link from the above 3 internal types back to the map type. }
Maplineno int32 // first use of TFORW as map key // InterType contains Type fields specific to interface types.
Embedlineno int32 // first use of TFORW as embedded type type InterType struct {
fields Fields
}
// for TFORW, where to copy the eventual value to // PtrType contains Type fields specific to pointer types.
Copyto []*Node type PtrType struct {
Elem *Type // element type
}
// DDDFieldType contains Type fields specific to TDDDFIELD types.
type DDDFieldType struct {
T *Type // reference to a slice type for ... args
}
// ChanArgsType contains Type fields specific to TCHANARGS types.
type ChanArgsType struct {
T *Type // reference to a chan type whose elements need a width check
}
// // FuncArgsType contains Type fields specific to TFUNCARGS types.
type FuncArgsType struct {
T *Type // reference to a func type whose elements need a width check
}
// ChanType contains Type fields specific to channel types.
type ChanType struct {
Elem *Type // element type
Dir ChanDir // channel direction
}
// ChanType returns t's extra channel-specific fields.
func (t *Type) ChanType() *ChanType {
t.wantEtype(TCHAN)
return t.Extra.(*ChanType)
}
// ArrayType contains Type fields specific to array types with known lengths.
type ArrayType struct {
Elem *Type // element type
Bound int64 // number of elements; always >= 0; do not use with sliceBound or dddBound
Haspointers uint8 // 0 unknown, 1 no, 2 yes
}
// SliceType contains Type fields specific to slice types.
type SliceType struct {
Elem *Type // element type
}
// DDDArrayType contains Type fields specific to ddd array types.
type DDDArrayType struct {
Elem *Type // element type
} }
// A Field represents a field in a struct or a method in an interface or // A Field represents a field in a struct or a method in an interface or
@ -252,38 +371,61 @@ func typ(et EType) *Type {
Lineno: lineno, Lineno: lineno,
} }
t.Orig = t t.Orig = t
// TODO(josharian): lazily initialize some of these?
switch t.Etype {
case TMAP:
t.Extra = new(MapType)
case TFORW:
t.Extra = new(ForwardType)
case TFUNC:
t.Extra = new(FuncType)
case TINTERMETH:
t.Extra = InterMethType{}
case TSTRUCT:
t.Extra = new(StructType)
case TINTER:
t.Extra = new(InterType)
case TPTR32, TPTR64:
t.Extra = PtrType{}
case TCHANARGS:
t.Extra = ChanArgsType{}
case TFUNCARGS:
t.Extra = FuncArgsType{}
case TDDDFIELD:
t.Extra = DDDFieldType{}
case TCHAN:
t.Extra = new(ChanType)
}
return t return t
} }
// typArray returns a new fixed-length array Type. // typArray returns a new fixed-length array Type.
func typArray(elem *Type, bound int64) *Type { func typArray(elem *Type, bound int64) *Type {
t := typ(TARRAY) t := typ(TARRAY)
t.Type = elem t.Extra = &ArrayType{Elem: elem, Bound: bound}
t.Bound = bound
return t return t
} }
// typSlice returns a new slice Type. // typSlice returns a new slice Type.
func typSlice(elem *Type) *Type { func typSlice(elem *Type) *Type {
t := typ(TARRAY) t := typ(TARRAY)
t.Type = elem t.Extra = SliceType{Elem: elem}
t.Bound = sliceBound
return t return t
} }
// typDDDArray returns a new [...]T array Type. // typDDDArray returns a new [...]T array Type.
func typDDDArray(elem *Type) *Type { func typDDDArray(elem *Type) *Type {
t := typ(TARRAY) t := typ(TARRAY)
t.Type = elem t.Extra = DDDArrayType{Elem: elem}
t.Bound = dddBound
return t return t
} }
// typChan returns a new chan Type with direction dir. // typChan returns a new chan Type with direction dir.
func typChan(elem *Type, dir ChanDir) *Type { func typChan(elem *Type, dir ChanDir) *Type {
t := typ(TCHAN) t := typ(TCHAN)
t.Type = elem ct := t.ChanType()
t.Chan = dir ct.Elem = elem
ct.Dir = dir
return t return t
} }
@ -294,29 +436,39 @@ func typMap(k, v *Type) *Type {
} }
t := typ(TMAP) t := typ(TMAP)
t.Down = k mt := t.MapType()
t.Type = v mt.Key = k
mt.Val = v
return t return t
} }
// typPtr returns a new pointer type pointing to t. // typPtr returns a new pointer type pointing to t.
func typPtr(elem *Type) *Type { func typPtr(elem *Type) *Type {
t := typ(Tptr) t := typ(Tptr)
t.Type = elem t.Extra = PtrType{Elem: elem}
t.Width = int64(Widthptr) t.Width = int64(Widthptr)
t.Align = uint8(Widthptr) t.Align = uint8(Widthptr)
return t return t
} }
// typWrapper returns a new wrapper psuedo-type. // typDDDField returns a new TDDDFIELD type for slice type s.
func typWrapper(et EType, wrapped *Type) *Type { func typDDDField(s *Type) *Type {
switch et { t := typ(TDDDFIELD)
case TCHANARGS, TFUNCARGS, TDDDFIELD: t.Extra = DDDFieldType{T: s}
default: return t
Fatalf("typWrapper bad etype %s", et) }
}
t := typ(et) // typChanArgs returns a new TCHANARGS type for channel type c.
t.Type = wrapped func typChanArgs(c *Type) *Type {
t := typ(TCHANARGS)
t.Extra = ChanArgsType{T: c}
return t
}
// typFuncArgs returns a new TFUNCARGS type for func type f.
func typFuncArgs(f *Type) *Type {
t := typ(TFUNCARGS)
t.Extra = FuncArgsType{T: f}
return t return t
} }
@ -362,20 +514,43 @@ func substAny(t *Type, types *[]*Type) *Type {
t = (*types)[0] t = (*types)[0]
*types = (*types)[1:] *types = (*types)[1:]
case TPTR32, TPTR64, TCHAN, TARRAY: case TPTR32, TPTR64:
elem := substAny(t.Type, types) elem := substAny(t.Elem(), types)
if elem != t.Type { if elem != t.Elem() {
t = t.Copy() t = t.Copy()
t.Type = elem t.Extra = PtrType{Elem: elem}
}
case TARRAY:
elem := substAny(t.Elem(), types)
if elem != t.Elem() {
t = t.Copy()
switch x := t.Extra.(type) {
case *ArrayType:
x.Elem = elem
case SliceType:
t.Extra = SliceType{Elem: elem}
case DDDArrayType:
t.Extra = DDDArrayType{Elem: elem}
default:
Fatalf("substAny bad array elem type %T %v", x, t)
}
}
case TCHAN:
elem := substAny(t.Elem(), types)
if elem != t.Elem() {
t = t.Copy()
t.Extra.(*ChanType).Elem = elem
} }
case TMAP: case TMAP:
key := substAny(t.Down, types) key := substAny(t.Key(), types)
val := substAny(t.Type, types) val := substAny(t.Val(), types)
if key != t.Down || val != t.Type { if key != t.Key() || val != t.Val() {
t = t.Copy() t = t.Copy()
t.Down = key t.Extra.(*MapType).Key = key
t.Type = val t.Extra.(*MapType).Val = val
} }
case TFUNC: case TFUNC:
@ -426,6 +601,32 @@ func (t *Type) Copy() *Type {
return nil return nil
} }
nt := *t nt := *t
// copy any *T Extra fields, to avoid aliasing
switch t.Etype {
case TMAP:
x := *t.Extra.(*MapType)
nt.Extra = &x
case TFORW:
x := *t.Extra.(*ForwardType)
nt.Extra = &x
case TFUNC:
x := *t.Extra.(*FuncType)
nt.Extra = &x
case TSTRUCT:
x := *t.Extra.(*StructType)
nt.Extra = &x
case TINTER:
x := *t.Extra.(*InterType)
nt.Extra = &x
case TCHAN:
x := *t.Extra.(*ChanType)
nt.Extra = &x
case TARRAY:
if arr, ok := t.Extra.(*ArrayType); ok {
x := *arr
nt.Extra = &x
}
}
// TODO(mdempsky): Find out why this is necessary and explain. // TODO(mdempsky): Find out why this is necessary and explain.
if t.Orig == t { if t.Orig == t {
nt.Orig = &nt nt.Orig = &nt
@ -483,17 +684,17 @@ func (t *Type) wantEtype2(et1, et2 EType) {
func (t *Type) RecvsP() **Type { func (t *Type) RecvsP() **Type {
t.wantEtype(TFUNC) t.wantEtype(TFUNC)
return &t.Type return &t.Extra.(*FuncType).Receiver
} }
func (t *Type) ParamsP() **Type { func (t *Type) ParamsP() **Type {
t.wantEtype(TFUNC) t.wantEtype(TFUNC)
return &t.Type.Down.Down return &t.Extra.(*FuncType).Params
} }
func (t *Type) ResultsP() **Type { func (t *Type) ResultsP() **Type {
t.wantEtype(TFUNC) t.wantEtype(TFUNC)
return &t.Type.Down return &t.Extra.(*FuncType).Results
} }
func (t *Type) Recvs() *Type { return *t.RecvsP() } func (t *Type) Recvs() *Type { return *t.RecvsP() }
@ -524,51 +725,82 @@ var paramsResults = [2]func(*Type) *Type{
// Key returns the key type of map type t. // Key returns the key type of map type t.
func (t *Type) Key() *Type { func (t *Type) Key() *Type {
t.wantEtype(TMAP) t.wantEtype(TMAP)
return t.Down return t.Extra.(*MapType).Key
} }
// Val returns the value type of map type t. // Val returns the value type of map type t.
func (t *Type) Val() *Type { func (t *Type) Val() *Type {
t.wantEtype(TMAP) t.wantEtype(TMAP)
return t.Type return t.Extra.(*MapType).Val
} }
// Elem returns the type of elements of t. // Elem returns the type of elements of t.
// Usable with pointers, channels, arrays, and slices. // Usable with pointers, channels, arrays, and slices.
func (t *Type) Elem() *Type { func (t *Type) Elem() *Type {
switch t.Etype { switch t.Etype {
case TPTR32, TPTR64, TCHAN, TARRAY: case TPTR32, TPTR64:
default: return t.Extra.(PtrType).Elem
Fatalf("Type.Elem %s", t.Etype) case TARRAY:
switch t := t.Extra.(type) {
case *ArrayType:
return t.Elem
case SliceType:
return t.Elem
case DDDArrayType:
return t.Elem
} }
return t.Type case TCHAN:
return t.Extra.(*ChanType).Elem
}
Fatalf("Type.Elem %s", t.Etype)
return nil
} }
// Wrapped returns the type that pseudo-type t wraps. // DDDField returns the slice ... type for TDDDFIELD type t.
func (t *Type) Wrapped() *Type { func (t *Type) DDDField() *Type {
switch t.Etype { t.wantEtype(TDDDFIELD)
case TCHANARGS, TFUNCARGS, TDDDFIELD: return t.Extra.(DDDFieldType).T
default: }
Fatalf("Type.Wrapped %s", t.Etype)
} // ChanArgs returns the channel type for TCHANARGS type t.
return t.Type func (t *Type) ChanArgs() *Type {
t.wantEtype(TCHANARGS)
return t.Extra.(ChanArgsType).T
}
// FuncArgs returns the channel type for TFUNCARGS type t.
func (t *Type) FuncArgs() *Type {
t.wantEtype(TFUNCARGS)
return t.Extra.(FuncArgsType).T
} }
// Nname returns the associated function's nname. // Nname returns the associated function's nname.
func (t *Type) Nname() *Node { func (t *Type) Nname() *Node {
t.wantEtype2(TFUNC, TINTERMETH) switch t.Etype {
return t.nname case TFUNC:
return t.Extra.(*FuncType).Nname
case TINTERMETH:
return t.Extra.(InterMethType).Nname
}
Fatalf("Type.Nname %v %v", t.Etype, t)
return nil
} }
// Nname sets the associated function's nname. // Nname sets the associated function's nname.
func (t *Type) SetNname(n *Node) { func (t *Type) SetNname(n *Node) {
t.wantEtype2(TFUNC, TINTERMETH) switch t.Etype {
t.nname = n case TFUNC:
t.Extra.(*FuncType).Nname = n
case TINTERMETH:
t.Extra = InterMethType{Nname: n}
default:
Fatalf("Type.SetNname %v %v", t.Etype, t)
}
} }
// IsFuncArgStruct reports whether t is a struct representing function parameters. // IsFuncArgStruct reports whether t is a struct representing function parameters.
func (t *Type) IsFuncArgStruct() bool { func (t *Type) IsFuncArgStruct() bool {
return t.Etype == TSTRUCT && t.Funarg return t.Etype == TSTRUCT && t.Extra.(*StructType).Funarg
} }
func (t *Type) Methods() *Fields { func (t *Type) Methods() *Fields {
@ -582,10 +814,14 @@ func (t *Type) AllMethods() *Fields {
} }
func (t *Type) Fields() *Fields { func (t *Type) Fields() *Fields {
if t.Etype != TSTRUCT && t.Etype != TINTER { switch t.Etype {
Fatalf("Fields: type %v does not have fields", t) case TSTRUCT:
return &t.Extra.(*StructType).fields
case TINTER:
return &t.Extra.(*InterType).fields
} }
return &t.fields Fatalf("Fields: type %v does not have fields", t)
return nil
} }
// Field returns the i'th field/method of struct/interface type t. // Field returns the i'th field/method of struct/interface type t.
@ -608,15 +844,15 @@ func (t *Type) isDDDArray() bool {
if t.Etype != TARRAY { if t.Etype != TARRAY {
return false return false
} }
t.checkBound() _, ok := t.Extra.(DDDArrayType)
return t.Bound == dddBound return ok
} }
// ArgWidth returns the total aligned argument size for a function. // ArgWidth returns the total aligned argument size for a function.
// It includes the receiver, parameters, and results. // It includes the receiver, parameters, and results.
func (t *Type) ArgWidth() int64 { func (t *Type) ArgWidth() int64 {
t.wantEtype(TFUNC) t.wantEtype(TFUNC)
return t.Argwid return t.Extra.(*FuncType).Argwid
} }
func (t *Type) Size() int64 { func (t *Type) Size() int64 {
@ -764,20 +1000,20 @@ func (t *Type) cmp(x *Type) ssa.Cmp {
// by the general code after the switch. // by the general code after the switch.
case TSTRUCT: case TSTRUCT:
if t.Map == nil { if t.StructType().Map == nil {
if x.Map != nil { if x.StructType().Map != nil {
return ssa.CMPlt // nil < non-nil return ssa.CMPlt // nil < non-nil
} }
// to the fallthrough // to the fallthrough
} else if x.Map == nil { } else if x.StructType().Map == nil {
return ssa.CMPgt // nil > non-nil return ssa.CMPgt // nil > non-nil
} else if t.Map.Bucket == t { } else if t.StructType().Map.MapType().Bucket == t {
// Both have non-nil Map // Both have non-nil Map
// Special case for Maps which include a recursive type where the recursion is not broken with a named type // Special case for Maps which include a recursive type where the recursion is not broken with a named type
if x.Map.Bucket != x { if x.StructType().Map.MapType().Bucket != x {
return ssa.CMPlt // bucket maps are least return ssa.CMPlt // bucket maps are least
} }
return t.Map.cmp(x.Map) return t.StructType().Map.cmp(x.StructType().Map)
} // If t != t.Map.Bucket, fall through to general case } // If t != t.Map.Bucket, fall through to general case
fallthrough fallthrough
@ -910,21 +1146,22 @@ func (t *Type) IsChan() bool {
return t.Etype == TCHAN return t.Etype == TCHAN
} }
// checkBound enforces that Bound has an acceptable value. // TODO: Remove noinline when issue 15084 is resolved.
func (t *Type) checkBound() { //go:noinline
if t.Bound != sliceBound && t.Bound < 0 && t.Bound != dddBound {
Fatalf("bad TARRAY bounds %d %s", t.Bound, t)
}
}
func (t *Type) IsSlice() bool { func (t *Type) IsSlice() bool {
t.checkBound() if t.Etype != TARRAY {
return t.Etype == TARRAY && t.Bound == sliceBound return false
}
_, ok := t.Extra.(SliceType)
return ok
} }
func (t *Type) IsArray() bool { func (t *Type) IsArray() bool {
t.checkBound() if t.Etype != TARRAY {
return t.Etype == TARRAY && t.Bound >= 0 return false
}
_, ok := t.Extra.(*ArrayType)
return ok
} }
func (t *Type) IsStruct() bool { func (t *Type) IsStruct() bool {
@ -961,24 +1198,48 @@ func (t *Type) FieldOff(i int) int64 {
func (t *Type) NumElem() int64 { func (t *Type) NumElem() int64 {
t.wantEtype(TARRAY) t.wantEtype(TARRAY)
t.checkBound() switch t := t.Extra.(type) {
case *ArrayType:
return t.Bound return t.Bound
case SliceType:
return sliceBound
case DDDArrayType:
return dddBound
}
Fatalf("NumElem on non-array %T %v", t.Extra, t)
return 0
} }
// SetNumElem sets the number of elements in an array type. // SetNumElem sets the number of elements in an array type.
// It should not be used if at all possible. // It should not be used if at all possible.
// Create a new array/slice/dddArray with typX instead. // Create a new array/slice/dddArray with typX instead.
// TODO(josharian): figure out how to get rid of this. // The only allowed uses are:
// * array -> slice as a hack to suppress extra error output
// * ddd array -> array
// TODO(josharian): figure out how to get rid of this entirely.
func (t *Type) SetNumElem(n int64) { func (t *Type) SetNumElem(n int64) {
t.wantEtype(TARRAY) t.wantEtype(TARRAY)
t.Bound = n switch {
case n >= 0:
if !t.isDDDArray() {
Fatalf("SetNumElem non-ddd -> array %v", t)
}
t.Extra = &ArrayType{Elem: t.Elem(), Bound: n}
case n == sliceBound:
if !t.IsArray() {
Fatalf("SetNumElem non-array -> slice %v", t)
}
t.Extra = SliceType{Elem: t.Elem()}
default:
Fatalf("SetNumElem %d %v", n, t)
}
} }
// ChanDir returns the direction of a channel type t. // ChanDir returns the direction of a channel type t.
// The direction will be one of Crecv, Csend, or Cboth. // The direction will be one of Crecv, Csend, or Cboth.
func (t *Type) ChanDir() ChanDir { func (t *Type) ChanDir() ChanDir {
t.wantEtype(TCHAN) t.wantEtype(TCHAN)
return t.Chan return t.Extra.(*ChanType).Dir
} }
func (t *Type) IsMemory() bool { return false } func (t *Type) IsMemory() bool { return false }

View File

@ -2103,7 +2103,7 @@ OpSwitch:
return n return n
} }
if Curfn.Type.Outnamed && n.List.Len() == 0 { if Curfn.Type.FuncType().Outnamed && n.List.Len() == 0 {
break OpSwitch break OpSwitch
} }
typecheckaste(ORETURN, nil, false, Curfn.Type.Results(), n.List, func() string { return "return argument" }) typecheckaste(ORETURN, nil, false, Curfn.Type.Results(), n.List, func() string { return "return argument" })
@ -2161,12 +2161,8 @@ OpSwitch:
t := n.Type t := n.Type
if t != nil && !t.IsFuncArgStruct() && n.Op != OTYPE { if t != nil && !t.IsFuncArgStruct() && n.Op != OTYPE {
switch t.Etype { switch t.Etype {
case TFUNC, // might have TANY; wait until its called case TFUNC, // might have TANY; wait until it's called
TANY, TANY, TFORW, TIDEAL, TNIL, TBLANK:
TFORW,
TIDEAL,
TNIL,
TBLANK:
break break
default: default:
@ -3522,13 +3518,13 @@ var mapqueue []*Node
func copytype(n *Node, t *Type) { func copytype(n *Node, t *Type) {
if t.Etype == TFORW { if t.Etype == TFORW {
// This type isn't computed yet; when it is, update n. // 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 return
} }
maplineno := n.Type.Maplineno maplineno := n.Type.Maplineno
embedlineno := n.Type.Embedlineno embedlineno := n.Type.ForwardType().Embedlineno
l := n.Type.Copyto l := n.Type.ForwardType().Copyto
// TODO(mdempsky): Fix Type rekinding. // TODO(mdempsky): Fix Type rekinding.
*n.Type = *t *n.Type = *t
@ -3544,7 +3540,6 @@ func copytype(n *Node, t *Type) {
t.Nod = nil t.Nod = nil
t.Printed = false t.Printed = false
t.Deferwidth = false t.Deferwidth = false
t.Copyto = nil
// Update nodes waiting on this type. // Update nodes waiting on this type.
for _, n := range l { for _, n := range l {

View File

@ -359,16 +359,16 @@ func lexinit1() {
// t = interface { Error() string } // t = interface { Error() string }
rcvr := typ(TSTRUCT) rcvr := typ(TSTRUCT)
rcvr.Funarg = true rcvr.StructType().Funarg = true
field := newField() field := newField()
field.Type = Ptrto(typ(TSTRUCT)) field.Type = Ptrto(typ(TSTRUCT))
rcvr.SetFields([]*Field{field}) rcvr.SetFields([]*Field{field})
in := typ(TSTRUCT) in := typ(TSTRUCT)
in.Funarg = true in.StructType().Funarg = true
out := typ(TSTRUCT) out := typ(TSTRUCT)
out.Funarg = true out.StructType().Funarg = true
field = newField() field = newField()
field.Type = Types[TSTRING] field.Type = Types[TSTRING]
out.SetFields([]*Field{field}) out.SetFields([]*Field{field})

View File

@ -287,7 +287,7 @@ func walkstmt(n *Node) *Node {
if n.List.Len() == 0 { if n.List.Len() == 0 {
break 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, // assign to the function out parameters,
// so that reorder3 can fix up conflicts // so that reorder3 can fix up conflicts
var rl []*Node var rl []*Node