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 }