go/src/cmd/compile/internal/gc/pgen.go
Matthew Dempsky 8c24bff52b cmd/compile: layout stack frame during SSA
Identify live stack variables during SSA and compute the stack frame
layout earlier so that we can emit instructions with the correct
offsets upfront.

Passes toolstash/buildall.

Change-Id: I191100dba274f1e364a15bdcfdc1d1466cdd1db5
Reviewed-on: https://go-review.googlesource.com/30216
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
2016-10-04 17:07:36 +00:00

464 lines
12 KiB
Go

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gc
import (
"cmd/compile/internal/ssa"
"cmd/internal/obj"
"cmd/internal/sys"
"fmt"
"sort"
"strings"
)
// "Portable" code generation.
var makefuncdatasym_nsym int
func makefuncdatasym(nameprefix string, funcdatakind int64) *Sym {
sym := lookupN(nameprefix, makefuncdatasym_nsym)
makefuncdatasym_nsym++
pnod := newname(sym)
pnod.Class = PEXTERN
p := Gins(obj.AFUNCDATA, nil, pnod)
Addrconst(&p.From, funcdatakind)
return sym
}
// gvardef inserts a VARDEF for n into the instruction stream.
// VARDEF is an annotation for the liveness analysis, marking a place
// where a complete initialization (definition) of a variable begins.
// Since the liveness analysis can see initialization of single-word
// variables quite easy, gvardef is usually only called for multi-word
// or 'fat' variables, those satisfying isfat(n->type).
// However, gvardef is also called when a non-fat variable is initialized
// via a block move; the only time this happens is when you have
// return f()
// for a function with multiple return values exactly matching the return
// types of the current function.
//
// A 'VARDEF x' annotation in the instruction stream tells the liveness
// analysis to behave as though the variable x is being initialized at that
// point in the instruction stream. The VARDEF must appear before the
// actual (multi-instruction) initialization, and it must also appear after
// any uses of the previous value, if any. For example, if compiling:
//
// x = x[1:]
//
// it is important to generate code like:
//
// base, len, cap = pieces of x[1:]
// VARDEF x
// x = {base, len, cap}
//
// If instead the generated code looked like:
//
// VARDEF x
// base, len, cap = pieces of x[1:]
// x = {base, len, cap}
//
// then the liveness analysis would decide the previous value of x was
// unnecessary even though it is about to be used by the x[1:] computation.
// Similarly, if the generated code looked like:
//
// base, len, cap = pieces of x[1:]
// x = {base, len, cap}
// VARDEF x
//
// then the liveness analysis will not preserve the new value of x, because
// the VARDEF appears to have "overwritten" it.
//
// VARDEF is a bit of a kludge to work around the fact that the instruction
// stream is working on single-word values but the liveness analysis
// wants to work on individual variables, which might be multi-word
// aggregates. It might make sense at some point to look into letting
// the liveness analysis work on single-word values as well, although
// there are complications around interface values, slices, and strings,
// all of which cannot be treated as individual words.
//
// VARKILL is the opposite of VARDEF: it marks a value as no longer needed,
// even if its address has been taken. That is, a VARKILL annotation asserts
// that its argument is certainly dead, for use when the liveness analysis
// would not otherwise be able to deduce that fact.
func gvardefx(n *Node, as obj.As) {
if n == nil {
Fatalf("gvardef nil")
}
if n.Op != ONAME {
yyerror("gvardef %#v; %v", n.Op, n)
return
}
switch n.Class {
case PAUTO, PPARAM, PPARAMOUT:
if !n.Used {
Prog(obj.ANOP)
return
}
if as == obj.AVARLIVE {
Gins(as, n, nil)
} else {
Gins(as, nil, n)
}
}
}
func Gvardef(n *Node) {
gvardefx(n, obj.AVARDEF)
}
func Gvarkill(n *Node) {
gvardefx(n, obj.AVARKILL)
}
func Gvarlive(n *Node) {
gvardefx(n, obj.AVARLIVE)
}
func removevardef(firstp *obj.Prog) {
for p := firstp; p != nil; p = p.Link {
for p.Link != nil && (p.Link.As == obj.AVARDEF || p.Link.As == obj.AVARKILL || p.Link.As == obj.AVARLIVE) {
p.Link = p.Link.Link
}
if p.To.Type == obj.TYPE_BRANCH {
for p.To.Val.(*obj.Prog) != nil && (p.To.Val.(*obj.Prog).As == obj.AVARDEF || p.To.Val.(*obj.Prog).As == obj.AVARKILL || p.To.Val.(*obj.Prog).As == obj.AVARLIVE) {
p.To.Val = p.To.Val.(*obj.Prog).Link
}
}
}
}
func emitptrargsmap() {
if Curfn.Func.Nname.Sym.Name == "_" {
return
}
sym := lookup(fmt.Sprintf("%s.args_stackmap", Curfn.Func.Nname.Sym.Name))
nptr := int(Curfn.Type.ArgWidth() / int64(Widthptr))
bv := bvalloc(int32(nptr) * 2)
nbitmap := 1
if Curfn.Type.Results().NumFields() > 0 {
nbitmap = 2
}
off := duint32(sym, 0, uint32(nbitmap))
off = duint32(sym, off, uint32(bv.n))
var xoffset int64
if Curfn.IsMethod() {
xoffset = 0
onebitwalktype1(Curfn.Type.Recvs(), &xoffset, bv)
}
if Curfn.Type.Params().NumFields() > 0 {
xoffset = 0
onebitwalktype1(Curfn.Type.Params(), &xoffset, bv)
}
for j := 0; int32(j) < bv.n; j += 32 {
off = duint32(sym, off, bv.b[j/32])
}
if Curfn.Type.Results().NumFields() > 0 {
xoffset = 0
onebitwalktype1(Curfn.Type.Results(), &xoffset, bv)
for j := 0; int32(j) < bv.n; j += 32 {
off = duint32(sym, off, bv.b[j/32])
}
}
ggloblsym(sym, int32(off), obj.RODATA|obj.LOCAL)
}
// cmpstackvarlt reports whether the stack variable a sorts before b.
//
// Sort the list of stack variables. Autos after anything else,
// within autos, unused after used, within used, things with
// pointers first, zeroed things first, and then decreasing size.
// Because autos are laid out in decreasing addresses
// on the stack, pointers first, zeroed things first and decreasing size
// really means, in memory, things with pointers needing zeroing at
// the top of the stack and increasing in size.
// Non-autos sort on offset.
func cmpstackvarlt(a, b *Node) bool {
if (a.Class == PAUTO) != (b.Class == PAUTO) {
return b.Class == PAUTO
}
if a.Class != PAUTO {
return a.Xoffset < b.Xoffset
}
if a.Used != b.Used {
return a.Used
}
ap := haspointers(a.Type)
bp := haspointers(b.Type)
if ap != bp {
return ap
}
ap = a.Name.Needzero
bp = b.Name.Needzero
if ap != bp {
return ap
}
if a.Type.Width != b.Type.Width {
return a.Type.Width > b.Type.Width
}
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] }
var scratchFpMem *Node
func (s *ssaExport) AllocFrame(f *ssa.Func) {
Stksize = 0
stkptrsize = 0
// Mark the PAUTO's unused.
for _, ln := range Curfn.Func.Dcl {
if ln.Class == PAUTO {
ln.Used = false
}
}
for _, l := range f.RegAlloc {
if ls, ok := l.(ssa.LocalSlot); ok {
ls.N.(*Node).Used = true
}
}
scratchUsed := false
for _, b := range f.Blocks {
for _, v := range b.Values {
switch a := v.Aux.(type) {
case *ssa.ArgSymbol:
a.Node.(*Node).Used = true
case *ssa.AutoSymbol:
a.Node.(*Node).Used = true
}
// TODO(mdempsky): Encode in opcodeTable
// whether an Op requires scratch memory.
switch v.Op {
case ssa.Op386UCOMISS, ssa.Op386UCOMISD,
ssa.Op386ADDSS, ssa.Op386SUBSS, ssa.Op386MULSS, ssa.Op386DIVSS,
ssa.Op386CVTSD2SS, ssa.Op386CVTSL2SS, ssa.Op386CVTSL2SD, ssa.Op386CVTTSD2SL, ssa.Op386CVTTSS2SL,
ssa.OpPPC64Xf2i64, ssa.OpPPC64Xi2f64:
scratchUsed = true
}
}
}
// To satisfy toolstash -cmp, preserve the unsorted
// declaration order so we can emit the ATYPE instructions in
// the same order.
// TODO(mdempsky): Remove in followup CL.
Curfn.Func.UnsortedDcls = append([]*Node(nil), Curfn.Func.Dcl...)
if f.Config.NeedsFpScratch {
scratchFpMem = temp(Types[TUINT64])
scratchFpMem.Used = scratchUsed
}
sort.Sort(byStackVar(Curfn.Func.Dcl))
// Reassign stack offsets of the locals that are used.
for i, n := range Curfn.Func.Dcl {
if n.Op != ONAME || n.Class != PAUTO {
continue
}
if !n.Used {
Curfn.Func.Dcl = Curfn.Func.Dcl[:i]
break
}
dowidth(n.Type)
w := n.Type.Width
if w >= Thearch.MAXWIDTH || w < 0 {
Fatalf("bad width")
}
Stksize += w
Stksize = Rnd(Stksize, int64(n.Type.Align))
if haspointers(n.Type) {
stkptrsize = Stksize
}
if Thearch.LinkArch.InFamily(sys.MIPS64, sys.ARM, sys.ARM64, sys.PPC64, sys.S390X) {
Stksize = Rnd(Stksize, int64(Widthptr))
}
if Stksize >= 1<<31 {
setlineno(Curfn)
yyerror("stack frame too large (>2GB)")
}
n.Xoffset = -Stksize
}
Stksize = Rnd(Stksize, int64(Widthreg))
stkptrsize = Rnd(stkptrsize, int64(Widthreg))
}
func compile(fn *Node) {
if Newproc == nil {
Newproc = Sysfunc("newproc")
Deferproc = Sysfunc("deferproc")
Deferreturn = Sysfunc("deferreturn")
panicindex = Sysfunc("panicindex")
panicslice = Sysfunc("panicslice")
panicdivide = Sysfunc("panicdivide")
growslice = Sysfunc("growslice")
writebarrierptr = Sysfunc("writebarrierptr")
typedmemmove = Sysfunc("typedmemmove")
panicdottype = Sysfunc("panicdottype")
}
defer func(lno int32) {
lineno = lno
}(setlineno(fn))
Curfn = fn
dowidth(Curfn.Type)
if fn.Nbody.Len() == 0 {
if pure_go || strings.HasPrefix(fn.Func.Nname.Sym.Name, "init.") {
yyerror("missing function body for %q", fn.Func.Nname.Sym.Name)
return
}
if Debug['A'] != 0 {
return
}
emitptrargsmap()
return
}
saveerrors()
if Curfn.Type.FuncType().Outnamed {
// add clearing of the output parameters
for _, t := range Curfn.Type.Results().Fields().Slice() {
if t.Nname != nil {
n := nod(OAS, t.Nname, nil)
n = typecheck(n, Etop)
Curfn.Nbody.Prepend(n)
}
}
}
order(Curfn)
if nerrors != 0 {
return
}
hasdefer = false
walk(Curfn)
if nerrors != 0 {
return
}
if instrumenting {
instrument(Curfn)
}
if nerrors != 0 {
return
}
// Build an SSA backend function.
ssafn := buildssa(Curfn)
if nerrors != 0 {
return
}
newplist()
setlineno(Curfn)
nam := Curfn.Func.Nname
if isblank(nam) {
nam = nil
}
ptxt := Gins(obj.ATEXT, nam, nil)
ptxt.From3 = new(obj.Addr)
if fn.Func.Dupok {
ptxt.From3.Offset |= obj.DUPOK
}
if fn.Func.Wrapper {
ptxt.From3.Offset |= obj.WRAPPER
}
if fn.Func.Needctxt {
ptxt.From3.Offset |= obj.NEEDCTXT
}
if fn.Func.Pragma&Nosplit != 0 {
ptxt.From3.Offset |= obj.NOSPLIT
}
if fn.Func.ReflectMethod {
ptxt.From3.Offset |= obj.REFLECTMETHOD
}
if fn.Func.Pragma&Systemstack != 0 {
ptxt.From.Sym.Cfunc = true
}
// Clumsy but important.
// See test/recover.go for test cases and src/reflect/value.go
// for the actual functions being considered.
if myimportpath == "reflect" {
if Curfn.Func.Nname.Sym.Name == "callReflect" || Curfn.Func.Nname.Sym.Name == "callMethod" {
ptxt.From3.Offset |= obj.WRAPPER
}
}
gcargs := makefuncdatasym("gcargs·", obj.FUNCDATA_ArgsPointerMaps)
gclocals := makefuncdatasym("gclocals·", obj.FUNCDATA_LocalsPointerMaps)
if obj.Fieldtrack_enabled != 0 && len(Curfn.Func.FieldTrack) > 0 {
trackSyms := make([]*Sym, 0, len(Curfn.Func.FieldTrack))
for sym := range Curfn.Func.FieldTrack {
trackSyms = append(trackSyms, sym)
}
sort.Sort(symByName(trackSyms))
for _, sym := range trackSyms {
gtrack(sym)
}
}
for _, n := range fn.Func.UnsortedDcls {
if n.Op != ONAME { // might be OTYPE or OLITERAL
continue
}
switch n.Class {
case PAUTO:
if !n.Used {
// Hacks to appease toolstash -cmp.
// TODO(mdempsky): Remove in followup CL.
pcloc++
Pc.Pc++
Linksym(ngotype(n))
continue
}
fallthrough
case PPARAM, PPARAMOUT:
p := Gins(obj.ATYPE, n, nil)
p.From.Gotype = Linksym(ngotype(n))
}
}
genssa(ssafn, ptxt, gcargs, gclocals)
ssafn.Free()
}
type symByName []*Sym
func (a symByName) Len() int { return len(a) }
func (a symByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
func (a symByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }