cmd/compile: change Func.{Dcl,Inldcl} from NodeList to slice

A slice uses less memory than a NodeList, and has better memory locality
when walking the list.

This uncovered a tricky case involving closures: the escape analysis
pass when run on a closure was appending to the Dcl list of the OCLOSURE
rather than the ODCLFUNC.  This happened to work because they shared the
same NodeList.  Fixed with a change to addrescapes, and a check to
Tempname to catch any recurrences.

This removes the last use of the listsort function outside of tests.
I'll send a separate CL to remove it.

Unfortunately, while this passes all tests, it does not pass toolstash
-cmp.  The problem is that cmpstackvarlt does not fully determine the
sort order, and the change from listsort to sort.Sort, while generally
desirable, produces a different ordering.  I could stage this by first
making cmpstackvarlt fully determined, but no matter what toolstash -cmp
is going to break at some point.

In my casual testing the compiler is 2.2% faster.

Update #14473.

Change-Id: I367d66daa4ec73ed95c14c66ccda3a2133ad95d5
Reviewed-on: https://go-review.googlesource.com/19919
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/src/cmd/compile/internal/amd64/ggen.go b/src/cmd/compile/internal/amd64/ggen.go
index b9e5bfb1..55fb9e0 100644
--- a/src/cmd/compile/internal/amd64/ggen.go
+++ b/src/cmd/compile/internal/amd64/ggen.go
@@ -14,8 +14,6 @@
 var isPlan9 = obj.Getgoos() == "plan9"
 
 func defframe(ptxt *obj.Prog) {
-	var n *gc.Node
-
 	// fill in argument size, stack size
 	ptxt.To.Type = obj.TYPE_TEXTSIZE
 
@@ -34,8 +32,7 @@
 	x0 := uint32(0)
 
 	// iterate through declarations - they are sorted in decreasing xoffset order.
-	for l := gc.Curfn.Func.Dcl; l != nil; l = l.Next {
-		n = l.N
+	for _, n := range gc.Curfn.Func.Dcl {
 		if !n.Name.Needzero {
 			continue
 		}
diff --git a/src/cmd/compile/internal/arm/ggen.go b/src/cmd/compile/internal/arm/ggen.go
index 517b4f4..5e282c8 100644
--- a/src/cmd/compile/internal/arm/ggen.go
+++ b/src/cmd/compile/internal/arm/ggen.go
@@ -11,8 +11,6 @@
 )
 
 func defframe(ptxt *obj.Prog) {
-	var n *gc.Node
-
 	// fill in argument size, stack size
 	ptxt.To.Type = obj.TYPE_TEXTSIZE
 
@@ -28,8 +26,7 @@
 	hi := int64(0)
 	lo := hi
 	r0 := uint32(0)
-	for l := gc.Curfn.Func.Dcl; l != nil; l = l.Next {
-		n = l.N
+	for _, n := range gc.Curfn.Func.Dcl {
 		if !n.Name.Needzero {
 			continue
 		}
diff --git a/src/cmd/compile/internal/arm64/ggen.go b/src/cmd/compile/internal/arm64/ggen.go
index 99ffd5a..a33b2b4 100644
--- a/src/cmd/compile/internal/arm64/ggen.go
+++ b/src/cmd/compile/internal/arm64/ggen.go
@@ -12,8 +12,6 @@
 )
 
 func defframe(ptxt *obj.Prog) {
-	var n *gc.Node
-
 	// fill in argument size, stack size
 	ptxt.To.Type = obj.TYPE_TEXTSIZE
 
@@ -37,8 +35,7 @@
 	lo := hi
 
 	// iterate through declarations - they are sorted in decreasing xoffset order.
-	for l := gc.Curfn.Func.Dcl; l != nil; l = l.Next {
-		n = l.N
+	for _, n := range gc.Curfn.Func.Dcl {
 		if !n.Name.Needzero {
 			continue
 		}
diff --git a/src/cmd/compile/internal/gc/cgen.go b/src/cmd/compile/internal/gc/cgen.go
index 6456240..fdeb6e6 100644
--- a/src/cmd/compile/internal/gc/cgen.go
+++ b/src/cmd/compile/internal/gc/cgen.go
@@ -2263,9 +2263,9 @@
 	// If copying .args, that's all the results, so record definition sites
 	// for them for the liveness analysis.
 	if ns.Op == ONAME && ns.Sym.Name == ".args" {
-		for l := Curfn.Func.Dcl; l != nil; l = l.Next {
-			if l.N.Class == PPARAMOUT {
-				Gvardef(l.N)
+		for _, ln := range Curfn.Func.Dcl {
+			if ln.Class == PPARAMOUT {
+				Gvardef(ln)
 			}
 		}
 	}
diff --git a/src/cmd/compile/internal/gc/closure.go b/src/cmd/compile/internal/gc/closure.go
index df3e31a..df36db1 100644
--- a/src/cmd/compile/internal/gc/closure.go
+++ b/src/cmd/compile/internal/gc/closure.go
@@ -96,9 +96,9 @@
 		}
 	}
 
-	for l := func_.Func.Dcl; l != nil; l = l.Next {
-		if l.N.Op == ONAME && (l.N.Class == PPARAM || l.N.Class == PPARAMOUT) {
-			l.N.Name.Decldepth = 1
+	for _, ln := range func_.Func.Dcl {
+		if ln.Op == ONAME && (ln.Class == PPARAM || ln.Class == PPARAMOUT) {
+			ln.Name.Decldepth = 1
 		}
 	}
 
@@ -198,7 +198,8 @@
 	makefuncsym(xfunc.Func.Nname.Sym)
 
 	xfunc.Nbody = func_.Nbody
-	xfunc.Func.Dcl = concat(func_.Func.Dcl, xfunc.Func.Dcl)
+	xfunc.Func.Dcl = append(func_.Func.Dcl, xfunc.Func.Dcl...)
+	func_.Func.Dcl = nil
 	if xfunc.Nbody == nil {
 		Fatalf("empty body - won't generate any code")
 	}
@@ -341,13 +342,13 @@
 			fld.Sym = fld.Nname.Sym
 
 			// Declare the new param and add it the first part of the input arguments.
-			xfunc.Func.Dcl = list(xfunc.Func.Dcl, fld.Nname)
+			xfunc.Func.Dcl = append(xfunc.Func.Dcl, fld.Nname)
 
 			*param = fld
 			param = &fld.Down
 		}
 		*param = original_args
-		xfunc.Func.Dcl = concat(xfunc.Func.Dcl, original_dcl)
+		xfunc.Func.Dcl = append(xfunc.Func.Dcl, original_dcl...)
 
 		// Recalculate param offsets.
 		if f.Type.Width > 0 {
@@ -386,7 +387,7 @@
 				// If it is a small variable captured by value, downgrade it to PAUTO.
 				v.Class = PAUTO
 				v.Ullman = 1
-				xfunc.Func.Dcl = list(xfunc.Func.Dcl, v)
+				xfunc.Func.Dcl = append(xfunc.Func.Dcl, v)
 				body = list(body, Nod(OAS, v, cv))
 			} else {
 				// Declare variable holding addresses taken from closure
@@ -396,7 +397,7 @@
 				addr.Class = PAUTO
 				addr.Used = true
 				addr.Name.Curfn = xfunc
-				xfunc.Func.Dcl = list(xfunc.Func.Dcl, addr)
+				xfunc.Func.Dcl = append(xfunc.Func.Dcl, addr)
 				v.Name.Heapaddr = addr
 				if v.Name.Byval {
 					cv = Nod(OADDR, cv, nil)
@@ -551,7 +552,7 @@
 		n = newname(Lookupf("a%d", i))
 		i++
 		n.Class = PPARAM
-		xfunc.Func.Dcl = list(xfunc.Func.Dcl, n)
+		xfunc.Func.Dcl = append(xfunc.Func.Dcl, n)
 		callargs = list(callargs, n)
 		fld = Nod(ODCLFIELD, n, typenod(t.Type))
 		if t.Isddd {
@@ -570,7 +571,7 @@
 		n = newname(Lookupf("r%d", i))
 		i++
 		n.Class = PPARAMOUT
-		xfunc.Func.Dcl = list(xfunc.Func.Dcl, n)
+		xfunc.Func.Dcl = append(xfunc.Func.Dcl, n)
 		retargs = list(retargs, n)
 		l = list(l, Nod(ODCLFIELD, n, typenod(t.Type)))
 	}
@@ -600,7 +601,7 @@
 	ptr.Ullman = 1
 	ptr.Used = true
 	ptr.Name.Curfn = xfunc
-	xfunc.Func.Dcl = list(xfunc.Func.Dcl, ptr)
+	xfunc.Func.Dcl = append(xfunc.Func.Dcl, ptr)
 	var body *NodeList
 	if Isptr[rcvrtype.Etype] || Isinter(rcvrtype) {
 		ptr.Name.Param.Ntype = typenod(rcvrtype)
diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go
index 7e44a47..ccbb2d9 100644
--- a/src/cmd/compile/internal/gc/dcl.go
+++ b/src/cmd/compile/internal/gc/dcl.go
@@ -187,7 +187,7 @@
 			Fatalf("automatic outside function")
 		}
 		if Curfn != nil {
-			Curfn.Func.Dcl = list(Curfn.Func.Dcl, n)
+			Curfn.Func.Dcl = append(Curfn.Func.Dcl, n)
 		}
 		if n.Op == OTYPE {
 			declare_typegen++
diff --git a/src/cmd/compile/internal/gc/esc.go b/src/cmd/compile/internal/gc/esc.go
index 1a5a433..4cafc83 100644
--- a/src/cmd/compile/internal/gc/esc.go
+++ b/src/cmd/compile/internal/gc/esc.go
@@ -476,35 +476,35 @@
 	savefn := Curfn
 	Curfn = func_
 
-	for ll := Curfn.Func.Dcl; ll != nil; ll = ll.Next {
-		if ll.N.Op != ONAME {
+	for _, ln := range Curfn.Func.Dcl {
+		if ln.Op != ONAME {
 			continue
 		}
-		llNE := e.nodeEscState(ll.N)
-		switch ll.N.Class {
+		llNE := e.nodeEscState(ln)
+		switch ln.Class {
 		// out params are in a loopdepth between the sink and all local variables
 		case PPARAMOUT:
 			llNE.Escloopdepth = 0
 
 		case PPARAM:
 			llNE.Escloopdepth = 1
-			if ll.N.Type != nil && !haspointers(ll.N.Type) {
+			if ln.Type != nil && !haspointers(ln.Type) {
 				break
 			}
 			if Curfn.Nbody == nil && !Curfn.Noescape {
-				ll.N.Esc = EscHeap
+				ln.Esc = EscHeap
 			} else {
-				ll.N.Esc = EscNone // prime for escflood later
+				ln.Esc = EscNone // prime for escflood later
 			}
-			e.noesc = list(e.noesc, ll.N)
+			e.noesc = list(e.noesc, ln)
 		}
 	}
 
 	// in a mutually recursive group we lose track of the return values
 	if e.recursive {
-		for ll := Curfn.Func.Dcl; ll != nil; ll = ll.Next {
-			if ll.N.Op == ONAME && ll.N.Class == PPARAMOUT {
-				escflows(e, &e.theSink, ll.N)
+		for _, ln := range Curfn.Func.Dcl {
+			if ln.Op == ONAME && ln.Class == PPARAMOUT {
+				escflows(e, &e.theSink, ln)
 			}
 		}
 	}
@@ -779,11 +779,14 @@
 			ll = e.nodeEscState(n.List.N).Escretval
 		}
 
-		for lr := Curfn.Func.Dcl; lr != nil && ll != nil; lr = lr.Next {
-			if lr.N.Op != ONAME || lr.N.Class != PPARAMOUT {
+		for _, lrn := range Curfn.Func.Dcl {
+			if ll == nil {
+				break
+			}
+			if lrn.Op != ONAME || lrn.Class != PPARAMOUT {
 				continue
 			}
-			escassign(e, lr.N, ll.N)
+			escassign(e, lrn, ll.N)
 			ll = ll.Next
 		}
 
@@ -1870,16 +1873,16 @@
 	savefn := Curfn
 	Curfn = func_
 
-	for ll := Curfn.Func.Dcl; ll != nil; ll = ll.Next {
-		if ll.N.Op != ONAME {
+	for _, ln := range Curfn.Func.Dcl {
+		if ln.Op != ONAME {
 			continue
 		}
 
-		switch ll.N.Esc & EscMask {
+		switch ln.Esc & EscMask {
 		case EscNone, // not touched by escflood
 			EscReturn:
-			if haspointers(ll.N.Type) { // don't bother tagging for scalars
-				ll.N.Name.Param.Field.Note = mktag(int(ll.N.Esc))
+			if haspointers(ln.Type) { // don't bother tagging for scalars
+				ln.Name.Param.Field.Note = mktag(int(ln.Esc))
 			}
 
 		case EscHeap, // touched by escflood, moved to heap
diff --git a/src/cmd/compile/internal/gc/gen.go b/src/cmd/compile/internal/gc/gen.go
index b756055..adebfb8 100644
--- a/src/cmd/compile/internal/gc/gen.go
+++ b/src/cmd/compile/internal/gc/gen.go
@@ -76,6 +76,9 @@
 			oldfn := Curfn
 
 			Curfn = n.Name.Curfn
+			if Curfn.Func.Closure != nil && Curfn.Op == OCLOSURE {
+				Curfn = Curfn.Func.Closure
+			}
 			n.Name.Heapaddr = temp(Ptrto(n.Type))
 			buf := fmt.Sprintf("&%v", n.Sym)
 			n.Name.Heapaddr.Sym = Lookup(buf)
@@ -585,6 +588,10 @@
 	if Curfn == nil {
 		Fatalf("no curfn for tempname")
 	}
+	if Curfn.Func.Closure != nil && Curfn.Op == OCLOSURE {
+		Dump("Tempname", Curfn)
+		Fatalf("adding tempname to wrong closure function")
+	}
 
 	if t == nil {
 		Yyerror("tempname called with nil type")
@@ -604,7 +611,7 @@
 	n.Ullman = 1
 	n.Esc = EscNever
 	n.Name.Curfn = Curfn
-	Curfn.Func.Dcl = list(Curfn.Func.Dcl, n)
+	Curfn.Func.Dcl = append(Curfn.Func.Dcl, n)
 
 	dowidth(t)
 	n.Xoffset = 0
diff --git a/src/cmd/compile/internal/gc/gsubr.go b/src/cmd/compile/internal/gc/gsubr.go
index 04e9865..f5d7a8d 100644
--- a/src/cmd/compile/internal/gc/gsubr.go
+++ b/src/cmd/compile/internal/gc/gsubr.go
@@ -556,9 +556,7 @@
 	}
 
 	if fp == 1 {
-		var n *Node
-		for l := Curfn.Func.Dcl; l != nil; l = l.Next {
-			n = l.N
+		for _, n := range Curfn.Func.Dcl {
 			if (n.Class == PPARAM || n.Class == PPARAMOUT) && !isblanksym(t.Sym) && n.Sym == t.Sym {
 				return n
 			}
diff --git a/src/cmd/compile/internal/gc/inl.go b/src/cmd/compile/internal/gc/inl.go
index a445f71..cae15f9 100644
--- a/src/cmd/compile/internal/gc/inl.go
+++ b/src/cmd/compile/internal/gc/inl.go
@@ -150,7 +150,7 @@
 
 	fn.Func.Nname.Func.Inl = fn.Nbody
 	fn.Nbody = inlcopylist(fn.Func.Nname.Func.Inl)
-	fn.Func.Nname.Func.Inldcl = inlcopylist(fn.Func.Nname.Name.Defn.Func.Dcl)
+	fn.Func.Nname.Func.Inldcl = inlcopyslice(fn.Func.Nname.Name.Defn.Func.Dcl)
 	fn.Func.Nname.Func.InlCost = int32(maxBudget - budget)
 
 	// hack, TODO, check for better way to link method nodes back to the thing with the ->inl
@@ -275,6 +275,18 @@
 	return m
 }
 
+// Inlcopyslice is like inlcopylist, but for a slice.
+func inlcopyslice(ll []*Node) []*Node {
+	r := make([]*Node, 0, len(ll))
+	for _, ln := range ll {
+		c := inlcopy(ln)
+		if c != nil {
+			r = append(r, c)
+		}
+	}
+	return r
+}
+
 // Inlcalls/nodelist/node walks fn's statements and expressions and substitutes any
 // calls made to inlineable functions.  This is the external entry point.
 func inlcalls(fn *Node) {
@@ -556,7 +568,7 @@
 
 	//dumplist("ninit pre", ninit);
 
-	var dcl *NodeList
+	var dcl []*Node
 	if fn.Name.Defn != nil { // local function
 		dcl = fn.Func.Inldcl // imported function
 	} else {
@@ -567,18 +579,18 @@
 	i := 0
 
 	// Make temp names to use instead of the originals
-	for ll := dcl; ll != nil; ll = ll.Next {
-		if ll.N.Class == PPARAMOUT { // return values handled below.
+	for _, ln := range dcl {
+		if ln.Class == PPARAMOUT { // return values handled below.
 			continue
 		}
-		if ll.N.Op == ONAME {
-			ll.N.Name.Inlvar = inlvar(ll.N)
+		if ln.Op == ONAME {
+			ln.Name.Inlvar = inlvar(ln)
 
 			// Typecheck because inlvar is not necessarily a function parameter.
-			typecheck(&ll.N.Name.Inlvar, Erv)
+			typecheck(&ln.Name.Inlvar, Erv)
 
-			if ll.N.Class&^PHEAP != PAUTO {
-				ninit = list(ninit, Nod(ODCL, ll.N.Name.Inlvar, nil)) // otherwise gen won't emit the allocations for heapallocs
+			if ln.Class&^PHEAP != PAUTO {
+				ninit = list(ninit, Nod(ODCL, ln.Name.Inlvar, nil)) // otherwise gen won't emit the allocations for heapallocs
 			}
 		}
 	}
@@ -852,7 +864,7 @@
 		addrescapes(n)
 	}
 
-	Curfn.Func.Dcl = list(Curfn.Func.Dcl, n)
+	Curfn.Func.Dcl = append(Curfn.Func.Dcl, n)
 	return n
 }
 
@@ -863,7 +875,7 @@
 	n.Class = PAUTO
 	n.Used = true
 	n.Name.Curfn = Curfn // the calling function, not the called one
-	Curfn.Func.Dcl = list(Curfn.Func.Dcl, n)
+	Curfn.Func.Dcl = append(Curfn.Func.Dcl, n)
 	return n
 }
 
@@ -875,7 +887,7 @@
 	n.Class = PAUTO
 	n.Used = true
 	n.Name.Curfn = Curfn // the calling function, not the called one
-	Curfn.Func.Dcl = list(Curfn.Func.Dcl, n)
+	Curfn.Func.Dcl = append(Curfn.Func.Dcl, n)
 	return n
 }
 
diff --git a/src/cmd/compile/internal/gc/parser.go b/src/cmd/compile/internal/gc/parser.go
index ffa2071..88d4511 100644
--- a/src/cmd/compile/internal/gc/parser.go
+++ b/src/cmd/compile/internal/gc/parser.go
@@ -2564,15 +2564,15 @@
 		stmt := Nod(ORETURN, nil, nil)
 		stmt.List = results
 		if stmt.List == nil && Curfn != nil {
-			for l := Curfn.Func.Dcl; l != nil; l = l.Next {
-				if l.N.Class == PPARAM {
+			for _, ln := range Curfn.Func.Dcl {
+				if ln.Class == PPARAM {
 					continue
 				}
-				if l.N.Class != PPARAMOUT {
+				if ln.Class != PPARAMOUT {
 					break
 				}
-				if l.N.Sym.Def != l.N {
-					Yyerror("%s is shadowed during return", l.N.Sym.Name)
+				if ln.Sym.Def != ln {
+					Yyerror("%s is shadowed during return", ln.Sym.Name)
 				}
 			}
 		}
diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go
index 3471b97..a44cc73 100644
--- a/src/cmd/compile/internal/gc/pgen.go
+++ b/src/cmd/compile/internal/gc/pgen.go
@@ -8,6 +8,7 @@
 	"cmd/internal/obj"
 	"crypto/md5"
 	"fmt"
+	"sort"
 	"strings"
 )
 
@@ -217,6 +218,13 @@
 	return a.Sym.Name < b.Sym.Name
 }
 
+// byStackvar implements sort.Interface for []*Node using cmpstackvarlt.
+type byStackVar []*Node
+
+func (s byStackVar) Len() int           { return len(s) }
+func (s byStackVar) Less(i, j int) bool { return cmpstackvarlt(s[i], s[j]) }
+func (s byStackVar) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+
 // stkdelta records the stack offset delta for a node
 // during the compaction of the stack frame to remove
 // unused stack slots.
@@ -227,25 +235,23 @@
 	Stksize = 0
 	stkptrsize = 0
 
-	if Curfn.Func.Dcl == nil {
+	if len(Curfn.Func.Dcl) == 0 {
 		return
 	}
 
 	// Mark the PAUTO's unused.
-	for ll := Curfn.Func.Dcl; ll != nil; ll = ll.Next {
-		if ll.N.Class == PAUTO {
-			ll.N.Used = false
+	for _, ln := range Curfn.Func.Dcl {
+		if ln.Class == PAUTO {
+			ln.Used = false
 		}
 	}
 
 	markautoused(ptxt)
 
-	listsort(&Curfn.Func.Dcl, cmpstackvarlt)
+	sort.Sort(byStackVar(Curfn.Func.Dcl))
 
 	// Unused autos are at the end, chop 'em off.
-	ll := Curfn.Func.Dcl
-
-	n := ll.N
+	n := Curfn.Func.Dcl[0]
 	if n.Class == PAUTO && n.Op == ONAME && !n.Used {
 		// No locals used at all
 		Curfn.Func.Dcl = nil
@@ -254,19 +260,17 @@
 		return
 	}
 
-	for ll := Curfn.Func.Dcl; ll.Next != nil; ll = ll.Next {
-		n = ll.Next.N
+	for i := 1; i < len(Curfn.Func.Dcl); i++ {
+		n = Curfn.Func.Dcl[i]
 		if n.Class == PAUTO && n.Op == ONAME && !n.Used {
-			ll.Next = nil
-			Curfn.Func.Dcl.End = ll
+			Curfn.Func.Dcl = Curfn.Func.Dcl[:i]
 			break
 		}
 	}
 
 	// Reassign stack offsets of the locals that are still there.
 	var w int64
-	for ll := Curfn.Func.Dcl; ll != nil; ll = ll.Next {
-		n = ll.N
+	for _, n := range Curfn.Func.Dcl {
 		if n.Class != PAUTO || n.Op != ONAME {
 			continue
 		}
@@ -298,12 +302,12 @@
 	fixautoused(ptxt)
 
 	// The debug information needs accurate offsets on the symbols.
-	for ll := Curfn.Func.Dcl; ll != nil; ll = ll.Next {
-		if ll.N.Class != PAUTO || ll.N.Op != ONAME {
+	for _, ln := range Curfn.Func.Dcl {
+		if ln.Class != PAUTO || ln.Op != ONAME {
 			continue
 		}
-		ll.N.Xoffset += stkdelta[ll.N]
-		delete(stkdelta, ll.N)
+		ln.Xoffset += stkdelta[ln]
+		delete(stkdelta, ln)
 	}
 }
 
@@ -455,16 +459,15 @@
 		gtrack(tracksym(t))
 	}
 
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		n = l.N
+	for _, n := range fn.Func.Dcl {
 		if n.Op != ONAME { // might be OTYPE or OLITERAL
 			continue
 		}
 		switch n.Class {
 		case PAUTO, PPARAM, PPARAMOUT:
-			Nodconst(&nod1, Types[TUINTPTR], l.N.Type.Width)
-			p = Thearch.Gins(obj.ATYPE, l.N, &nod1)
-			p.From.Gotype = Linksym(ngotype(l.N))
+			Nodconst(&nod1, Types[TUINTPTR], n.Type.Width)
+			p = Thearch.Gins(obj.ATYPE, n, &nod1)
+			p.From.Gotype = Linksym(ngotype(n))
 		}
 	}
 
diff --git a/src/cmd/compile/internal/gc/plive.go b/src/cmd/compile/internal/gc/plive.go
index 8719029..84a24a8 100644
--- a/src/cmd/compile/internal/gc/plive.go
+++ b/src/cmd/compile/internal/gc/plive.go
@@ -197,8 +197,8 @@
 // variables.
 func getvariables(fn *Node) []*Node {
 	result := make([]*Node, 0, 0)
-	for ll := fn.Func.Dcl; ll != nil; ll = ll.Next {
-		if ll.N.Op == ONAME {
+	for _, ln := range fn.Func.Dcl {
+		if ln.Op == ONAME {
 			// In order for GODEBUG=gcdead=1 to work, each bitmap needs
 			// to contain information about all variables covered by the bitmap.
 			// For local variables, the bitmap only covers the stkptrsize
@@ -218,24 +218,24 @@
 			// Later, when we want to find the index of a node in the variables list,
 			// we will check that n->curfn == curfn and n->opt > 0. Then n->opt - 1
 			// is the index in the variables list.
-			ll.N.SetOpt(nil)
+			ln.SetOpt(nil)
 
 			// The compiler doesn't emit initializations for zero-width parameters or results.
-			if ll.N.Type.Width == 0 {
+			if ln.Type.Width == 0 {
 				continue
 			}
 
-			ll.N.Name.Curfn = Curfn
-			switch ll.N.Class {
+			ln.Name.Curfn = Curfn
+			switch ln.Class {
 			case PAUTO:
-				if haspointers(ll.N.Type) {
-					ll.N.SetOpt(int32(len(result)))
-					result = append(result, ll.N)
+				if haspointers(ln.Type) {
+					ln.SetOpt(int32(len(result)))
+					result = append(result, ln)
 				}
 
 			case PPARAM, PPARAMOUT:
-				ll.N.SetOpt(int32(len(result)))
-				result = append(result, ll.N)
+				ln.SetOpt(int32(len(result)))
+				result = append(result, ln)
 			}
 		}
 	}
@@ -795,8 +795,8 @@
 }
 
 func checkauto(fn *Node, p *obj.Prog, n *Node) {
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		if l.N.Op == ONAME && l.N.Class == PAUTO && l.N == n {
+	for _, ln := range fn.Func.Dcl {
+		if ln.Op == ONAME && ln.Class == PAUTO && ln == n {
 			return
 		}
 	}
@@ -807,8 +807,8 @@
 	}
 
 	fmt.Printf("checkauto %v: %v (%p; class=%d) not found in %p %v\n", funcSym(Curfn), n, n, n.Class, p, p)
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		fmt.Printf("\t%v (%p; class=%d)\n", l.N, l.N, l.N.Class)
+	for _, ln := range fn.Func.Dcl {
+		fmt.Printf("\t%v (%p; class=%d)\n", ln, ln, ln.Class)
 	}
 	Yyerror("checkauto: invariant lost")
 }
@@ -817,10 +817,8 @@
 	if isfunny(n) {
 		return
 	}
-	var a *Node
 	var class Class
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		a = l.N
+	for _, a := range fn.Func.Dcl {
 		class = a.Class &^ PHEAP
 		if a.Op == ONAME && (class == PPARAM || class == PPARAMOUT) && a == n {
 			return
@@ -828,8 +826,8 @@
 	}
 
 	fmt.Printf("checkparam %v: %v (%p; class=%d) not found in %v\n", Curfn, n, n, n.Class, p)
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		fmt.Printf("\t%v (%p; class=%d)\n", l.N, l.N, l.N.Class)
+	for _, ln := range fn.Func.Dcl {
+		fmt.Printf("\t%v (%p; class=%d)\n", ln, ln, ln.Class)
 	}
 	Yyerror("checkparam: invariant lost")
 }
@@ -1807,9 +1805,9 @@
 	onebitwritesymbol(lv.argslivepointers, argssym)
 
 	// Free everything.
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		if l.N != nil {
-			l.N.SetOpt(nil)
+	for _, ln := range fn.Func.Dcl {
+		if ln != nil {
+			ln.SetOpt(nil)
 		}
 	}
 	freeliveness(lv)
diff --git a/src/cmd/compile/internal/gc/popt.go b/src/cmd/compile/internal/gc/popt.go
index 6eb5c7b..0a2d8c4 100644
--- a/src/cmd/compile/internal/gc/popt.go
+++ b/src/cmd/compile/internal/gc/popt.go
@@ -589,8 +589,8 @@
 
 	// Build list of all mergeable variables.
 	var vars []*TempVar
-	for l := Curfn.Func.Dcl; l != nil; l = l.Next {
-		if n := l.N; canmerge(n) {
+	for _, n := range Curfn.Func.Dcl {
+		if canmerge(n) {
 			v := &TempVar{}
 			vars = append(vars, v)
 			n.SetOpt(v)
@@ -819,22 +819,15 @@
 	}
 
 	// Delete merged nodes from declaration list.
-	for lp := &Curfn.Func.Dcl; ; {
-		l := *lp
-		if l == nil {
-			break
-		}
-
-		Curfn.Func.Dcl.End = l
-		n := l.N
+	dcl := make([]*Node, 0, len(Curfn.Func.Dcl)-nkill)
+	for _, n := range Curfn.Func.Dcl {
 		v, _ := n.Opt().(*TempVar)
 		if v != nil && (v.merge != nil || v.removed) {
-			*lp = l.Next
 			continue
 		}
-
-		lp = &l.Next
+		dcl = append(dcl, n)
 	}
+	Curfn.Func.Dcl = dcl
 
 	// Clear aux structures.
 	for _, v := range vars {
diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go
index 5287626..a480971 100644
--- a/src/cmd/compile/internal/gc/subr.go
+++ b/src/cmd/compile/internal/gc/subr.go
@@ -1543,8 +1543,8 @@
 
 	if Curfn != nil {
 		fmt.Printf("--- %v frame ---\n", Curfn.Func.Nname.Sym)
-		for l := Curfn.Func.Dcl; l != nil; l = l.Next {
-			printframenode(l.N)
+		for _, ln := range Curfn.Func.Dcl {
+			printframenode(ln)
 		}
 	}
 }
diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go
index a11b37e..adf447d 100644
--- a/src/cmd/compile/internal/gc/syntax.go
+++ b/src/cmd/compile/internal/gc/syntax.go
@@ -152,8 +152,8 @@
 	Enter      *NodeList
 	Exit       *NodeList
 	Cvars      *NodeList // closure params
-	Dcl        *NodeList // autodcl for this func/closure
-	Inldcl     *NodeList // copy of dcl for use in inlining
+	Dcl        []*Node   // autodcl for this func/closure
+	Inldcl     []*Node   // copy of dcl for use in inlining
 	Closgen    int
 	Outerfunc  *Node
 	Fieldtrack []*Type
diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go
index 8fd6f85..395f04c 100644
--- a/src/cmd/compile/internal/gc/typecheck.go
+++ b/src/cmd/compile/internal/gc/typecheck.go
@@ -3433,9 +3433,9 @@
 		addmethod(n.Func.Shortname.Sym, t, true, n.Func.Nname.Nointerface)
 	}
 
-	for l := n.Func.Dcl; l != nil; l = l.Next {
-		if l.N.Op == ONAME && (l.N.Class == PPARAM || l.N.Class == PPARAMOUT) {
-			l.N.Name.Decldepth = 1
+	for _, ln := range n.Func.Dcl {
+		if ln.Op == ONAME && (ln.Class == PPARAM || ln.Class == PPARAMOUT) {
+			ln.Name.Decldepth = 1
 		}
 	}
 }
diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go
index f324d5e..acc923a 100644
--- a/src/cmd/compile/internal/gc/walk.go
+++ b/src/cmd/compile/internal/gc/walk.go
@@ -29,33 +29,34 @@
 
 	// Final typecheck for any unused variables.
 	// It's hard to be on the heap when not-used, but best to be consistent about &~PHEAP here and below.
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		if l.N.Op == ONAME && l.N.Class&^PHEAP == PAUTO {
-			typecheck(&l.N, Erv|Easgn)
+	for i, ln := range fn.Func.Dcl {
+		if ln.Op == ONAME && ln.Class&^PHEAP == PAUTO {
+			typecheck(&ln, Erv|Easgn)
+			fn.Func.Dcl[i] = ln
 		}
 	}
 
 	// Propagate the used flag for typeswitch variables up to the NONAME in it's definition.
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		if l.N.Op == ONAME && l.N.Class&^PHEAP == PAUTO && l.N.Name.Defn != nil && l.N.Name.Defn.Op == OTYPESW && l.N.Used {
-			l.N.Name.Defn.Left.Used = true
+	for _, ln := range fn.Func.Dcl {
+		if ln.Op == ONAME && ln.Class&^PHEAP == PAUTO && ln.Name.Defn != nil && ln.Name.Defn.Op == OTYPESW && ln.Used {
+			ln.Name.Defn.Left.Used = true
 		}
 	}
 
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		if l.N.Op != ONAME || l.N.Class&^PHEAP != PAUTO || l.N.Sym.Name[0] == '&' || l.N.Used {
+	for _, ln := range fn.Func.Dcl {
+		if ln.Op != ONAME || ln.Class&^PHEAP != PAUTO || ln.Sym.Name[0] == '&' || ln.Used {
 			continue
 		}
-		if defn := l.N.Name.Defn; defn != nil && defn.Op == OTYPESW {
+		if defn := ln.Name.Defn; defn != nil && defn.Op == OTYPESW {
 			if defn.Left.Used {
 				continue
 			}
 			lineno = defn.Left.Lineno
-			Yyerror("%v declared and not used", l.N.Sym)
+			Yyerror("%v declared and not used", ln.Sym)
 			defn.Left.Used = true // suppress repeats
 		} else {
-			lineno = l.N.Lineno
-			Yyerror("%v declared and not used", l.N.Sym)
+			lineno = ln.Lineno
+			Yyerror("%v declared and not used", ln.Sym)
 		}
 	}
 
@@ -92,11 +93,11 @@
 }
 
 func paramoutheap(fn *Node) bool {
-	for l := fn.Func.Dcl; l != nil; l = l.Next {
-		switch l.N.Class {
+	for _, ln := range fn.Func.Dcl {
+		switch ln.Class {
 		case PPARAMOUT,
 			PPARAMOUT | PHEAP:
-			return l.N.Addrtaken
+			return ln.Addrtaken
 
 			// stop early - parameters are over
 		case PAUTO,
@@ -290,13 +291,13 @@
 			var rl *NodeList
 
 			var cl Class
-			for ll := Curfn.Func.Dcl; ll != nil; ll = ll.Next {
-				cl = ll.N.Class &^ PHEAP
+			for _, ln := range Curfn.Func.Dcl {
+				cl = ln.Class &^ PHEAP
 				if cl == PAUTO {
 					break
 				}
 				if cl == PPARAMOUT {
-					rl = list(rl, ll.N)
+					rl = list(rl, ln)
 				}
 			}
 
diff --git a/src/cmd/compile/internal/mips64/ggen.go b/src/cmd/compile/internal/mips64/ggen.go
index 8c285a2..429eb35 100644
--- a/src/cmd/compile/internal/mips64/ggen.go
+++ b/src/cmd/compile/internal/mips64/ggen.go
@@ -12,8 +12,6 @@
 )
 
 func defframe(ptxt *obj.Prog) {
-	var n *gc.Node
-
 	// fill in argument size, stack size
 	ptxt.To.Type = obj.TYPE_TEXTSIZE
 
@@ -30,8 +28,7 @@
 	lo := hi
 
 	// iterate through declarations - they are sorted in decreasing xoffset order.
-	for l := gc.Curfn.Func.Dcl; l != nil; l = l.Next {
-		n = l.N
+	for _, n := range gc.Curfn.Func.Dcl {
 		if !n.Name.Needzero {
 			continue
 		}
diff --git a/src/cmd/compile/internal/ppc64/ggen.go b/src/cmd/compile/internal/ppc64/ggen.go
index 00fcdb8..5e50f9e 100644
--- a/src/cmd/compile/internal/ppc64/ggen.go
+++ b/src/cmd/compile/internal/ppc64/ggen.go
@@ -12,8 +12,6 @@
 )
 
 func defframe(ptxt *obj.Prog) {
-	var n *gc.Node
-
 	// fill in argument size, stack size
 	ptxt.To.Type = obj.TYPE_TEXTSIZE
 
@@ -30,8 +28,7 @@
 	lo := hi
 
 	// iterate through declarations - they are sorted in decreasing xoffset order.
-	for l := gc.Curfn.Func.Dcl; l != nil; l = l.Next {
-		n = l.N
+	for _, n := range gc.Curfn.Func.Dcl {
 		if !n.Name.Needzero {
 			continue
 		}
diff --git a/src/cmd/compile/internal/x86/ggen.go b/src/cmd/compile/internal/x86/ggen.go
index 139b199..480ae1c 100644
--- a/src/cmd/compile/internal/x86/ggen.go
+++ b/src/cmd/compile/internal/x86/ggen.go
@@ -11,8 +11,6 @@
 )
 
 func defframe(ptxt *obj.Prog) {
-	var n *gc.Node
-
 	// fill in argument size, stack size
 	ptxt.To.Type = obj.TYPE_TEXTSIZE
 
@@ -28,8 +26,7 @@
 	hi := int64(0)
 	lo := hi
 	ax := uint32(0)
-	for l := gc.Curfn.Func.Dcl; l != nil; l = l.Next {
-		n = l.N
+	for _, n := range gc.Curfn.Func.Dcl {
 		if !n.Name.Needzero {
 			continue
 		}