diff options
Diffstat (limited to 'src/cmd/compile/internal/noder/linker.go')
-rw-r--r-- | src/cmd/compile/internal/noder/linker.go | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/noder/linker.go b/src/cmd/compile/internal/noder/linker.go new file mode 100644 index 0000000..5d0459d --- /dev/null +++ b/src/cmd/compile/internal/noder/linker.go @@ -0,0 +1,337 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package noder + +import ( + "internal/pkgbits" + "io" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" + "cmd/compile/internal/types" + "cmd/internal/goobj" + "cmd/internal/obj" +) + +// This file implements the unified IR linker, which combines the +// local package's stub data with imported package data to produce a +// complete export data file. It also rewrites the compiler's +// extension data sections based on the results of compilation (e.g., +// the function inlining cost and linker symbol index assignments). +// +// TODO(mdempsky): Using the name "linker" here is confusing, because +// readers are likely to mistake references to it for cmd/link. But +// there's a shortage of good names for "something that combines +// multiple parts into a cohesive whole"... e.g., "assembler" and +// "compiler" are also already taken. + +// TODO(mdempsky): Should linker go into pkgbits? Probably the +// low-level linking details can be moved there, but the logic for +// handling extension data needs to stay in the compiler. + +// A linker combines a package's stub export data with any referenced +// elements from imported packages into a single, self-contained +// export data file. +type linker struct { + pw pkgbits.PkgEncoder + + pkgs map[string]pkgbits.Index + decls map[*types.Sym]pkgbits.Index + bodies map[*types.Sym]pkgbits.Index +} + +// relocAll ensures that all elements specified by pr and relocs are +// copied into the output export data file, and returns the +// corresponding indices in the output. +func (l *linker) relocAll(pr *pkgReader, relocs []pkgbits.RelocEnt) []pkgbits.RelocEnt { + res := make([]pkgbits.RelocEnt, len(relocs)) + for i, rent := range relocs { + rent.Idx = l.relocIdx(pr, rent.Kind, rent.Idx) + res[i] = rent + } + return res +} + +// relocIdx ensures a single element is copied into the output export +// data file, and returns the corresponding index in the output. +func (l *linker) relocIdx(pr *pkgReader, k pkgbits.RelocKind, idx pkgbits.Index) pkgbits.Index { + assert(pr != nil) + + absIdx := pr.AbsIdx(k, idx) + + if newidx := pr.newindex[absIdx]; newidx != 0 { + return ^newidx + } + + var newidx pkgbits.Index + switch k { + case pkgbits.RelocString: + newidx = l.relocString(pr, idx) + case pkgbits.RelocPkg: + newidx = l.relocPkg(pr, idx) + case pkgbits.RelocObj: + newidx = l.relocObj(pr, idx) + + default: + // Generic relocations. + // + // TODO(mdempsky): Deduplicate more sections? In fact, I think + // every section could be deduplicated. This would also be easier + // if we do external relocations. + + w := l.pw.NewEncoderRaw(k) + l.relocCommon(pr, &w, k, idx) + newidx = w.Idx + } + + pr.newindex[absIdx] = ^newidx + + return newidx +} + +// relocString copies the specified string from pr into the output +// export data file, deduplicating it against other strings. +func (l *linker) relocString(pr *pkgReader, idx pkgbits.Index) pkgbits.Index { + return l.pw.StringIdx(pr.StringIdx(idx)) +} + +// relocPkg copies the specified package from pr into the output +// export data file, rewriting its import path to match how it was +// imported. +// +// TODO(mdempsky): Since CL 391014, we already have the compilation +// unit's import path, so there should be no need to rewrite packages +// anymore. +func (l *linker) relocPkg(pr *pkgReader, idx pkgbits.Index) pkgbits.Index { + path := pr.PeekPkgPath(idx) + + if newidx, ok := l.pkgs[path]; ok { + return newidx + } + + r := pr.NewDecoder(pkgbits.RelocPkg, idx, pkgbits.SyncPkgDef) + w := l.pw.NewEncoder(pkgbits.RelocPkg, pkgbits.SyncPkgDef) + l.pkgs[path] = w.Idx + + // TODO(mdempsky): We end up leaving an empty string reference here + // from when the package was originally written as "". Probably not + // a big deal, but a little annoying. Maybe relocating + // cross-references in place is the way to go after all. + w.Relocs = l.relocAll(pr, r.Relocs) + + _ = r.String() // original path + w.String(path) + + io.Copy(&w.Data, &r.Data) + + return w.Flush() +} + +// relocObj copies the specified object from pr into the output export +// data file, rewriting its compiler-private extension data (e.g., +// adding inlining cost and escape analysis results for functions). +func (l *linker) relocObj(pr *pkgReader, idx pkgbits.Index) pkgbits.Index { + path, name, tag := pr.PeekObj(idx) + sym := types.NewPkg(path, "").Lookup(name) + + if newidx, ok := l.decls[sym]; ok { + return newidx + } + + if tag == pkgbits.ObjStub && path != "builtin" && path != "unsafe" { + pri, ok := objReader[sym] + if !ok { + base.Fatalf("missing reader for %q.%v", path, name) + } + assert(ok) + + pr = pri.pr + idx = pri.idx + + path2, name2, tag2 := pr.PeekObj(idx) + sym2 := types.NewPkg(path2, "").Lookup(name2) + assert(sym == sym2) + assert(tag2 != pkgbits.ObjStub) + } + + w := l.pw.NewEncoderRaw(pkgbits.RelocObj) + wext := l.pw.NewEncoderRaw(pkgbits.RelocObjExt) + wname := l.pw.NewEncoderRaw(pkgbits.RelocName) + wdict := l.pw.NewEncoderRaw(pkgbits.RelocObjDict) + + l.decls[sym] = w.Idx + assert(wext.Idx == w.Idx) + assert(wname.Idx == w.Idx) + assert(wdict.Idx == w.Idx) + + l.relocCommon(pr, &w, pkgbits.RelocObj, idx) + l.relocCommon(pr, &wname, pkgbits.RelocName, idx) + l.relocCommon(pr, &wdict, pkgbits.RelocObjDict, idx) + + // Generic types and functions won't have definitions, and imported + // objects may not either. + obj, _ := sym.Def.(*ir.Name) + local := sym.Pkg == types.LocalPkg + + if local && obj != nil { + wext.Sync(pkgbits.SyncObject1) + switch tag { + case pkgbits.ObjFunc: + l.relocFuncExt(&wext, obj) + case pkgbits.ObjType: + l.relocTypeExt(&wext, obj) + case pkgbits.ObjVar: + l.relocVarExt(&wext, obj) + } + wext.Flush() + } else { + l.relocCommon(pr, &wext, pkgbits.RelocObjExt, idx) + } + + // Check if we need to export the inline bodies for functions and + // methods. + if obj != nil { + if obj.Op() == ir.ONAME && obj.Class == ir.PFUNC { + l.exportBody(obj, local) + } + + if obj.Op() == ir.OTYPE && !obj.Alias() { + if typ := obj.Type(); !typ.IsInterface() { + for _, method := range typ.Methods().Slice() { + l.exportBody(method.Nname.(*ir.Name), local) + } + } + } + } + + return w.Idx +} + +// exportBody exports the given function or method's body, if +// appropriate. local indicates whether it's a local function or +// method available on a locally declared type. (Due to cross-package +// type aliases, a method may be imported, but still available on a +// locally declared type.) +func (l *linker) exportBody(obj *ir.Name, local bool) { + assert(obj.Op() == ir.ONAME && obj.Class == ir.PFUNC) + + fn := obj.Func + if fn.Inl == nil { + return // not inlinable anyway + } + + // As a simple heuristic, if the function was declared in this + // package or we inlined it somewhere in this package, then we'll + // (re)export the function body. This isn't perfect, but seems + // reasonable in practice. In particular, it has the nice property + // that in the worst case, adding a blank import ensures the + // function body is available for inlining. + // + // TODO(mdempsky): Reimplement the reachable method crawling logic + // from typecheck/crawler.go. + exportBody := local || fn.Inl.Body != nil + if !exportBody { + return + } + + sym := obj.Sym() + if _, ok := l.bodies[sym]; ok { + // Due to type aliases, we might visit methods multiple times. + base.AssertfAt(obj.Type().Recv() != nil, obj.Pos(), "expected method: %v", obj) + return + } + + pri, ok := bodyReaderFor(fn) + assert(ok) + l.bodies[sym] = l.relocIdx(pri.pr, pkgbits.RelocBody, pri.idx) +} + +// relocCommon copies the specified element from pr into w, +// recursively relocating any referenced elements as well. +func (l *linker) relocCommon(pr *pkgReader, w *pkgbits.Encoder, k pkgbits.RelocKind, idx pkgbits.Index) { + r := pr.NewDecoderRaw(k, idx) + w.Relocs = l.relocAll(pr, r.Relocs) + io.Copy(&w.Data, &r.Data) + w.Flush() +} + +func (l *linker) pragmaFlag(w *pkgbits.Encoder, pragma ir.PragmaFlag) { + w.Sync(pkgbits.SyncPragma) + w.Int(int(pragma)) +} + +func (l *linker) relocFuncExt(w *pkgbits.Encoder, name *ir.Name) { + w.Sync(pkgbits.SyncFuncExt) + + l.pragmaFlag(w, name.Func.Pragma) + l.linkname(w, name) + + // Relocated extension data. + w.Bool(true) + + // Record definition ABI so cross-ABI calls can be direct. + // This is important for the performance of calling some + // common functions implemented in assembly (e.g., bytealg). + w.Uint64(uint64(name.Func.ABI)) + + // Escape analysis. + for _, fs := range &types.RecvsParams { + for _, f := range fs(name.Type()).FieldSlice() { + w.String(f.Note) + } + } + + if inl := name.Func.Inl; w.Bool(inl != nil) { + w.Len(int(inl.Cost)) + w.Bool(inl.CanDelayResults) + } + + w.Sync(pkgbits.SyncEOF) +} + +func (l *linker) relocTypeExt(w *pkgbits.Encoder, name *ir.Name) { + w.Sync(pkgbits.SyncTypeExt) + + typ := name.Type() + + l.pragmaFlag(w, name.Pragma()) + + // For type T, export the index of type descriptor symbols of T and *T. + l.lsymIdx(w, "", reflectdata.TypeLinksym(typ)) + l.lsymIdx(w, "", reflectdata.TypeLinksym(typ.PtrTo())) + + if typ.Kind() != types.TINTER { + for _, method := range typ.Methods().Slice() { + l.relocFuncExt(w, method.Nname.(*ir.Name)) + } + } +} + +func (l *linker) relocVarExt(w *pkgbits.Encoder, name *ir.Name) { + w.Sync(pkgbits.SyncVarExt) + l.linkname(w, name) +} + +func (l *linker) linkname(w *pkgbits.Encoder, name *ir.Name) { + w.Sync(pkgbits.SyncLinkname) + + linkname := name.Sym().Linkname + if !l.lsymIdx(w, linkname, name.Linksym()) { + w.String(linkname) + } +} + +func (l *linker) lsymIdx(w *pkgbits.Encoder, linkname string, lsym *obj.LSym) bool { + if lsym.PkgIdx > goobj.PkgIdxSelf || (lsym.PkgIdx == goobj.PkgIdxInvalid && !lsym.Indexed()) || linkname != "" { + w.Int64(-1) + return false + } + + // For a defined symbol, export its index. + // For re-exporting an imported symbol, pass its index through. + w.Int64(int64(lsym.SymIdx)) + return true +} |