github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/ssagen/pgen.go (about)

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