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