github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/noder/unified.go (about) 1 // Copyright 2021 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 noder 6 7 import ( 8 "fmt" 9 "io" 10 "runtime" 11 "sort" 12 "strings" 13 14 "github.com/go-asm/go/pkgbits" 15 16 "github.com/go-asm/go/cmd/compile/base" 17 "github.com/go-asm/go/cmd/compile/inline" 18 "github.com/go-asm/go/cmd/compile/ir" 19 "github.com/go-asm/go/cmd/compile/pgo" 20 "github.com/go-asm/go/cmd/compile/typecheck" 21 "github.com/go-asm/go/cmd/compile/types" 22 "github.com/go-asm/go/cmd/compile/types2" 23 "github.com/go-asm/go/cmd/src" 24 ) 25 26 // localPkgReader holds the package reader used for reading the local 27 // package. It exists so the unified IR linker can refer back to it 28 // later. 29 var localPkgReader *pkgReader 30 31 // LookupMethodFunc returns the ir.Func for an arbitrary full symbol name if 32 // that function exists in the set of available export data. 33 // 34 // This allows lookup of arbitrary functions and methods that aren't otherwise 35 // referenced by the local package and thus haven't been read yet. 36 // 37 // TODO(prattmic): Does not handle instantiation of generic types. Currently 38 // profiles don't contain the original type arguments, so we won't be able to 39 // create the runtime dictionaries. 40 // 41 // TODO(prattmic): Hit rate of this function is usually fairly low, and errors 42 // are only used when debug logging is enabled. Consider constructing cheaper 43 // errors by default. 44 func LookupFunc(fullName string) (*ir.Func, error) { 45 pkgPath, symName, err := ir.ParseLinkFuncName(fullName) 46 if err != nil { 47 return nil, fmt.Errorf("error parsing symbol name %q: %v", fullName, err) 48 } 49 50 pkg, ok := types.PkgMap()[pkgPath] 51 if !ok { 52 return nil, fmt.Errorf("pkg %s doesn't exist in %v", pkgPath, types.PkgMap()) 53 } 54 55 // Symbol naming is ambiguous. We can't necessarily distinguish between 56 // a method and a closure. e.g., is foo.Bar.func1 a closure defined in 57 // function Bar, or a method on type Bar? Thus we must simply attempt 58 // to lookup both. 59 60 fn, err := lookupFunction(pkg, symName) 61 if err == nil { 62 return fn, nil 63 } 64 65 fn, mErr := lookupMethod(pkg, symName) 66 if mErr == nil { 67 return fn, nil 68 } 69 70 return nil, fmt.Errorf("%s is not a function (%v) or method (%v)", fullName, err, mErr) 71 } 72 73 func lookupFunction(pkg *types.Pkg, symName string) (*ir.Func, error) { 74 sym := pkg.Lookup(symName) 75 76 // TODO(prattmic): Enclosed functions (e.g., foo.Bar.func1) are not 77 // present in objReader, only as OCLOSURE nodes in the enclosing 78 // function. 79 pri, ok := objReader[sym] 80 if !ok { 81 return nil, fmt.Errorf("func sym %v missing objReader", sym) 82 } 83 84 name := pri.pr.objIdx(pri.idx, nil, nil, false).(*ir.Name) 85 if name.Op() != ir.ONAME || name.Class != ir.PFUNC { 86 return nil, fmt.Errorf("func sym %v refers to non-function name: %v", sym, name) 87 } 88 return name.Func, nil 89 } 90 91 func lookupMethod(pkg *types.Pkg, symName string) (*ir.Func, error) { 92 // N.B. readPackage creates a Sym for every object in the package to 93 // initialize objReader and importBodyReader, even if the object isn't 94 // read. 95 // 96 // However, objReader is only initialized for top-level objects, so we 97 // must first lookup the type and use that to find the method rather 98 // than looking for the method directly. 99 typ, meth, err := ir.LookupMethodSelector(pkg, symName) 100 if err != nil { 101 return nil, fmt.Errorf("error looking up method symbol %q: %v", symName, err) 102 } 103 104 pri, ok := objReader[typ] 105 if !ok { 106 return nil, fmt.Errorf("type sym %v missing objReader", typ) 107 } 108 109 name := pri.pr.objIdx(pri.idx, nil, nil, false).(*ir.Name) 110 if name.Op() != ir.OTYPE { 111 return nil, fmt.Errorf("type sym %v refers to non-type name: %v", typ, name) 112 } 113 if name.Alias() { 114 return nil, fmt.Errorf("type sym %v refers to alias", typ) 115 } 116 117 for _, m := range name.Type().Methods() { 118 if m.Sym == meth { 119 fn := m.Nname.(*ir.Name).Func 120 return fn, nil 121 } 122 } 123 124 return nil, fmt.Errorf("method %s missing from method set of %v", symName, typ) 125 } 126 127 // unified constructs the local package's Internal Representation (IR) 128 // from its syntax tree (AST). 129 // 130 // The pipeline contains 2 steps: 131 // 132 // 1. Generate the export data "stub". 133 // 134 // 2. Generate the IR from the export data above. 135 // 136 // The package data "stub" at step (1) contains everything from the local package, 137 // but nothing that has been imported. When we're actually writing out export data 138 // to the output files (see writeNewExport), we run the "linker", which: 139 // 140 // - Updates compiler extensions data (e.g. inlining cost, escape analysis results). 141 // 142 // - Handles re-exporting any transitive dependencies. 143 // 144 // - Prunes out any unnecessary details (e.g. non-inlineable functions, because any 145 // downstream importers only care about inlinable functions). 146 // 147 // The source files are typechecked twice: once before writing the export data 148 // using types2, and again after reading the export data using gc/typecheck. 149 // The duplication of work will go away once we only use the types2 type checker, 150 // removing the gc/typecheck step. For now, it is kept because: 151 // 152 // - It reduces the engineering costs in maintaining a fork of typecheck 153 // (e.g. no need to backport fixes like CL 327651). 154 // 155 // - It makes it easier to pass toolstash -cmp. 156 // 157 // - Historically, we would always re-run the typechecker after importing a package, 158 // even though we know the imported data is valid. It's not ideal, but it's 159 // not causing any problems either. 160 // 161 // - gc/typecheck is still in charge of some transformations, such as rewriting 162 // multi-valued function calls or transforming ir.OINDEX to ir.OINDEXMAP. 163 // 164 // Using the syntax tree with types2, which has a complete representation of generics, 165 // the unified IR has the full typed AST needed for introspection during step (1). 166 // In other words, we have all the necessary information to build the generic IR form 167 // (see writer.captureVars for an example). 168 func unified(m posMap, noders []*noder) { 169 inline.InlineCall = unifiedInlineCall 170 typecheck.HaveInlineBody = unifiedHaveInlineBody 171 pgo.LookupFunc = LookupFunc 172 173 data := writePkgStub(m, noders) 174 175 target := typecheck.Target 176 177 localPkgReader = newPkgReader(pkgbits.NewPkgDecoder(types.LocalPkg.Path, data)) 178 readPackage(localPkgReader, types.LocalPkg, true) 179 180 r := localPkgReader.newReader(pkgbits.RelocMeta, pkgbits.PrivateRootIdx, pkgbits.SyncPrivate) 181 r.pkgInit(types.LocalPkg, target) 182 183 readBodies(target, false) 184 185 // Check that nothing snuck past typechecking. 186 for _, fn := range target.Funcs { 187 if fn.Typecheck() == 0 { 188 base.FatalfAt(fn.Pos(), "missed typecheck: %v", fn) 189 } 190 191 // For functions, check that at least their first statement (if 192 // any) was typechecked too. 193 if len(fn.Body) != 0 { 194 if stmt := fn.Body[0]; stmt.Typecheck() == 0 { 195 base.FatalfAt(stmt.Pos(), "missed typecheck: %v", stmt) 196 } 197 } 198 } 199 200 // For functions originally came from package runtime, 201 // mark as norace to prevent instrumenting, see issue #60439. 202 for _, fn := range target.Funcs { 203 if !base.Flag.CompilingRuntime && types.RuntimeSymName(fn.Sym()) != "" { 204 fn.Pragma |= ir.Norace 205 } 206 } 207 208 base.ExitIfErrors() // just in case 209 } 210 211 // readBodies iteratively expands all pending dictionaries and 212 // function bodies. 213 // 214 // If duringInlining is true, then the inline.InlineDecls is called as 215 // necessary on instantiations of imported generic functions, so their 216 // inlining costs can be computed. 217 func readBodies(target *ir.Package, duringInlining bool) { 218 var inlDecls []*ir.Func 219 220 // Don't use range--bodyIdx can add closures to todoBodies. 221 for { 222 // The order we expand dictionaries and bodies doesn't matter, so 223 // pop from the end to reduce todoBodies reallocations if it grows 224 // further. 225 // 226 // However, we do at least need to flush any pending dictionaries 227 // before reading bodies, because bodies might reference the 228 // dictionaries. 229 230 if len(todoDicts) > 0 { 231 fn := todoDicts[len(todoDicts)-1] 232 todoDicts = todoDicts[:len(todoDicts)-1] 233 fn() 234 continue 235 } 236 237 if len(todoBodies) > 0 { 238 fn := todoBodies[len(todoBodies)-1] 239 todoBodies = todoBodies[:len(todoBodies)-1] 240 241 pri, ok := bodyReader[fn] 242 assert(ok) 243 pri.funcBody(fn) 244 245 // Instantiated generic function: add to Decls for typechecking 246 // and compilation. 247 if fn.OClosure == nil && len(pri.dict.targs) != 0 { 248 // cmd/link does not support a type symbol referencing a method symbol 249 // across DSO boundary, so force re-compiling methods on a generic type 250 // even it was seen from imported package in linkshared mode, see #58966. 251 canSkipNonGenericMethod := !(base.Ctxt.Flag_linkshared && ir.IsMethod(fn)) 252 if duringInlining && canSkipNonGenericMethod { 253 inlDecls = append(inlDecls, fn) 254 } else { 255 target.Funcs = append(target.Funcs, fn) 256 } 257 } 258 259 continue 260 } 261 262 break 263 } 264 265 todoDicts = nil 266 todoBodies = nil 267 268 if len(inlDecls) != 0 { 269 // If we instantiated any generic functions during inlining, we need 270 // to call CanInline on them so they'll be transitively inlined 271 // correctly (#56280). 272 // 273 // We know these functions were already compiled in an imported 274 // package though, so we don't need to actually apply InlineCalls or 275 // save the function bodies any further than this. 276 // 277 // We can also lower the -m flag to 0, to suppress duplicate "can 278 // inline" diagnostics reported against the imported package. Again, 279 // we already reported those diagnostics in the original package, so 280 // it's pointless repeating them here. 281 282 oldLowerM := base.Flag.LowerM 283 base.Flag.LowerM = 0 284 inline.CanInlineFuncs(inlDecls, nil) 285 base.Flag.LowerM = oldLowerM 286 287 for _, fn := range inlDecls { 288 fn.Body = nil // free memory 289 } 290 } 291 } 292 293 // writePkgStub type checks the given parsed source files, 294 // writes an export data package stub representing them, 295 // and returns the result. 296 func writePkgStub(m posMap, noders []*noder) string { 297 pkg, info := checkFiles(m, noders) 298 299 pw := newPkgWriter(m, pkg, info) 300 301 pw.collectDecls(noders) 302 303 publicRootWriter := pw.newWriter(pkgbits.RelocMeta, pkgbits.SyncPublic) 304 privateRootWriter := pw.newWriter(pkgbits.RelocMeta, pkgbits.SyncPrivate) 305 306 assert(publicRootWriter.Idx == pkgbits.PublicRootIdx) 307 assert(privateRootWriter.Idx == pkgbits.PrivateRootIdx) 308 309 { 310 w := publicRootWriter 311 w.pkg(pkg) 312 w.Bool(false) // TODO(mdempsky): Remove; was "has init" 313 314 scope := pkg.Scope() 315 names := scope.Names() 316 w.Len(len(names)) 317 for _, name := range names { 318 w.obj(scope.Lookup(name), nil) 319 } 320 321 w.Sync(pkgbits.SyncEOF) 322 w.Flush() 323 } 324 325 { 326 w := privateRootWriter 327 w.pkgInit(noders) 328 w.Flush() 329 } 330 331 var sb strings.Builder 332 pw.DumpTo(&sb) 333 334 // At this point, we're done with types2. Make sure the package is 335 // garbage collected. 336 freePackage(pkg) 337 338 return sb.String() 339 } 340 341 // freePackage ensures the given package is garbage collected. 342 func freePackage(pkg *types2.Package) { 343 // The GC test below relies on a precise GC that runs finalizers as 344 // soon as objects are unreachable. Our implementation provides 345 // this, but other/older implementations may not (e.g., Go 1.4 does 346 // not because of #22350). To avoid imposing unnecessary 347 // restrictions on the GOROOT_BOOTSTRAP toolchain, we skip the test 348 // during bootstrapping. 349 if base.CompilerBootstrap || base.Debug.GCCheck == 0 { 350 *pkg = types2.Package{} 351 return 352 } 353 354 // Set a finalizer on pkg so we can detect if/when it's collected. 355 done := make(chan struct{}) 356 runtime.SetFinalizer(pkg, func(*types2.Package) { close(done) }) 357 358 // Important: objects involved in cycles are not finalized, so zero 359 // out pkg to break its cycles and allow the finalizer to run. 360 *pkg = types2.Package{} 361 362 // It typically takes just 1 or 2 cycles to release pkg, but it 363 // doesn't hurt to try a few more times. 364 for i := 0; i < 10; i++ { 365 select { 366 case <-done: 367 return 368 default: 369 runtime.GC() 370 } 371 } 372 373 base.Fatalf("package never finalized") 374 } 375 376 // readPackage reads package export data from pr to populate 377 // importpkg. 378 // 379 // localStub indicates whether pr is reading the stub export data for 380 // the local package, as opposed to relocated export data for an 381 // import. 382 func readPackage(pr *pkgReader, importpkg *types.Pkg, localStub bool) { 383 { 384 r := pr.newReader(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic) 385 386 pkg := r.pkg() 387 base.Assertf(pkg == importpkg, "have package %q (%p), want package %q (%p)", pkg.Path, pkg, importpkg.Path, importpkg) 388 389 r.Bool() // TODO(mdempsky): Remove; was "has init" 390 391 for i, n := 0, r.Len(); i < n; i++ { 392 r.Sync(pkgbits.SyncObject) 393 assert(!r.Bool()) 394 idx := r.Reloc(pkgbits.RelocObj) 395 assert(r.Len() == 0) 396 397 path, name, code := r.p.PeekObj(idx) 398 if code != pkgbits.ObjStub { 399 objReader[types.NewPkg(path, "").Lookup(name)] = pkgReaderIndex{pr, idx, nil, nil, nil} 400 } 401 } 402 403 r.Sync(pkgbits.SyncEOF) 404 } 405 406 if !localStub { 407 r := pr.newReader(pkgbits.RelocMeta, pkgbits.PrivateRootIdx, pkgbits.SyncPrivate) 408 409 if r.Bool() { 410 sym := importpkg.Lookup(".inittask") 411 task := ir.NewNameAt(src.NoXPos, sym, nil) 412 task.Class = ir.PEXTERN 413 sym.Def = task 414 } 415 416 for i, n := 0, r.Len(); i < n; i++ { 417 path := r.String() 418 name := r.String() 419 idx := r.Reloc(pkgbits.RelocBody) 420 421 sym := types.NewPkg(path, "").Lookup(name) 422 if _, ok := importBodyReader[sym]; !ok { 423 importBodyReader[sym] = pkgReaderIndex{pr, idx, nil, nil, nil} 424 } 425 } 426 427 r.Sync(pkgbits.SyncEOF) 428 } 429 } 430 431 // writeUnifiedExport writes to `out` the finalized, self-contained 432 // Unified IR export data file for the current compilation unit. 433 func writeUnifiedExport(out io.Writer) { 434 l := linker{ 435 pw: pkgbits.NewPkgEncoder(base.Debug.SyncFrames), 436 437 pkgs: make(map[string]pkgbits.Index), 438 decls: make(map[*types.Sym]pkgbits.Index), 439 bodies: make(map[*types.Sym]pkgbits.Index), 440 } 441 442 publicRootWriter := l.pw.NewEncoder(pkgbits.RelocMeta, pkgbits.SyncPublic) 443 privateRootWriter := l.pw.NewEncoder(pkgbits.RelocMeta, pkgbits.SyncPrivate) 444 assert(publicRootWriter.Idx == pkgbits.PublicRootIdx) 445 assert(privateRootWriter.Idx == pkgbits.PrivateRootIdx) 446 447 var selfPkgIdx pkgbits.Index 448 449 { 450 pr := localPkgReader 451 r := pr.NewDecoder(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic) 452 453 r.Sync(pkgbits.SyncPkg) 454 selfPkgIdx = l.relocIdx(pr, pkgbits.RelocPkg, r.Reloc(pkgbits.RelocPkg)) 455 456 r.Bool() // TODO(mdempsky): Remove; was "has init" 457 458 for i, n := 0, r.Len(); i < n; i++ { 459 r.Sync(pkgbits.SyncObject) 460 assert(!r.Bool()) 461 idx := r.Reloc(pkgbits.RelocObj) 462 assert(r.Len() == 0) 463 464 xpath, xname, xtag := pr.PeekObj(idx) 465 assert(xpath == pr.PkgPath()) 466 assert(xtag != pkgbits.ObjStub) 467 468 if types.IsExported(xname) { 469 l.relocIdx(pr, pkgbits.RelocObj, idx) 470 } 471 } 472 473 r.Sync(pkgbits.SyncEOF) 474 } 475 476 { 477 var idxs []pkgbits.Index 478 for _, idx := range l.decls { 479 idxs = append(idxs, idx) 480 } 481 sort.Slice(idxs, func(i, j int) bool { return idxs[i] < idxs[j] }) 482 483 w := publicRootWriter 484 485 w.Sync(pkgbits.SyncPkg) 486 w.Reloc(pkgbits.RelocPkg, selfPkgIdx) 487 w.Bool(false) // TODO(mdempsky): Remove; was "has init" 488 489 w.Len(len(idxs)) 490 for _, idx := range idxs { 491 w.Sync(pkgbits.SyncObject) 492 w.Bool(false) 493 w.Reloc(pkgbits.RelocObj, idx) 494 w.Len(0) 495 } 496 497 w.Sync(pkgbits.SyncEOF) 498 w.Flush() 499 } 500 501 { 502 type symIdx struct { 503 sym *types.Sym 504 idx pkgbits.Index 505 } 506 var bodies []symIdx 507 for sym, idx := range l.bodies { 508 bodies = append(bodies, symIdx{sym, idx}) 509 } 510 sort.Slice(bodies, func(i, j int) bool { return bodies[i].idx < bodies[j].idx }) 511 512 w := privateRootWriter 513 514 w.Bool(typecheck.Lookup(".inittask").Def != nil) 515 516 w.Len(len(bodies)) 517 for _, body := range bodies { 518 w.String(body.sym.Pkg.Path) 519 w.String(body.sym.Name) 520 w.Reloc(pkgbits.RelocBody, body.idx) 521 } 522 523 w.Sync(pkgbits.SyncEOF) 524 w.Flush() 525 } 526 527 base.Ctxt.Fingerprint = l.pw.DumpTo(out) 528 }