Source file src/cmd/compile/internal/ssagen/pgen.go

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssagen
     6  
     7  import (
     8  	"fmt"
     9  	"internal/buildcfg"
    10  	"os"
    11  	"slices"
    12  	"sort"
    13  	"strings"
    14  	"sync"
    15  
    16  	"cmd/compile/internal/base"
    17  	"cmd/compile/internal/inline"
    18  	"cmd/compile/internal/ir"
    19  	"cmd/compile/internal/liveness"
    20  	"cmd/compile/internal/objw"
    21  	"cmd/compile/internal/pgoir"
    22  	"cmd/compile/internal/ssa"
    23  	"cmd/compile/internal/ssa/ssadebug"
    24  	"cmd/compile/internal/ssa/ssaop"
    25  	"cmd/compile/internal/types"
    26  	"cmd/internal/obj"
    27  	"cmd/internal/objabi"
    28  	"cmd/internal/src"
    29  )
    30  
    31  // cmpstackvarlt reports whether the stack variable a sorts before b.
    32  func cmpstackvarlt(a, b *ir.Name, mls *liveness.MergeLocalsState) bool {
    33  	// Sort non-autos before autos.
    34  	if needAlloc(a) != needAlloc(b) {
    35  		return needAlloc(b)
    36  	}
    37  
    38  	// If both are non-auto (e.g., parameters, results), then sort by
    39  	// frame offset (defined by ABI).
    40  	if !needAlloc(a) {
    41  		return a.FrameOffset() < b.FrameOffset()
    42  	}
    43  
    44  	// From here on, a and b are both autos (i.e., local variables).
    45  
    46  	// Sort followers after leaders, if mls != nil
    47  	if mls != nil {
    48  		aFollow := mls.Subsumed(a)
    49  		bFollow := mls.Subsumed(b)
    50  		if aFollow != bFollow {
    51  			return bFollow
    52  		}
    53  	}
    54  
    55  	// Sort used before unused (so AllocFrame can truncate unused
    56  	// variables).
    57  	if a.Used() != b.Used() {
    58  		return a.Used()
    59  	}
    60  
    61  	// Sort pointer-typed before non-pointer types.
    62  	// Keeps the stack's GC bitmap compact.
    63  	ap := a.Type().HasPointers()
    64  	bp := b.Type().HasPointers()
    65  	if ap != bp {
    66  		return ap
    67  	}
    68  
    69  	// Group variables that need zeroing, so we can efficiently zero
    70  	// them altogether.
    71  	ap = a.Needzero()
    72  	bp = b.Needzero()
    73  	if ap != bp {
    74  		return ap
    75  	}
    76  
    77  	// Sort variables in descending alignment order, so we can optimally
    78  	// pack variables into the frame.
    79  	if a.Type().Alignment() != b.Type().Alignment() {
    80  		return a.Type().Alignment() > b.Type().Alignment()
    81  	}
    82  
    83  	// Sort normal variables before open-coded-defer slots, so that the
    84  	// latter are grouped together and near the top of the frame (to
    85  	// minimize varint encoding of their varp offset).
    86  	if a.OpenDeferSlot() != b.OpenDeferSlot() {
    87  		return a.OpenDeferSlot()
    88  	}
    89  
    90  	// If a and b are both open-coded defer slots, then order them by
    91  	// index in descending order, so they'll be laid out in the frame in
    92  	// ascending order.
    93  	//
    94  	// Their index was saved in FrameOffset in state.openDeferSave.
    95  	if a.OpenDeferSlot() {
    96  		return a.FrameOffset() > b.FrameOffset()
    97  	}
    98  
    99  	// Tie breaker for stable results.
   100  	return a.Sym().Name < b.Sym().Name
   101  }
   102  
   103  // needAlloc reports whether n is within the current frame, for which we need to
   104  // allocate space. In particular, it excludes arguments and results, which are in
   105  // the callers frame.
   106  func needAlloc(n *ir.Name) bool {
   107  	if n.Op() != ir.ONAME {
   108  		base.FatalfAt(n.Pos(), "%v has unexpected Op %v", n, n.Op())
   109  	}
   110  
   111  	switch n.Class {
   112  	case ir.PAUTO:
   113  		return true
   114  	case ir.PPARAM:
   115  		return false
   116  	case ir.PPARAMOUT:
   117  		return n.IsOutputParamInRegisters()
   118  
   119  	default:
   120  		base.FatalfAt(n.Pos(), "%v has unexpected Class %v", n, n.Class)
   121  		return false
   122  	}
   123  }
   124  
   125  func (s *ssafn) AllocFrame(f *ssa.Func) {
   126  	s.stksize = 0
   127  	s.stkptrsize = 0
   128  	s.stkalign = int64(types.RegSize)
   129  	fn := s.curfn
   130  
   131  	// Mark the PAUTO's unused.
   132  	for _, ln := range fn.Dcl {
   133  		if ln.OpenDeferSlot() {
   134  			// Open-coded defer slots have indices that were assigned
   135  			// upfront during SSA construction, but the defer statement can
   136  			// later get removed during deadcode elimination (#61895). To
   137  			// keep their relative offsets correct, treat them all as used.
   138  			continue
   139  		}
   140  
   141  		if needAlloc(ln) {
   142  			ln.SetUsed(false)
   143  		}
   144  	}
   145  
   146  	for _, l := range f.RegAlloc {
   147  		if ls, ok := l.(ssa.LocalSlot); ok {
   148  			ls.N.SetUsed(true)
   149  		}
   150  	}
   151  
   152  	for _, b := range f.Blocks {
   153  		for _, v := range b.Values {
   154  			if n, ok := v.Aux.(*ir.Name); ok {
   155  				switch n.Class {
   156  				case ir.PPARAMOUT:
   157  					if n.IsOutputParamInRegisters() && v.Op == ssaop.OpVarDef {
   158  						// ignore VarDef, look for "real" uses.
   159  						// TODO: maybe do this for PAUTO as well?
   160  						continue
   161  					}
   162  					fallthrough
   163  				case ir.PPARAM, ir.PAUTO:
   164  					n.SetUsed(true)
   165  				}
   166  			}
   167  		}
   168  	}
   169  
   170  	var mls *liveness.MergeLocalsState
   171  	var leaders map[*ir.Name]int64
   172  	if base.Debug.MergeLocals != 0 {
   173  		mls = liveness.MergeLocals(fn, f)
   174  		if base.Debug.MergeLocalsTrace > 0 && mls != nil {
   175  			savedNP, savedP := mls.EstSavings()
   176  			fmt.Fprintf(os.Stderr, "%s: %d bytes of stack space saved via stack slot merging (%d nonpointer %d pointer)\n", ir.FuncName(fn), savedNP+savedP, savedNP, savedP)
   177  			if base.Debug.MergeLocalsTrace > 1 {
   178  				fmt.Fprintf(os.Stderr, "=-= merge locals state for %v:\n%v",
   179  					fn, mls)
   180  			}
   181  		}
   182  		leaders = make(map[*ir.Name]int64)
   183  	}
   184  
   185  	// Use sort.SliceStable instead of sort.Slice so stack layout (and thus
   186  	// compiler output) is less sensitive to frontend changes that
   187  	// introduce or remove unused variables.
   188  	sort.SliceStable(fn.Dcl, func(i, j int) bool {
   189  		return cmpstackvarlt(fn.Dcl[i], fn.Dcl[j], mls)
   190  	})
   191  
   192  	if mls != nil {
   193  		// Rewrite fn.Dcl to reposition followers (subsumed vars) to
   194  		// be immediately following the leader var in their partition.
   195  		followers := []*ir.Name{}
   196  		newdcl := make([]*ir.Name, 0, len(fn.Dcl))
   197  		for i := 0; i < len(fn.Dcl); i++ {
   198  			n := fn.Dcl[i]
   199  			if mls.Subsumed(n) {
   200  				continue
   201  			}
   202  			newdcl = append(newdcl, n)
   203  			if mls.IsLeader(n) {
   204  				followers = mls.Followers(n, followers)
   205  				// position followers immediately after leader
   206  				newdcl = append(newdcl, followers...)
   207  			}
   208  		}
   209  		fn.Dcl = newdcl
   210  	}
   211  
   212  	if base.Debug.MergeLocalsTrace > 1 && mls != nil {
   213  		fmt.Fprintf(os.Stderr, "=-= sorted DCL for %v:\n", fn)
   214  		for i, v := range fn.Dcl {
   215  			if !ssa.IsMergeCandidate(v) {
   216  				continue
   217  			}
   218  			fmt.Fprintf(os.Stderr, " %d: %q isleader=%v subsumed=%v used=%v sz=%d align=%d t=%s\n", i, v.Sym().Name, mls.IsLeader(v), mls.Subsumed(v), v.Used(), v.Type().Size(), v.Type().Alignment(), v.Type().String())
   219  		}
   220  	}
   221  
   222  	// Reassign stack offsets of the locals that are used.
   223  	lastHasPtr := false
   224  	for i, n := range fn.Dcl {
   225  		if n.Op() != ir.ONAME || n.Class != ir.PAUTO && !(n.Class == ir.PPARAMOUT && n.IsOutputParamInRegisters()) {
   226  			// i.e., stack assign if AUTO, or if PARAMOUT in registers (which has no predefined spill locations)
   227  			continue
   228  		}
   229  		if mls != nil && mls.Subsumed(n) {
   230  			continue
   231  		}
   232  		if !n.Used() {
   233  			fn.DebugInfo.(*ssadebug.FuncDebug).OptDcl = fn.Dcl[i:]
   234  			fn.Dcl = fn.Dcl[:i]
   235  			break
   236  		}
   237  		types.CalcSize(n.Type())
   238  		w := n.Type().Size()
   239  		if w >= types.MaxWidth || w < 0 {
   240  			base.Fatalf("bad width")
   241  		}
   242  		if w == 0 && lastHasPtr {
   243  			// Pad between a pointer-containing object and a zero-sized object.
   244  			// This prevents a pointer to the zero-sized object from being interpreted
   245  			// as a pointer to the pointer-containing object (and causing it
   246  			// to be scanned when it shouldn't be). See issue 24993.
   247  			w = 1
   248  		}
   249  		s.stksize += w
   250  		s.stksize = types.RoundUp(s.stksize, n.Type().Alignment())
   251  		if n.Type().Alignment() > int64(types.RegSize) {
   252  			s.stkalign = n.Type().Alignment()
   253  		}
   254  		if n.Type().HasPointers() {
   255  			s.stkptrsize = s.stksize
   256  			lastHasPtr = true
   257  		} else {
   258  			lastHasPtr = false
   259  		}
   260  		n.SetFrameOffset(-s.stksize)
   261  		if mls != nil && mls.IsLeader(n) {
   262  			leaders[n] = -s.stksize
   263  		}
   264  	}
   265  
   266  	if mls != nil {
   267  		// Update offsets of followers (subsumed vars) to be the
   268  		// same as the leader var in their partition.
   269  		for i := 0; i < len(fn.Dcl); i++ {
   270  			n := fn.Dcl[i]
   271  			if !mls.Subsumed(n) {
   272  				continue
   273  			}
   274  			leader := mls.Leader(n)
   275  			off, ok := leaders[leader]
   276  			if !ok {
   277  				panic("internal error missing leader")
   278  			}
   279  			// Set the stack offset this subsumed (followed) var
   280  			// to be the same as the leader.
   281  			n.SetFrameOffset(off)
   282  		}
   283  
   284  		if base.Debug.MergeLocalsTrace > 1 {
   285  			fmt.Fprintf(os.Stderr, "=-= stack layout for %v:\n", fn)
   286  			for i, v := range fn.Dcl {
   287  				if v.Op() != ir.ONAME || (v.Class != ir.PAUTO && !(v.Class == ir.PPARAMOUT && v.IsOutputParamInRegisters())) {
   288  					continue
   289  				}
   290  				fmt.Fprintf(os.Stderr, " %d: %q frameoff %d isleader=%v subsumed=%v sz=%d align=%d t=%s\n", i, v.Sym().Name, v.FrameOffset(), mls.IsLeader(v), mls.Subsumed(v), v.Type().Size(), v.Type().Alignment(), v.Type().String())
   291  			}
   292  		}
   293  	}
   294  
   295  	s.stksize = types.RoundUp(s.stksize, s.stkalign)
   296  	s.stkptrsize = types.RoundUp(s.stkptrsize, s.stkalign)
   297  }
   298  
   299  const maxStackSize = 1 << 30
   300  
   301  // Compile builds an SSA backend function,
   302  // uses it to generate a plist,
   303  // and flushes that plist to machine code.
   304  // worker indicates which of the backend workers is doing the processing.
   305  func Compile(ssacompiler ssa.Compiler, fn *ir.Func, worker int, profile *pgoir.Profile) {
   306  	f, htmlWriter := buildssa(ssacompiler, fn, worker, inline.IsPgoHotFunc(fn, profile) || inline.HasPgoHotInline(fn))
   307  	// Note: check arg size to fix issue 25507.
   308  	if f.Frontend().(*ssafn).stksize >= maxStackSize || f.OwnAux.ArgWidth() >= maxStackSize {
   309  		largeStackFramesMu.Lock()
   310  		largeStackFrames = append(largeStackFrames, largeStack{locals: f.Frontend().(*ssafn).stksize, args: f.OwnAux.ArgWidth(), pos: fn.Pos()})
   311  		largeStackFramesMu.Unlock()
   312  		return
   313  	}
   314  	pp := objw.NewProgs(fn, worker)
   315  	defer pp.Free()
   316  	genssa(htmlWriter, f, pp)
   317  	// Check frame size again.
   318  	// The check above included only the space needed for local variables.
   319  	// After genssa, the space needed includes local variables and the callee arg region.
   320  	// We must do this check prior to calling pp.Flush.
   321  	// If there are any oversized stack frames,
   322  	// the assembler may emit inscrutable complaints about invalid instructions.
   323  	if pp.Text.To.Offset >= maxStackSize {
   324  		largeStackFramesMu.Lock()
   325  		locals := f.Frontend().(*ssafn).stksize
   326  		largeStackFrames = append(largeStackFrames, largeStack{locals: locals, args: f.OwnAux.ArgWidth(), callee: pp.Text.To.Offset - locals, pos: fn.Pos()})
   327  		largeStackFramesMu.Unlock()
   328  		return
   329  	}
   330  
   331  	pp.Flush() // assemble, fill in boilerplate, etc.
   332  
   333  	// If we're compiling the package init function, search for any
   334  	// relocations that target global map init outline functions and
   335  	// turn them into weak relocs.
   336  	if fn.IsPackageInit() && base.Debug.WrapGlobalMapCtl != 1 {
   337  		weakenGlobalMapInitRelocs(fn)
   338  	}
   339  
   340  	// fieldtrack must be called after pp.Flush. See issue 20014.
   341  	fieldtrack(pp.Text.From.Sym, fn.FieldTrack)
   342  }
   343  
   344  // globalMapInitLsyms records the LSym of each map.init.NNN outlined
   345  // map initializer function created by the compiler.
   346  var globalMapInitLsyms map[*obj.LSym]struct{}
   347  
   348  // RegisterMapInitLsym records "s" in the set of outlined map initializer
   349  // functions.
   350  func RegisterMapInitLsym(s *obj.LSym) {
   351  	if globalMapInitLsyms == nil {
   352  		globalMapInitLsyms = make(map[*obj.LSym]struct{})
   353  	}
   354  	globalMapInitLsyms[s] = struct{}{}
   355  }
   356  
   357  // weakenGlobalMapInitRelocs walks through all of the relocations on a
   358  // given a package init function "fn" and looks for relocs that target
   359  // outlined global map initializer functions; if it finds any such
   360  // relocs, it flags them as R_WEAK.
   361  func weakenGlobalMapInitRelocs(fn *ir.Func) {
   362  	if globalMapInitLsyms == nil {
   363  		return
   364  	}
   365  	for i := range fn.LSym.R {
   366  		tgt := fn.LSym.R[i].Sym
   367  		if tgt == nil {
   368  			continue
   369  		}
   370  		if _, ok := globalMapInitLsyms[tgt]; !ok {
   371  			continue
   372  		}
   373  		if base.Debug.WrapGlobalMapDbg > 1 {
   374  			fmt.Fprintf(os.Stderr, "=-= weakify fn %v reloc %d %+v\n", fn, i,
   375  				fn.LSym.R[i])
   376  		}
   377  		// set the R_WEAK bit, leave rest of reloc type intact
   378  		fn.LSym.R[i].Type |= objabi.R_WEAK
   379  	}
   380  }
   381  
   382  // StackOffset returns the stack location of a LocalSlot relative to the
   383  // stack pointer, suitable for use in a DWARF location entry. This has nothing
   384  // to do with its offset in the user variable.
   385  func StackOffset(slot ssa.LocalSlot) int32 {
   386  	n := slot.N
   387  	var off int64
   388  	switch n.Class {
   389  	case ir.PPARAM, ir.PPARAMOUT:
   390  		if !n.IsOutputParamInRegisters() {
   391  			off = n.FrameOffset() + base.Ctxt.Arch.FixedFrameSize
   392  			break
   393  		}
   394  		fallthrough // PPARAMOUT in registers allocates like an AUTO
   395  	case ir.PAUTO:
   396  		off = n.FrameOffset()
   397  		if base.Ctxt.Arch.FixedFrameSize == 0 {
   398  			off -= int64(types.PtrSize)
   399  		}
   400  		if buildcfg.FramePointerEnabled {
   401  			off -= int64(types.PtrSize)
   402  		}
   403  	}
   404  	return int32(off + slot.Off)
   405  }
   406  
   407  // fieldtrack adds R_USEFIELD relocations to fnsym to record any
   408  // struct fields that it used.
   409  func fieldtrack(fnsym *obj.LSym, tracked map[*obj.LSym]struct{}) {
   410  	if fnsym == nil {
   411  		return
   412  	}
   413  	if !buildcfg.Experiment.FieldTrack || len(tracked) == 0 {
   414  		return
   415  	}
   416  
   417  	trackSyms := make([]*obj.LSym, 0, len(tracked))
   418  	for sym := range tracked {
   419  		trackSyms = append(trackSyms, sym)
   420  	}
   421  	slices.SortFunc(trackSyms, func(a, b *obj.LSym) int { return strings.Compare(a.Name, b.Name) })
   422  	for _, sym := range trackSyms {
   423  		fnsym.AddRel(base.Ctxt, obj.Reloc{Type: objabi.R_USEFIELD, Sym: sym})
   424  	}
   425  }
   426  
   427  // largeStack is info about a function whose stack frame is too large (rare).
   428  type largeStack struct {
   429  	locals int64
   430  	args   int64
   431  	callee int64
   432  	pos    src.XPos
   433  }
   434  
   435  var (
   436  	largeStackFramesMu sync.Mutex // protects largeStackFrames
   437  	largeStackFrames   []largeStack
   438  )
   439  
   440  func CheckLargeStacks() {
   441  	// Check whether any of the functions we have compiled have gigantic stack frames.
   442  	sort.Slice(largeStackFrames, func(i, j int) bool {
   443  		return largeStackFrames[i].pos.Before(largeStackFrames[j].pos)
   444  	})
   445  	for _, large := range largeStackFrames {
   446  		if large.callee != 0 {
   447  			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)
   448  		} else {
   449  			base.ErrorfAt(large.pos, 0, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20)
   450  		}
   451  	}
   452  }
   453  

View as plain text