diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:25:22 +0000 |
commit | f6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch) | |
tree | 7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/cmd/compile/internal/ssagen/pgen.go | |
parent | Initial commit. (diff) | |
download | golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip |
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/compile/internal/ssagen/pgen.go')
-rw-r--r-- | src/cmd/compile/internal/ssagen/pgen.go | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/ssagen/pgen.go b/src/cmd/compile/internal/ssagen/pgen.go new file mode 100644 index 0000000..e7a0699 --- /dev/null +++ b/src/cmd/compile/internal/ssagen/pgen.go @@ -0,0 +1,364 @@ +// 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 ( + "fmt" + "internal/buildcfg" + "os" + "sort" + "sync" + + "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. +func cmpstackvarlt(a, b *ir.Name) bool { + // Sort non-autos before autos. + if needAlloc(a) != needAlloc(b) { + return needAlloc(b) + } + + // If both are non-auto (e.g., parameters, results), then sort by + // frame offset (defined by ABI). + if !needAlloc(a) { + return a.FrameOffset() < b.FrameOffset() + } + + // From here on, a and b are both autos (i.e., local variables). + + // Sort used before unused (so AllocFrame can truncate unused + // variables). + if a.Used() != b.Used() { + return a.Used() + } + + // Sort pointer-typed before non-pointer types. + // Keeps the stack's GC bitmap compact. + ap := a.Type().HasPointers() + bp := b.Type().HasPointers() + if ap != bp { + return ap + } + + // Group variables that need zeroing, so we can efficiently zero + // them altogether. + ap = a.Needzero() + bp = b.Needzero() + if ap != bp { + return ap + } + + // Sort variables in descending alignment order, so we can optimally + // pack variables into the frame. + if a.Type().Alignment() != b.Type().Alignment() { + return a.Type().Alignment() > b.Type().Alignment() + } + + // Sort normal variables before open-coded-defer slots, so that the + // latter are grouped together and near the top of the frame (to + // minimize varint encoding of their varp offset). + if a.OpenDeferSlot() != b.OpenDeferSlot() { + return a.OpenDeferSlot() + } + + // If a and b are both open-coded defer slots, then order them by + // index in descending order, so they'll be laid out in the frame in + // ascending order. + // + // Their index was saved in FrameOffset in state.openDeferSave. + if a.OpenDeferSlot() { + return a.FrameOffset() > b.FrameOffset() + } + + // Tie breaker for stable results. + 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 + s.stkalign = int64(types.RegSize) + fn := s.curfn + + // Mark the PAUTO's unused. + for _, ln := range fn.Dcl { + if ln.OpenDeferSlot() { + // Open-coded defer slots have indices that were assigned + // upfront during SSA construction, but the defer statement can + // later get removed during deadcode elimination (#61895). To + // keep their relative offsets correct, treat them all as used. + continue + } + + 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.DebugInfo.(*ssa.FuncDebug).OptDcl = fn.Dcl[i:] + fn.Dcl = fn.Dcl[:i] + break + } + + types.CalcSize(n.Type()) + w := n.Type().Size() + 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.RoundUp(s.stksize, n.Type().Alignment()) + if n.Type().Alignment() > int64(types.RegSize) { + s.stkalign = n.Type().Alignment() + } + if n.Type().HasPointers() { + s.stkptrsize = s.stksize + lastHasPtr = true + } else { + lastHasPtr = false + } + n.SetFrameOffset(-s.stksize) + } + + s.stksize = types.RoundUp(s.stksize, s.stkalign) + s.stkptrsize = types.RoundUp(s.stkptrsize, s.stkalign) +} + +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. + + // If we're compiling the package init function, search for any + // relocations that target global map init outline functions and + // turn them into weak relocs. + if fn.IsPackageInit() && base.Debug.WrapGlobalMapCtl != 1 { + weakenGlobalMapInitRelocs(fn) + } + + // fieldtrack must be called after pp.Flush. See issue 20014. + fieldtrack(pp.Text.From.Sym, fn.FieldTrack) +} + +// globalMapInitLsyms records the LSym of each map.init.NNN outlined +// map initializer function created by the compiler. +var globalMapInitLsyms map[*obj.LSym]struct{} + +// RegisterMapInitLsym records "s" in the set of outlined map initializer +// functions. +func RegisterMapInitLsym(s *obj.LSym) { + if globalMapInitLsyms == nil { + globalMapInitLsyms = make(map[*obj.LSym]struct{}) + } + globalMapInitLsyms[s] = struct{}{} +} + +// weakenGlobalMapInitRelocs walks through all of the relocations on a +// given a package init function "fn" and looks for relocs that target +// outlined global map initializer functions; if it finds any such +// relocs, it flags them as R_WEAK. +func weakenGlobalMapInitRelocs(fn *ir.Func) { + if globalMapInitLsyms == nil { + return + } + for i := range fn.LSym.R { + tgt := fn.LSym.R[i].Sym + if tgt == nil { + continue + } + if _, ok := globalMapInitLsyms[tgt]; !ok { + continue + } + if base.Debug.WrapGlobalMapDbg > 1 { + fmt.Fprintf(os.Stderr, "=-= weakify fn %v reloc %d %+v\n", fn, i, + fn.LSym.R[i]) + } + // set the R_WEAK bit, leave rest of reloc type intact + fn.LSym.R[i].Type |= objabi.R_WEAK + } +} + +// 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.Arch.FixedFrameSize + break + } + fallthrough // PPARAMOUT in registers allocates like an AUTO + case ir.PAUTO: + off = n.FrameOffset() + if base.Ctxt.Arch.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, 0, "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, 0, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20) + } + } +} |