// 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 ssagen import ( "internal/buildcfg" "internal/race" "math/rand" "sort" "sync" "time" "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/compile/internal/objw" "cmd/compile/internal/ssa" "cmd/compile/internal/types" "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/src" ) // 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 *ir.Name) bool { if needAlloc(a) != needAlloc(b) { return needAlloc(b) } if !needAlloc(a) { return a.FrameOffset() < b.FrameOffset() } if a.Used() != b.Used() { return a.Used() } ap := a.Type().HasPointers() bp := b.Type().HasPointers() if ap != bp { return ap } ap = a.Needzero() bp = b.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 []*ir.Name 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] } // needAlloc reports whether n is within the current frame, for which we need to // allocate space. In particular, it excludes arguments and results, which are in // the callers frame. func needAlloc(n *ir.Name) bool { if n.Op() != ir.ONAME { base.FatalfAt(n.Pos(), "%v has unexpected Op %v", n, n.Op()) } switch n.Class { case ir.PAUTO: return true case ir.PPARAM: return false case ir.PPARAMOUT: return n.IsOutputParamInRegisters() default: base.FatalfAt(n.Pos(), "%v has unexpected Class %v", n, n.Class) return false } } func (s *ssafn) AllocFrame(f *ssa.Func) { s.stksize = 0 s.stkptrsize = 0 fn := s.curfn // Mark the PAUTO's unused. for _, ln := range fn.Dcl { if needAlloc(ln) { ln.SetUsed(false) } } for _, l := range f.RegAlloc { if ls, ok := l.(ssa.LocalSlot); ok { ls.N.SetUsed(true) } } for _, b := range f.Blocks { for _, v := range b.Values { if n, ok := v.Aux.(*ir.Name); ok { switch n.Class { case ir.PPARAMOUT: if n.IsOutputParamInRegisters() && v.Op == ssa.OpVarDef { // ignore VarDef, look for "real" uses. // TODO: maybe do this for PAUTO as well? continue } fallthrough case ir.PPARAM, ir.PAUTO: n.SetUsed(true) } } } } // Use sort.Stable instead of sort.Sort so stack layout (and thus // compiler output) is less sensitive to frontend changes that // introduce or remove unused variables. sort.Stable(byStackVar(fn.Dcl)) // Reassign stack offsets of the locals that are used. lastHasPtr := false for i, n := range fn.Dcl { if n.Op() != ir.ONAME || n.Class != ir.PAUTO && !(n.Class == ir.PPARAMOUT && n.IsOutputParamInRegisters()) { // i.e., stack assign if AUTO, or if PARAMOUT in registers (which has no predefined spill locations) continue } if !n.Used() { fn.Dcl = fn.Dcl[:i] break } types.CalcSize(n.Type()) w := n.Type().Width if w >= types.MaxWidth || w < 0 { base.Fatalf("bad width") } if w == 0 && lastHasPtr { // Pad between a pointer-containing object and a zero-sized object. // This prevents a pointer to the zero-sized object from being interpreted // as a pointer to the pointer-containing object (and causing it // to be scanned when it shouldn't be). See issue 24993. w = 1 } s.stksize += w s.stksize = types.Rnd(s.stksize, int64(n.Type().Align)) if n.Type().HasPointers() { s.stkptrsize = s.stksize lastHasPtr = true } else { lastHasPtr = false } n.SetFrameOffset(-s.stksize) } s.stksize = types.Rnd(s.stksize, int64(types.RegSize)) s.stkptrsize = types.Rnd(s.stkptrsize, int64(types.RegSize)) } const maxStackSize = 1 << 30 // Compile builds an SSA backend function, // uses it to generate a plist, // and flushes that plist to machine code. // worker indicates which of the backend workers is doing the processing. func Compile(fn *ir.Func, worker int) { f := buildssa(fn, worker) // Note: check arg size to fix issue 25507. if f.Frontend().(*ssafn).stksize >= maxStackSize || f.OwnAux.ArgWidth() >= maxStackSize { largeStackFramesMu.Lock() largeStackFrames = append(largeStackFrames, largeStack{locals: f.Frontend().(*ssafn).stksize, args: f.OwnAux.ArgWidth(), pos: fn.Pos()}) largeStackFramesMu.Unlock() return } pp := objw.NewProgs(fn, worker) defer pp.Free() genssa(f, pp) // Check frame size again. // The check above included only the space needed for local variables. // After genssa, the space needed includes local variables and the callee arg region. // We must do this check prior to calling pp.Flush. // If there are any oversized stack frames, // the assembler may emit inscrutable complaints about invalid instructions. if pp.Text.To.Offset >= maxStackSize { largeStackFramesMu.Lock() locals := f.Frontend().(*ssafn).stksize largeStackFrames = append(largeStackFrames, largeStack{locals: locals, args: f.OwnAux.ArgWidth(), callee: pp.Text.To.Offset - locals, pos: fn.Pos()}) largeStackFramesMu.Unlock() return } pp.Flush() // assemble, fill in boilerplate, etc. // fieldtrack must be called after pp.Flush. See issue 20014. fieldtrack(pp.Text.From.Sym, fn.FieldTrack) } func init() { if race.Enabled { rand.Seed(time.Now().UnixNano()) } } // StackOffset returns the stack location of a LocalSlot relative to the // stack pointer, suitable for use in a DWARF location entry. This has nothing // to do with its offset in the user variable. func StackOffset(slot ssa.LocalSlot) int32 { n := slot.N var off int64 switch n.Class { case ir.PPARAM, ir.PPARAMOUT: if !n.IsOutputParamInRegisters() { off = n.FrameOffset() + base.Ctxt.FixedFrameSize() break } fallthrough // PPARAMOUT in registers allocates like an AUTO case ir.PAUTO: off = n.FrameOffset() if base.Ctxt.FixedFrameSize() == 0 { off -= int64(types.PtrSize) } if buildcfg.FramePointerEnabled { off -= int64(types.PtrSize) } } return int32(off + slot.Off) } // fieldtrack adds R_USEFIELD relocations to fnsym to record any // struct fields that it used. func fieldtrack(fnsym *obj.LSym, tracked map[*obj.LSym]struct{}) { if fnsym == nil { return } if !buildcfg.Experiment.FieldTrack || len(tracked) == 0 { return } trackSyms := make([]*obj.LSym, 0, len(tracked)) for sym := range tracked { trackSyms = append(trackSyms, sym) } sort.Slice(trackSyms, func(i, j int) bool { return trackSyms[i].Name < trackSyms[j].Name }) for _, sym := range trackSyms { r := obj.Addrel(fnsym) r.Sym = sym r.Type = objabi.R_USEFIELD } } // largeStack is info about a function whose stack frame is too large (rare). type largeStack struct { locals int64 args int64 callee int64 pos src.XPos } var ( largeStackFramesMu sync.Mutex // protects largeStackFrames largeStackFrames []largeStack ) func CheckLargeStacks() { // Check whether any of the functions we have compiled have gigantic stack frames. sort.Slice(largeStackFrames, func(i, j int) bool { return largeStackFrames[i].pos.Before(largeStackFrames[j].pos) }) for _, large := range largeStackFrames { if large.callee != 0 { base.ErrorfAt(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args + %d MB callee", large.locals>>20, large.args>>20, large.callee>>20) } else { base.ErrorfAt(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20) } } }