From 9ae8f71c9431d287893443fa2b7fbdb72a9b56a2 Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Mon, 3 Aug 2020 13:19:46 -0400 Subject: [PATCH 1/9] [dev.link] cmd/link: stop renumbering files for pclntab generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates two new symbols: runtime.cutab, and runtime.filetab, and strips the filenames out of runtime.pclntab_old. All stats are for cmd/compile. Time: Pclntab_GC 48.2ms ± 3% 45.5ms ± 9% -5.47% (p=0.004 n=9+9) Alloc/op: Pclntab_GC 30.0MB ± 0% 29.5MB ± 0% -1.88% (p=0.000 n=10+10) Allocs/op: Pclntab_GC 90.4k ± 0% 73.1k ± 0% -19.11% (p=0.000 n=10+10) live-B: Pclntab_GC 29.1M ± 0% 29.2M ± 0% +0.10% (p=0.000 n=10+10) binary sizes: NEW: 18565600 OLD: 18532768 The size differences in the binary are caused by the increased size of the Func objects, and (less likely) some extra alignment padding needed as a result. This is probably the maximum increase in size we'll size from the pclntab reworking. Change-Id: Idd95a9b159fea46f7701cfe6506813b88257fbea Reviewed-on: https://go-review.googlesource.com/c/go/+/246497 Run-TryBot: Jeremy Faller TryBot-Result: Gobot Gobot Reviewed-by: Than McIntosh Reviewed-by: Austin Clements --- src/cmd/link/internal/ld/data.go | 4 + src/cmd/link/internal/ld/link.go | 1 - src/cmd/link/internal/ld/pcln.go | 303 ++++++++++++++++------------- src/cmd/link/internal/ld/symtab.go | 12 +- src/debug/gosym/pclntab.go | 52 +++-- src/runtime/runtime2.go | 7 +- src/runtime/symtab.go | 19 +- 7 files changed, 231 insertions(+), 167 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index dc7096ea8c..a551d46403 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1923,6 +1923,8 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pcheader", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.funcnametab", 0), sect) + ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.cutab", 0), sect) + ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab_old", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect) if ctxt.HeadType == objabi.Haix { @@ -2507,6 +2509,8 @@ func (ctxt *Link) address() []*sym.Segment { ctxt.xdefine("runtime.pclntab", sym.SRODATA, int64(pclntab.Vaddr)) ctxt.defineInternal("runtime.pcheader", sym.SRODATA) ctxt.defineInternal("runtime.funcnametab", sym.SRODATA) + ctxt.defineInternal("runtime.cutab", sym.SRODATA) + ctxt.defineInternal("runtime.filetab", sym.SRODATA) ctxt.defineInternal("runtime.pclntab_old", sym.SRODATA) ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length)) ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr)) diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index a2c8552e94..f26d051a49 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -71,7 +71,6 @@ type Link struct { LibraryByPkg map[string]*sym.Library Shlibs []Shlib Textp []loader.Sym - NumFilesyms int Moduledata loader.Sym PackageFile map[string]string diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index 30e0bdc839..c7535f6a61 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -6,16 +6,11 @@ package ld import ( "cmd/internal/goobj" - "cmd/internal/obj" "cmd/internal/objabi" - "cmd/internal/src" "cmd/internal/sys" "cmd/link/internal/loader" "cmd/link/internal/sym" - "encoding/binary" "fmt" - "log" - "math" "os" "path/filepath" "strings" @@ -23,18 +18,13 @@ import ( // oldPclnState holds state information used during pclntab generation. Here // 'ldr' is just a pointer to the context's loader, 'deferReturnSym' is the -// index for the symbol "runtime.deferreturn", 'nameToOffset' is a helper -// function for capturing function names, 'numberedFiles' records the file -// number assigned to a given file symbol, 'filepaths' is a slice of expanded -// paths (indexed by file number). +// index for the symbol "runtime.deferreturn", // // NB: This is deprecated, and will be eliminated when pclntab_old is // eliminated. type oldPclnState struct { ldr *loader.Loader deferReturnSym loader.Sym - numberedFiles map[string]int64 - filepaths []string } // pclntab holds the state needed for pclntab generation. @@ -42,9 +32,6 @@ type pclntab struct { // The first and last functions found. firstFunc, lastFunc loader.Sym - // The offset to the filetab. - filetabOffset int32 - // Running total size of pclntab. size int64 @@ -54,6 +41,8 @@ type pclntab struct { pcheader loader.Sym funcnametab loader.Sym findfunctab loader.Sym + cutab loader.Sym + filetab loader.Sym // The number of functions + number of TEXT sections - 1. This is such an // unexpected value because platforms that have more than one TEXT section @@ -64,6 +53,9 @@ type pclntab struct { // On most platforms this is the number of reachable functions. nfunc int32 + // The number of filenames in runtime.filetab. + nfiles uint32 + // maps the function symbol to offset in runtime.funcnametab // This doesn't need to reside in the state once pclntab_old's been // deleted -- it can live in generateFuncnametab. @@ -89,11 +81,6 @@ func makeOldPclnState(ctxt *Link) *oldPclnState { state := &oldPclnState{ ldr: ldr, deferReturnSym: drs, - numberedFiles: make(map[string]int64), - // NB: initial entry in filepaths below is to reserve the zero value, - // so that when we do a map lookup in numberedFiles fails, it will not - // return a value slot in filepaths. - filepaths: []string{""}, } return state @@ -153,78 +140,6 @@ func ftabaddstring(ftab *loader.SymbolBuilder, s string) int32 { return int32(start) } -// numberfile assigns a file number to the file if it hasn't been assigned already. -// This funciton looks at a CU's file at index [i], and if it's a new filename, -// stores that filename in the global file table, and adds it to the map lookup -// for renumbering pcfile. -func (state *oldPclnState) numberfile(cu *sym.CompilationUnit, i goobj.CUFileIndex) int64 { - file := cu.FileTable[i] - if val, ok := state.numberedFiles[file]; ok { - return val - } - path := file - if strings.HasPrefix(path, src.FileSymPrefix) { - path = file[len(src.FileSymPrefix):] - } - val := int64(len(state.filepaths)) - state.numberedFiles[file] = val - state.filepaths = append(state.filepaths, expandGoroot(path)) - return val -} - -func (state *oldPclnState) fileVal(cu *sym.CompilationUnit, i int32) int64 { - file := cu.FileTable[i] - if val, ok := state.numberedFiles[file]; ok { - return val - } - panic("should have been numbered first") -} - -func (state *oldPclnState) renumberfiles(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, d *sym.Pcdata) { - // Give files numbers. - nf := fi.NumFile() - for i := uint32(0); i < nf; i++ { - state.numberfile(cu, fi.File(int(i))) - } - - buf := make([]byte, binary.MaxVarintLen32) - newval := int32(-1) - var out sym.Pcdata - it := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) - for it.Init(d.P); !it.Done; it.Next() { - // value delta - oldval := it.Value - - var val int32 - if oldval == -1 { - val = -1 - } else { - if oldval < 0 || oldval >= int32(len(cu.FileTable)) { - log.Fatalf("bad pcdata %d", oldval) - } - val = int32(state.fileVal(cu, oldval)) - } - - dv := val - newval - newval = val - - // value - n := binary.PutVarint(buf, int64(dv)) - out.P = append(out.P, buf[:n]...) - - // pc delta - pc := (it.NextPC - it.PC) / it.PCScale - n = binary.PutUvarint(buf, uint64(pc)) - out.P = append(out.P, buf[:n]...) - } - - // terminating value delta - // we want to write varint-encoded 0, which is just 0 - out.P = append(out.P, 0) - - *d = out -} - // onlycsymbol looks at a symbol's name to report whether this is a // symbol that is referenced by C code func onlycsymbol(sname string) bool { @@ -308,12 +223,7 @@ func (state *oldPclnState) genInlTreeSym(cu *sym.CompilationUnit, fi loader.Func ninl := fi.NumInlTree() for i := 0; i < int(ninl); i++ { call := fi.InlTree(i) - // Usually, call.File is already numbered since the file - // shows up in the Pcfile table. However, two inlined calls - // might overlap exactly so that only the innermost file - // appears in the Pcfile table. In that case, this assigns - // the outer file a number. - val := state.numberfile(cu, call.File) + val := call.File nameoff, ok := newState.funcNameOffset[call.Func] if !ok { panic("couldn't find function name offset") @@ -359,11 +269,14 @@ func (state *pclntab) generatePCHeader(ctxt *Link) { header.SetUint8(ctxt.Arch, 6, uint8(ctxt.Arch.MinLC)) header.SetUint8(ctxt.Arch, 7, uint8(ctxt.Arch.PtrSize)) off := header.SetUint(ctxt.Arch, 8, uint64(state.nfunc)) + off = header.SetUint(ctxt.Arch, off, uint64(state.nfiles)) off = writeSymOffset(off, state.funcnametab) + off = writeSymOffset(off, state.cutab) + off = writeSymOffset(off, state.filetab) off = writeSymOffset(off, state.pclntab) } - size := int64(8 + 3*ctxt.Arch.PtrSize) + size := int64(8 + 6*ctxt.Arch.PtrSize) state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader) } @@ -417,6 +330,139 @@ func (state *pclntab) generateFuncnametab(ctxt *Link, container loader.Bitmap) { state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab) } +// walkFilenames walks the filenames in the all reachable functions. +func walkFilenames(ctxt *Link, container loader.Bitmap, f func(*sym.CompilationUnit, goobj.CUFileIndex)) { + ldr := ctxt.loader + + // Loop through all functions, finding the filenames we need. + for _, ls := range ctxt.Textp { + s := loader.Sym(ls) + if !emitPcln(ctxt, s, container) { + continue + } + + fi := ldr.FuncInfo(s) + if !fi.Valid() { + continue + } + fi.Preload() + + cu := ldr.SymUnit(s) + for i, nf := 0, int(fi.NumFile()); i < nf; i++ { + f(cu, fi.File(i)) + } + for i, ninl := 0, int(fi.NumInlTree()); i < ninl; i++ { + call := fi.InlTree(i) + f(cu, call.File) + } + } +} + +// generateFilenameTabs creates LUTs needed for filename lookup. Returns a slice +// of the index at which each CU begins in runtime.cutab. +// +// Function objects keep track of the files they reference to print the stack. +// This function creates a per-CU list of filenames if CU[M] references +// files[1-N], the following is generated: +// +// runtime.cutab: +// CU[M] +// offsetToFilename[0] +// offsetToFilename[1] +// .. +// +// runtime.filetab +// filename[0] +// filename[1] +// +// Looking up a filename then becomes: +// 0) Given a func, and filename index [K] +// 1) Get Func.CUIndex: M := func.cuOffset +// 2) Find filename offset: fileOffset := runtime.cutab[M+K] +// 3) Get the filename: getcstring(runtime.filetab[fileOffset]) +func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, container loader.Bitmap) []uint32 { + // On a per-CU basis, keep track of all the filenames we need. + // + // Note, that we store the filenames in a separate section in the object + // files, and deduplicate based on the actual value. It would be better to + // store the filenames as symbols, using content addressable symbols (and + // then not loading extra filenames), and just use the hash value of the + // symbol name to do this cataloging. + // + // TOOD: Store filenames as symbols. (Note this would be easiest if you + // also move strings to ALWAYS using the larger content addressable hash + // function, and use that hash value for uniqueness testing.) + cuEntries := make([]goobj.CUFileIndex, len(compUnits)) + fileOffsets := make(map[string]uint32) + + // Walk the filenames. + // We store the total filename string length we need to load, and the max + // file index we've seen per CU so we can calculate how large the + // CU->global table needs to be. + var fileSize int64 + walkFilenames(ctxt, container, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) { + // Note we use the raw filename for lookup, but use the expanded filename + // when we save the size. + filename := cu.FileTable[i] + if _, ok := fileOffsets[filename]; !ok { + fileOffsets[filename] = uint32(fileSize) + fileSize += int64(len(expandFile(filename)) + 1) // NULL terminate + } + + // Find the maximum file index we've seen. + if cuEntries[cu.PclnIndex] < i+1 { + cuEntries[cu.PclnIndex] = i + 1 // Store max + 1 + } + }) + + // Calculate the size of the runtime.cutab variable. + var totalEntries uint32 + cuOffsets := make([]uint32, len(cuEntries)) + for i, entries := range cuEntries { + // Note, cutab is a slice of uint32, so an offset to a cu's entry is just the + // running total of all cu indices we've needed to store so far, not the + // number of bytes we've stored so far. + cuOffsets[i] = totalEntries + totalEntries += uint32(entries) + } + + // Write cutab. + writeCutab := func(ctxt *Link, s loader.Sym) { + sb := ctxt.loader.MakeSymbolUpdater(s) + + var off int64 + for i, max := range cuEntries { + // Write the per CU LUT. + cu := compUnits[i] + for j := goobj.CUFileIndex(0); j < max; j++ { + fileOffset, ok := fileOffsets[cu.FileTable[j]] + if !ok { + // We're looping through all possible file indices. It's possible a file's + // been deadcode eliminated, and although it's a valid file in the CU, it's + // not needed in this binary. When that happens, use an invalid offset. + fileOffset = ^uint32(0) + } + off = sb.SetUint32(ctxt.Arch, off, fileOffset) + } + } + } + state.cutab = state.addGeneratedSym(ctxt, "runtime.cutab", int64(totalEntries*4), writeCutab) + + // Write filetab. + writeFiletab := func(ctxt *Link, s loader.Sym) { + sb := ctxt.loader.MakeSymbolUpdater(s) + + // Write the strings. + for filename, loc := range fileOffsets { + sb.AddStringAt(int64(loc), expandFile(filename)) + } + } + state.nfiles = uint32(len(fileOffsets)) + state.filetab = state.addGeneratedSym(ctxt, "runtime.filetab", fileSize, writeFiletab) + + return cuOffsets +} + // pclntab initializes the pclntab symbol with // runtime function and file name information. @@ -425,7 +471,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // Go 1.2's symtab layout is documented in golang.org/s/go12symtab, but the // layout and data has changed since that time. // - // As of July 2020, here's the layout of pclntab: + // As of August 2020, here's the layout of pclntab: // // .gopclntab/__gopclntab [elf/macho section] // runtime.pclntab @@ -438,17 +484,23 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // offset to runtime.pclntab_old from beginning of runtime.pcheader // // runtime.funcnametab - // []list of null terminated function names + // []list of null terminated function names + // + // runtime.cutab + // for i=0..#CUs + // for j=0..#max used file index in CU[i] + // uint32 offset into runtime.filetab for the filename[j] + // + // runtime.filetab + // []null terminated filename strings // // runtime.pclntab_old // function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes] // end PC [thearch.ptrsize bytes] - // offset to file table [4 bytes] // func structures, pcdata tables. - // filetable oldState := makeOldPclnState(ctxt) - state, _ := makePclntab(ctxt, container) + state, compUnits := makePclntab(ctxt, container) ldr := ctxt.loader state.carrier = ldr.LookupOrCreateSym("runtime.pclntab", 0) @@ -461,6 +513,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { state.pclntab = ldr.LookupOrCreateSym("runtime.pclntab_old", 0) state.generatePCHeader(ctxt) state.generateFuncnametab(ctxt, container) + cuOffsets := state.generateFilenameTabs(ctxt, compUnits, container) funcdataBytes := int64(0) ldr.SetCarrierSym(state.pclntab, state.carrier) @@ -583,7 +636,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // fixed size of struct, checked below off := funcstart - end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 5*4 + int32(len(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize) + end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + int32(len(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize) if len(funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) { end += 4 } @@ -616,17 +669,6 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { pcsp = sym.Pcdata{P: fi.Pcsp()} pcfile = sym.Pcdata{P: fi.Pcfile()} pcline = sym.Pcdata{P: fi.Pcline()} - oldState.renumberfiles(ctxt, cu, fi, &pcfile) - if false { - // Sanity check the new numbering - it := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) - for it.Init(pcfile.P); !it.Done; it.Next() { - if it.Value < 1 || it.Value > int32(len(oldState.numberedFiles)) { - ctxt.Errorf(s, "bad file number in pcfile: %d not in range [1, %d]\n", it.Value, len(oldState.numberedFiles)) - errorexit() - } - } - } } if fi.Valid() && fi.NumInlTree() > 0 { @@ -641,15 +683,12 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { off = writepctab(off, pcline.P) off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(len(pcdata)))) - // Store the compilation unit index. - cuIdx := ^uint16(0) + // Store the offset to compilation unit's file table. + cuIdx := ^uint32(0) if cu := ldr.SymUnit(s); cu != nil { - if cu.PclnIndex > math.MaxUint16 { - panic("cu limit reached.") - } - cuIdx = uint16(cu.PclnIndex) + cuIdx = cuOffsets[cu.PclnIndex] } - off = int32(ftab.SetUint16(ctxt.Arch, int64(off), cuIdx)) + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), cuIdx)) // funcID uint8 var funcID objabi.FuncID @@ -658,6 +697,8 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { } off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(funcID))) + off += 2 // pad + // nfuncdata must be the final entry. off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(len(funcdata)))) for i := range pcdata { @@ -694,26 +735,8 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // Final entry of table is just end pc. setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), state.lastFunc, ldr.SymSize(state.lastFunc)) - // Start file table. - dSize := len(ftab.Data()) - start := int32(dSize) - start += int32(-dSize) & (int32(ctxt.Arch.PtrSize) - 1) - state.filetabOffset = start - ftab.SetUint32(ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint32(start)) - - nf := len(oldState.numberedFiles) - ftab.Grow(int64(start) + int64((nf+1)*4)) - ftab.SetUint32(ctxt.Arch, int64(start), uint32(nf+1)) - for i := nf; i > 0; i-- { - path := oldState.filepaths[i] - val := int64(i) - ftab.SetUint32(ctxt.Arch, int64(start)+val*4, uint32(ftabaddstring(ftab, path))) - } - ftab.SetSize(int64(len(ftab.Data()))) - ctxt.NumFilesyms = len(oldState.numberedFiles) - if ctxt.Debugvlog != 0 { ctxt.Logf("pclntab=%d bytes, funcdata total %d bytes\n", ftab.Size(), funcdataBytes) } diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index bc880955b8..d05b98f04a 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -619,6 +619,14 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { moduledata.AddAddr(ctxt.Arch, pcln.funcnametab) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.funcnametab))) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.funcnametab))) + // The cutab slice + moduledata.AddAddr(ctxt.Arch, pcln.cutab) + moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.cutab))) + moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.cutab))) + // The filetab slice + moduledata.AddAddr(ctxt.Arch, pcln.filetab) + moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab))) + moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab))) // The pclntab slice moduledata.AddAddr(ctxt.Arch, pcln.pclntab) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pclntab))) @@ -627,10 +635,6 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { moduledata.AddAddr(ctxt.Arch, pcln.pclntab) moduledata.AddUint(ctxt.Arch, uint64(pcln.nfunc+1)) moduledata.AddUint(ctxt.Arch, uint64(pcln.nfunc+1)) - // The filetab slice - moduledata.AddAddrPlus(ctxt.Arch, pcln.pclntab, int64(pcln.filetabOffset)) - moduledata.AddUint(ctxt.Arch, uint64(ctxt.NumFilesyms)+1) - moduledata.AddUint(ctxt.Arch, uint64(ctxt.NumFilesyms)+1) // findfunctab moduledata.AddAddr(ctxt.Arch, pcln.findfunctab) // minpc, maxpc diff --git a/src/debug/gosym/pclntab.go b/src/debug/gosym/pclntab.go index e5c50520fc..e383ea460a 100644 --- a/src/debug/gosym/pclntab.go +++ b/src/debug/gosym/pclntab.go @@ -53,6 +53,7 @@ type LineTable struct { quantum uint32 ptrsize uint32 funcnametab []byte + cutab []byte funcdata []byte functab []byte nfunctab uint32 @@ -223,17 +224,18 @@ func (t *LineTable) parsePclnTab() { switch possibleVersion { case ver116: t.nfunctab = uint32(t.uintptr(t.Data[8:])) - offset := t.uintptr(t.Data[8+t.ptrsize:]) + t.nfiletab = uint32(t.uintptr(t.Data[8+t.ptrsize:])) + offset := t.uintptr(t.Data[8+2*t.ptrsize:]) t.funcnametab = t.Data[offset:] - offset = t.uintptr(t.Data[8+2*t.ptrsize:]) + offset = t.uintptr(t.Data[8+3*t.ptrsize:]) + t.cutab = t.Data[offset:] + offset = t.uintptr(t.Data[8+4*t.ptrsize:]) + t.filetab = t.Data[offset:] + offset = t.uintptr(t.Data[8+5*t.ptrsize:]) t.funcdata = t.Data[offset:] t.functab = t.Data[offset:] functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize - fileoff := t.binary.Uint32(t.functab[functabsize:]) - t.filetab = t.functab[fileoff:] t.functab = t.functab[:functabsize] - t.nfiletab = t.binary.Uint32(t.filetab) - t.filetab = t.filetab[:t.nfiletab*4] case ver12: t.nfunctab = uint32(t.uintptr(t.Data[8:])) t.funcdata = t.Data @@ -330,17 +332,22 @@ func (t *LineTable) funcName(off uint32) string { return s } -// string returns a Go string found at off. -func (t *LineTable) string(off uint32) string { +// stringFrom returns a Go string found at off from a position. +func (t *LineTable) stringFrom(arr []byte, off uint32) string { if s, ok := t.strings[off]; ok { return s } - i := bytes.IndexByte(t.funcdata[off:], 0) - s := string(t.funcdata[off : off+uint32(i)]) + i := bytes.IndexByte(arr[off:], 0) + s := string(arr[off : off+uint32(i)]) t.strings[off] = s return s } +// string returns a Go string found at off. +func (t *LineTable) string(off uint32) string { + return t.stringFrom(t.funcdata, off) +} + // step advances to the next pc, value pair in the encoded table. func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool { uvdelta := t.readvarint(p) @@ -453,7 +460,15 @@ func (t *LineTable) go12PCToFile(pc uint64) (file string) { if fno <= 0 { return "" } - return t.string(t.binary.Uint32(t.filetab[4*fno:])) + if t.version == ver12 { + return t.string(t.binary.Uint32(t.filetab[4*fno:])) + } + // Go ≥ 1.16 + cuoff := t.binary.Uint32(f[t.ptrsize+7*4:]) + if fnoff := t.binary.Uint32(t.cutab[(cuoff+uint32(fno))*4:]); fnoff != ^uint32(0) { + return t.stringFrom(t.filetab, fnoff) + } + return "" } // go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2 pcln table. @@ -496,9 +511,18 @@ func (t *LineTable) initFileMap() { } m := make(map[string]uint32) - for i := uint32(1); i < t.nfiletab; i++ { - s := t.string(t.binary.Uint32(t.filetab[4*i:])) - m[s] = i + if t.version == ver12 { + for i := uint32(1); i < t.nfiletab; i++ { + s := t.string(t.binary.Uint32(t.filetab[4*i:])) + m[s] = i + } + } else { + var pos uint32 + for i := uint32(1); i < t.nfiletab; i++ { + s := t.stringFrom(t.filetab, pos) + pos += uint32(len(s) + 1) + m[s] = i + } } t.fileMap = m } diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 0bddcaa789..5a79c7e6ec 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -804,9 +804,10 @@ type _func struct { pcfile int32 pcln int32 npcdata int32 - cuIndex uint16 // TODO(jfaller): 16 bits is never enough, make this larger. - funcID funcID // set for certain special runtime functions - nfuncdata uint8 // must be last + cuOffset uint32 // runtime.cutab offset of this function's CU + funcID funcID // set for certain special runtime functions + _ [2]byte // pad + nfuncdata uint8 // must be last } // Pseudo-Func that is returned for PCs that occur in inlined code. diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index ddb5ea82b4..fbd9315522 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -334,14 +334,17 @@ const ( funcID_wrapper // any autogenerated code (hash/eq algorithms, method wrappers, etc.) ) -// PCHeader holds data used by the pclntab lookups. +// pcHeader holds data used by the pclntab lookups. type pcHeader struct { magic uint32 // 0xFFFFFFFA pad1, pad2 uint8 // 0,0 minLC uint8 // min instruction size ptrSize uint8 // size of a ptr in bytes nfunc int // number of functions in the module + nfiles uint // number of entries in the file tab. funcnameOffset uintptr // offset to the funcnametab variable from pcHeader + cuOffset uintptr // offset to the cutab variable from pcHeader + filetabOffset uintptr // offset to the filetab variable from pcHeader pclnOffset uintptr // offset to the pclntab variable from pcHeader } @@ -353,9 +356,10 @@ type pcHeader struct { type moduledata struct { pcHeader *pcHeader funcnametab []byte + cutab []uint32 + filetab []byte pclntable []byte ftab []functab - filetab []uint32 findfunctab uintptr minpc, maxpc uintptr @@ -851,7 +855,12 @@ func funcfile(f funcInfo, fileno int32) string { if !f.valid() { return "?" } - return gostringnocopy(&datap.pclntable[datap.filetab[fileno]]) + // Make sure the cu index and file offset are valid + if fileoff := datap.cutab[f.cuOffset+uint32(fileno)]; fileoff != ^uint32(0) { + return gostringnocopy(&datap.filetab[fileoff]) + } + // pcln section is corrupt. + return "?" } func funcline1(f funcInfo, targetpc uintptr, strict bool) (file string, line int32) { @@ -865,7 +874,7 @@ func funcline1(f funcInfo, targetpc uintptr, strict bool) (file string, line int // print("looking for ", hex(targetpc), " in ", funcname(f), " got file=", fileno, " line=", lineno, "\n") return "?", 0 } - file = gostringnocopy(&datap.pclntable[datap.filetab[fileno]]) + file = funcfile(f, fileno) return } @@ -1005,7 +1014,7 @@ type inlinedCall struct { parent int16 // index of parent in the inltree, or < 0 funcID funcID // type of the called function _ byte - file int32 // fileno index into filetab + file int32 // perCU file index for inlined call. See cmd/link:pcln.go line int32 // line number of the call site func_ int32 // offset into pclntab for name of called function parentPc int32 // position of an instruction whose source position is the call site (offset from entry) From b249703e3c53cd7f1e5f808fb2f03714fec44b43 Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Wed, 12 Aug 2020 12:54:03 -0400 Subject: [PATCH 2/9] [dev.link] cmd/compile, cmd/asm: add length to hashed symbols While working on deduplicating pcdata, I found that the following hashed symbols would result in the same: [] == [0,0,0,0....] This makes using content addressable symbols untenable for pcdata. Adding the length to the hash keeps the dream alive. No difference in binary size (darwin, cmd/compile), spurious improvements in DWARF phase memory. Change-Id: I21101f7754a3d870922b0dea39c947cc8509432f Reviewed-on: https://go-review.googlesource.com/c/go/+/247903 Run-TryBot: Jeremy Faller TryBot-Result: Gobot Gobot Reviewed-by: Than McIntosh Reviewed-by: Austin Clements Reviewed-by: Cherry Zhang --- src/cmd/internal/obj/objfile.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index 7bc4f4992e..8234697d72 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -372,10 +372,22 @@ func contentHash64(s *LSym) goobj.Hash64Type { // hashed symbols. func (w *writer) contentHash(s *LSym) goobj.HashType { h := sha1.New() + var tmp [14]byte + + // Include the size of the symbol in the hash. + // This preserves the length of symbols, preventing the following two symbols + // from hashing the same: + // + // [2]int{1,2} ≠ [10]int{1,2,0,0,0...} + // + // In this case, if the smaller symbol is alive, the larger is not kept unless + // needed. + binary.LittleEndian.PutUint64(tmp[:8], uint64(s.Size)) + h.Write(tmp[:8]) + // The compiler trims trailing zeros _sometimes_. We just do // it always. h.Write(bytes.TrimRight(s.P, "\x00")) - var tmp [14]byte for i := range s.R { r := &s.R[i] binary.LittleEndian.PutUint32(tmp[:4], uint32(r.Off)) From 954db9fe51154e5d4663c0c1a62c82a99eef1ed4 Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Wed, 12 Aug 2020 16:37:42 -0400 Subject: [PATCH 3/9] [dev.link] debug/gosym: fix file mappings CL 246497 introduced bugs in gosym that the long tests caught. These two bugs were: 1) In 1.16, 0 is now a valid file number from pcfile tables. 2) Also, in 1.16, when we scan all functions looking for a pc/file pair, the values returned from pcfile are no longer the direct offset into the file table. Rather, the values from pcfile are the offset into the cu->file look-up table. This CL fixes those two issues. Change-Id: I0cd280bdcaeda89faaf9fac41809abdb87734499 Reviewed-on: https://go-review.googlesource.com/c/go/+/248317 Reviewed-by: Cherry Zhang Reviewed-by: Austin Clements Reviewed-by: Than McIntosh --- src/debug/gosym/pclntab.go | 41 ++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/debug/gosym/pclntab.go b/src/debug/gosym/pclntab.go index e383ea460a..21edddda20 100644 --- a/src/debug/gosym/pclntab.go +++ b/src/debug/gosym/pclntab.go @@ -59,9 +59,12 @@ type LineTable struct { nfunctab uint32 filetab []byte nfiletab uint32 - fileMap map[string]uint32 funcNames map[uint32]string // cache the function names strings map[uint32]string // interned substrings of Data, keyed by offset + // fileMap varies depending on the version of the object file. + // For ver12, it maps the name to the index in the file table. + // For ver116, it maps the name to the offset in filetab. + fileMap map[string]uint32 } // NOTE(rsc): This is wrong for GOARCH=arm, which uses a quantum of 4, @@ -388,7 +391,7 @@ func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 { // to file number. Since most functions come from a single file, these // are usually short and quick to scan. If a file match is found, then the // code goes to the expense of looking for a simultaneous line number match. -func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32) uint64 { +func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32, cutab []byte) uint64 { if filetab == 0 || linetab == 0 { return 0 } @@ -401,8 +404,12 @@ func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, linePC := entry fileStartPC := filePC for t.step(&fp, &filePC, &fileVal, filePC == entry) { - if fileVal == filenum && fileStartPC < filePC { - // fileVal is in effect starting at fileStartPC up to + fileIndex := fileVal + if t.version == ver116 { + fileIndex = int32(t.binary.Uint32(cutab[fileVal*4:])) + } + if fileIndex == filenum && fileStartPC < filePC { + // fileIndex is in effect starting at fileStartPC up to // but not including filePC, and it's the file we want. // Run the PC table looking for a matching line number // or until we reach filePC. @@ -457,13 +464,16 @@ func (t *LineTable) go12PCToFile(pc uint64) (file string) { entry := t.uintptr(f) filetab := t.binary.Uint32(f[t.ptrsize+4*4:]) fno := t.pcvalue(filetab, entry, pc) - if fno <= 0 { - return "" - } if t.version == ver12 { + if fno <= 0 { + return "" + } return t.string(t.binary.Uint32(t.filetab[4*fno:])) } // Go ≥ 1.16 + if fno < 0 { // 0 is valid for ≥ 1.16 + return "" + } cuoff := t.binary.Uint32(f[t.ptrsize+7*4:]) if fnoff := t.binary.Uint32(t.cutab[(cuoff+uint32(fno))*4:]); fnoff != ^uint32(0) { return t.stringFrom(t.filetab, fnoff) @@ -471,7 +481,7 @@ func (t *LineTable) go12PCToFile(pc uint64) (file string) { return "" } -// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2 pcln table. +// go12LineToPC maps a (file, line) pair to a program counter for the Go 1.2/1.16 pcln table. func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) { defer func() { if recover() != nil { @@ -480,20 +490,25 @@ func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) { }() t.initFileMap() - filenum := t.fileMap[file] - if filenum == 0 { + filenum, ok := t.fileMap[file] + if !ok { return 0 } // Scan all functions. // If this turns out to be a bottleneck, we could build a map[int32][]int32 // mapping file number to a list of functions with code from that file. + var cutab []byte for i := uint32(0); i < t.nfunctab; i++ { f := t.funcdata[t.uintptr(t.functab[2*t.ptrsize*i+t.ptrsize:]):] entry := t.uintptr(f) filetab := t.binary.Uint32(f[t.ptrsize+4*4:]) linetab := t.binary.Uint32(f[t.ptrsize+5*4:]) - pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line)) + if t.version == ver116 { + cuoff := t.binary.Uint32(f[t.ptrsize+7*4:]) * 4 + cutab = t.cutab[cuoff:] + } + pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line), cutab) if pc != 0 { return pc } @@ -518,10 +533,10 @@ func (t *LineTable) initFileMap() { } } else { var pos uint32 - for i := uint32(1); i < t.nfiletab; i++ { + for i := uint32(0); i < t.nfiletab; i++ { s := t.stringFrom(t.filetab, pos) + m[s] = pos pos += uint32(len(s) + 1) - m[s] = i } } t.fileMap = m From 5387cdcb24a07f5d0d49d5105ced2b69e6aafde9 Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Fri, 7 Aug 2020 11:31:20 -0400 Subject: [PATCH 4/9] [dev.link] cmd/link, cmd/compile: create content addressable pcdata syms Switch pcdata over to content addressable symbols. This is the last step before removing these from pclntab_old. No meaningful benchmarks changes come from this work. Change-Id: I3f74f3d6026a278babe437c8010e22992c92bd89 Reviewed-on: https://go-review.googlesource.com/c/go/+/247399 Reviewed-by: Austin Clements Reviewed-by: Than McIntosh --- src/cmd/internal/goobj/funcinfo.go | 82 ++++++++++++++------------ src/cmd/internal/goobj/objfile.go | 12 ++-- src/cmd/internal/obj/link.go | 15 ++--- src/cmd/internal/obj/objfile.go | 69 +++++++++++++++++----- src/cmd/internal/obj/pcln.go | 55 ++++++++++------- src/cmd/internal/objfile/goobj.go | 16 +++-- src/cmd/link/internal/ld/dwarf.go | 2 +- src/cmd/link/internal/ld/lib.go | 2 +- src/cmd/link/internal/ld/pcln.go | 13 ++-- src/cmd/link/internal/loader/loader.go | 46 +++++++-------- 10 files changed, 181 insertions(+), 131 deletions(-) diff --git a/src/cmd/internal/goobj/funcinfo.go b/src/cmd/internal/goobj/funcinfo.go index e0e6068b4b..2cca8f6c4e 100644 --- a/src/cmd/internal/goobj/funcinfo.go +++ b/src/cmd/internal/goobj/funcinfo.go @@ -23,12 +23,11 @@ type FuncInfo struct { Locals uint32 FuncID objabi.FuncID - Pcsp uint32 - Pcfile uint32 - Pcline uint32 - Pcinline uint32 - Pcdata []uint32 - PcdataEnd uint32 + Pcsp SymRef + Pcfile SymRef + Pcline SymRef + Pcinline SymRef + Pcdata []SymRef Funcdataoff []uint32 File []CUFileIndex @@ -41,20 +40,24 @@ func (a *FuncInfo) Write(w *bytes.Buffer) { binary.LittleEndian.PutUint32(b[:], x) w.Write(b[:]) } + writeSymRef := func(s SymRef) { + writeUint32(s.PkgIdx) + writeUint32(s.SymIdx) + } writeUint32(a.Args) writeUint32(a.Locals) writeUint32(uint32(a.FuncID)) - writeUint32(a.Pcsp) - writeUint32(a.Pcfile) - writeUint32(a.Pcline) - writeUint32(a.Pcinline) + writeSymRef(a.Pcsp) + writeSymRef(a.Pcfile) + writeSymRef(a.Pcline) + writeSymRef(a.Pcinline) writeUint32(uint32(len(a.Pcdata))) - for _, x := range a.Pcdata { - writeUint32(x) + for _, sym := range a.Pcdata { + writeSymRef(sym) } - writeUint32(a.PcdataEnd) + writeUint32(uint32(len(a.Funcdataoff))) for _, x := range a.Funcdataoff { writeUint32(x) @@ -75,21 +78,23 @@ func (a *FuncInfo) Read(b []byte) { b = b[4:] return x } + readSymIdx := func() SymRef { + return SymRef{readUint32(), readUint32()} + } a.Args = readUint32() a.Locals = readUint32() a.FuncID = objabi.FuncID(readUint32()) - a.Pcsp = readUint32() - a.Pcfile = readUint32() - a.Pcline = readUint32() - a.Pcinline = readUint32() - pcdatalen := readUint32() - a.Pcdata = make([]uint32, pcdatalen) + a.Pcsp = readSymIdx() + a.Pcfile = readSymIdx() + a.Pcline = readSymIdx() + a.Pcinline = readSymIdx() + a.Pcdata = make([]SymRef, readUint32()) for i := range a.Pcdata { - a.Pcdata[i] = readUint32() + a.Pcdata[i] = readSymIdx() } - a.PcdataEnd = readUint32() + funcdataofflen := readUint32() a.Funcdataoff = make([]uint32, funcdataofflen) for i := range a.Funcdataoff { @@ -127,11 +132,13 @@ type FuncInfoLengths struct { func (*FuncInfo) ReadFuncInfoLengths(b []byte) FuncInfoLengths { var result FuncInfoLengths - const numpcdataOff = 28 + // Offset to the number of pcdata values. This value is determined by counting + // the number of bytes until we write pcdata to the file. + const numpcdataOff = 44 result.NumPcdata = binary.LittleEndian.Uint32(b[numpcdataOff:]) result.PcdataOff = numpcdataOff + 4 - numfuncdataoffOff := result.PcdataOff + 4*(result.NumPcdata+1) + numfuncdataoffOff := result.PcdataOff + 8*result.NumPcdata result.NumFuncdataoff = binary.LittleEndian.Uint32(b[numfuncdataoffOff:]) result.FuncdataoffOff = numfuncdataoffOff + 4 @@ -154,29 +161,28 @@ func (*FuncInfo) ReadLocals(b []byte) uint32 { return binary.LittleEndian.Uint32 func (*FuncInfo) ReadFuncID(b []byte) uint32 { return binary.LittleEndian.Uint32(b[8:]) } -// return start and end offsets. -func (*FuncInfo) ReadPcsp(b []byte) (uint32, uint32) { - return binary.LittleEndian.Uint32(b[12:]), binary.LittleEndian.Uint32(b[16:]) +func (*FuncInfo) ReadPcsp(b []byte) SymRef { + return SymRef{binary.LittleEndian.Uint32(b[12:]), binary.LittleEndian.Uint32(b[16:])} } -// return start and end offsets. -func (*FuncInfo) ReadPcfile(b []byte) (uint32, uint32) { - return binary.LittleEndian.Uint32(b[16:]), binary.LittleEndian.Uint32(b[20:]) +func (*FuncInfo) ReadPcfile(b []byte) SymRef { + return SymRef{binary.LittleEndian.Uint32(b[20:]), binary.LittleEndian.Uint32(b[24:])} } -// return start and end offsets. -func (*FuncInfo) ReadPcline(b []byte) (uint32, uint32) { - return binary.LittleEndian.Uint32(b[20:]), binary.LittleEndian.Uint32(b[24:]) +func (*FuncInfo) ReadPcline(b []byte) SymRef { + return SymRef{binary.LittleEndian.Uint32(b[28:]), binary.LittleEndian.Uint32(b[32:])} } -// return start and end offsets. -func (*FuncInfo) ReadPcinline(b []byte, pcdataoffset uint32) (uint32, uint32) { - return binary.LittleEndian.Uint32(b[24:]), binary.LittleEndian.Uint32(b[pcdataoffset:]) +func (*FuncInfo) ReadPcinline(b []byte) SymRef { + return SymRef{binary.LittleEndian.Uint32(b[36:]), binary.LittleEndian.Uint32(b[40:])} } -// return start and end offsets. -func (*FuncInfo) ReadPcdata(b []byte, pcdataoffset uint32, k uint32) (uint32, uint32) { - return binary.LittleEndian.Uint32(b[pcdataoffset+4*k:]), binary.LittleEndian.Uint32(b[pcdataoffset+4+4*k:]) +func (*FuncInfo) ReadPcdata(b []byte) []SymRef { + syms := make([]SymRef, binary.LittleEndian.Uint32(b[44:])) + for i := range syms { + syms[i] = SymRef{binary.LittleEndian.Uint32(b[48+i*8:]), binary.LittleEndian.Uint32(b[52+i*8:])} + } + return syms } func (*FuncInfo) ReadFuncdataoff(b []byte, funcdataofffoff uint32, k uint32) int64 { diff --git a/src/cmd/internal/goobj/objfile.go b/src/cmd/internal/goobj/objfile.go index 5d4a253024..9a64f96cd6 100644 --- a/src/cmd/internal/goobj/objfile.go +++ b/src/cmd/internal/goobj/objfile.go @@ -421,8 +421,11 @@ const ( AuxDwarfLoc AuxDwarfRanges AuxDwarfLines - - // TODO: more. Pcdata? + AuxPcsp + AuxPcfile + AuxPcline + AuxPcinline + AuxPcdata ) func (a *Aux) Type() uint8 { return a[0] } @@ -827,11 +830,6 @@ func (r *Reader) Data(i uint32) []byte { return r.BytesAt(base+off, int(end-off)) } -// AuxDataBase returns the base offset of the aux data block. -func (r *Reader) PcdataBase() uint32 { - return r.h.Offsets[BlkPcdata] -} - // NRefName returns the number of referenced symbol names. func (r *Reader) NRefName() int { return int(r.h.Offsets[BlkRefName+1]-r.h.Offsets[BlkRefName]) / RefNameSize diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index dc47e51be9..11fab63065 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -624,11 +624,12 @@ func (s *LSym) CanBeAnSSASym() { } type Pcln struct { - Pcsp Pcdata - Pcfile Pcdata - Pcline Pcdata - Pcinline Pcdata - Pcdata []Pcdata + // Aux symbols for pcln + Pcsp *LSym + Pcfile *LSym + Pcline *LSym + Pcinline *LSym + Pcdata []*LSym Funcdata []*LSym Funcdataoff []int64 UsedFiles map[goobj.CUFileIndex]struct{} // file indices used while generating pcfile @@ -650,10 +651,6 @@ type Auto struct { Gotype *LSym } -type Pcdata struct { - P []byte -} - // Link holds the context for writing object code from a compiler // to be linker input or for reading that input into the linker. type Link struct { diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index 8234697d72..a2bbdff24e 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -185,7 +185,11 @@ func WriteObjFile(ctxt *Link, b *bio.Writer) { // Pcdata h.Offsets[goobj.BlkPcdata] = w.Offset() for _, s := range ctxt.Text { // iteration order must match genFuncInfoSyms - if s.Func != nil { + // Because of the phase order, it's possible that we try to write an invalid + // object file, and the Pcln variables haven't been filled in. As such, we + // need to check that Pcsp exists, and assume the other pcln variables exist + // as well. Tests like test/fixedbugs/issue22200.go demonstrate this issue. + if s.Func != nil && s.Func.Pcln.Pcsp != nil { pc := &s.Func.Pcln w.Bytes(pc.Pcsp.P) w.Bytes(pc.Pcfile.P) @@ -478,6 +482,22 @@ func (w *writer) Aux(s *LSym) { if s.Func.dwarfDebugLinesSym != nil && s.Func.dwarfDebugLinesSym.Size != 0 { w.aux1(goobj.AuxDwarfLines, s.Func.dwarfDebugLinesSym) } + if s.Func.Pcln.Pcsp != nil && s.Func.Pcln.Pcsp.Size != 0 { + w.aux1(goobj.AuxPcsp, s.Func.Pcln.Pcsp) + } + if s.Func.Pcln.Pcfile != nil && s.Func.Pcln.Pcfile.Size != 0 { + w.aux1(goobj.AuxPcfile, s.Func.Pcln.Pcfile) + } + if s.Func.Pcln.Pcline != nil && s.Func.Pcln.Pcline.Size != 0 { + w.aux1(goobj.AuxPcline, s.Func.Pcln.Pcline) + } + if s.Func.Pcln.Pcinline != nil && s.Func.Pcln.Pcinline.Size != 0 { + w.aux1(goobj.AuxPcinline, s.Func.Pcln.Pcinline) + } + for _, pcSym := range s.Func.Pcln.Pcdata { + w.aux1(goobj.AuxPcdata, pcSym) + } + } } @@ -559,6 +579,19 @@ func nAuxSym(s *LSym) int { if s.Func.dwarfDebugLinesSym != nil && s.Func.dwarfDebugLinesSym.Size != 0 { n++ } + if s.Func.Pcln.Pcsp != nil && s.Func.Pcln.Pcsp.Size != 0 { + n++ + } + if s.Func.Pcln.Pcfile != nil && s.Func.Pcln.Pcfile.Size != 0 { + n++ + } + if s.Func.Pcln.Pcline != nil && s.Func.Pcln.Pcline.Size != 0 { + n++ + } + if s.Func.Pcln.Pcinline != nil && s.Func.Pcln.Pcinline.Size != 0 { + n++ + } + n += len(s.Func.Pcln.Pcdata) } return n } @@ -566,7 +599,17 @@ func nAuxSym(s *LSym) int { // generate symbols for FuncInfo. func genFuncInfoSyms(ctxt *Link) { infosyms := make([]*LSym, 0, len(ctxt.Text)) - var pcdataoff uint32 + hashedsyms := make([]*LSym, 0, 4*len(ctxt.Text)) + preparePcSym := func(s *LSym) *LSym { + if s == nil { + return s + } + s.PkgIdx = goobj.PkgIdxHashed + s.SymIdx = int32(len(hashedsyms) + len(ctxt.hasheddefs)) + s.Set(AttrIndexed, true) + hashedsyms = append(hashedsyms, s) + return s + } var b bytes.Buffer symidx := int32(len(ctxt.defs)) for _, s := range ctxt.Text { @@ -579,20 +622,14 @@ func genFuncInfoSyms(ctxt *Link) { FuncID: objabi.FuncID(s.Func.FuncID), } pc := &s.Func.Pcln - o.Pcsp = pcdataoff - pcdataoff += uint32(len(pc.Pcsp.P)) - o.Pcfile = pcdataoff - pcdataoff += uint32(len(pc.Pcfile.P)) - o.Pcline = pcdataoff - pcdataoff += uint32(len(pc.Pcline.P)) - o.Pcinline = pcdataoff - pcdataoff += uint32(len(pc.Pcinline.P)) - o.Pcdata = make([]uint32, len(pc.Pcdata)) - for i, pcd := range pc.Pcdata { - o.Pcdata[i] = pcdataoff - pcdataoff += uint32(len(pcd.P)) + o.Pcsp = makeSymRef(preparePcSym(pc.Pcsp)) + o.Pcfile = makeSymRef(preparePcSym(pc.Pcfile)) + o.Pcline = makeSymRef(preparePcSym(pc.Pcline)) + o.Pcinline = makeSymRef(preparePcSym(pc.Pcinline)) + o.Pcdata = make([]goobj.SymRef, len(pc.Pcdata)) + for i, pcSym := range pc.Pcdata { + o.Pcdata[i] = makeSymRef(preparePcSym(pcSym)) } - o.PcdataEnd = pcdataoff o.Funcdataoff = make([]uint32, len(pc.Funcdataoff)) for i, x := range pc.Funcdataoff { o.Funcdataoff[i] = uint32(x) @@ -642,9 +679,9 @@ func genFuncInfoSyms(ctxt *Link) { } } ctxt.defs = append(ctxt.defs, infosyms...) + ctxt.hasheddefs = append(ctxt.hasheddefs, hashedsyms...) } -// debugDumpAux is a dumper for selected aux symbols. func writeAuxSymDebug(ctxt *Link, par *LSym, aux *LSym) { // Most aux symbols (ex: funcdata) are not interesting-- // pick out just the DWARF ones for now. diff --git a/src/cmd/internal/obj/pcln.go b/src/cmd/internal/obj/pcln.go index 1f7ccf47ef..7750637796 100644 --- a/src/cmd/internal/obj/pcln.go +++ b/src/cmd/internal/obj/pcln.go @@ -6,6 +6,7 @@ package obj import ( "cmd/internal/goobj" + "cmd/internal/objabi" "encoding/binary" "log" ) @@ -14,16 +15,19 @@ import ( // returned by valfunc parameterized by arg. The invocation of valfunc to update the // current value is, for each p, // -// val = valfunc(func, val, p, 0, arg); -// record val as value at p->pc; -// val = valfunc(func, val, p, 1, arg); +// sym = valfunc(func, p, 0, arg); +// record sym.P as value at p->pc; +// sym = valfunc(func, p, 1, arg); // // where func is the function, val is the current value, p is the instruction being // considered, and arg can be used to further parameterize valfunc. -func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(*Link, *LSym, int32, *Prog, int32, interface{}) int32, arg interface{}) { +func funcpctab(ctxt *Link, func_ *LSym, desc string, valfunc func(*Link, *LSym, int32, *Prog, int32, interface{}) int32, arg interface{}) *LSym { dbg := desc == ctxt.Debugpcln - - dst.P = dst.P[:0] + dst := []byte{} + sym := &LSym{ + Type: objabi.SRODATA, + Attribute: AttrContentAddressable, + } if dbg { ctxt.Logf("funcpctab %s [valfunc=%s]\n", func_.Name, desc) @@ -32,7 +36,8 @@ func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(* val := int32(-1) oldval := val if func_.Func.Text == nil { - return + // Return the emtpy symbol we've built so far. + return sym } pc := func_.Func.Text.Pc @@ -88,13 +93,13 @@ func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(* if started { pcdelta := (p.Pc - pc) / int64(ctxt.Arch.MinLC) n := binary.PutUvarint(buf, uint64(pcdelta)) - dst.P = append(dst.P, buf[:n]...) + dst = append(dst, buf[:n]...) pc = p.Pc } delta := val - oldval n := binary.PutVarint(buf, int64(delta)) - dst.P = append(dst.P, buf[:n]...) + dst = append(dst, buf[:n]...) oldval = val started = true val = valfunc(ctxt, func_, val, p, 1, arg) @@ -109,18 +114,22 @@ func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(* ctxt.Diag("negative pc offset: %v", v) } n := binary.PutUvarint(buf, uint64(v)) - dst.P = append(dst.P, buf[:n]...) + dst = append(dst, buf[:n]...) // add terminating varint-encoded 0, which is just 0 - dst.P = append(dst.P, 0) + dst = append(dst, 0) } if dbg { - ctxt.Logf("wrote %d bytes to %p\n", len(dst.P), dst) - for _, p := range dst.P { + ctxt.Logf("wrote %d bytes to %p\n", len(dst), dst) + for _, p := range dst { ctxt.Logf(" %02x", p) } ctxt.Logf("\n") } + + sym.Size = int64(len(dst)) + sym.P = dst + return sym } // pctofileline computes either the file number (arg == 0) @@ -268,18 +277,17 @@ func linkpcln(ctxt *Link, cursym *LSym) { } } - pcln.Pcdata = make([]Pcdata, npcdata) - pcln.Pcdata = pcln.Pcdata[:npcdata] + pcln.Pcdata = make([]*LSym, npcdata) pcln.Funcdata = make([]*LSym, nfuncdata) pcln.Funcdataoff = make([]int64, nfuncdata) pcln.Funcdataoff = pcln.Funcdataoff[:nfuncdata] - funcpctab(ctxt, &pcln.Pcsp, cursym, "pctospadj", pctospadj, nil) - funcpctab(ctxt, &pcln.Pcfile, cursym, "pctofile", pctofileline, pcln) - funcpctab(ctxt, &pcln.Pcline, cursym, "pctoline", pctofileline, nil) + pcln.Pcsp = funcpctab(ctxt, cursym, "pctospadj", pctospadj, nil) + pcln.Pcfile = funcpctab(ctxt, cursym, "pctofile", pctofileline, pcln) + pcln.Pcline = funcpctab(ctxt, cursym, "pctoline", pctofileline, nil) pcinlineState := new(pcinlineState) - funcpctab(ctxt, &pcln.Pcinline, cursym, "pctoinline", pcinlineState.pctoinline, nil) + pcln.Pcinline = funcpctab(ctxt, cursym, "pctoinline", pcinlineState.pctoinline, nil) for _, inlMark := range cursym.Func.InlMarks { pcinlineState.setParentPC(ctxt, int(inlMark.id), int32(inlMark.p.Pc)) } @@ -309,9 +317,14 @@ func linkpcln(ctxt *Link, cursym *LSym) { // pcdata. for i := 0; i < npcdata; i++ { if (havepc[i/32]>>uint(i%32))&1 == 0 { - continue + // use an empty symbol. + pcln.Pcdata[i] = &LSym{ + Type: objabi.SRODATA, + Attribute: AttrContentAddressable, + } + } else { + pcln.Pcdata[i] = funcpctab(ctxt, cursym, "pctopcdata", pctopcdata, interface{}(uint32(i))) } - funcpctab(ctxt, &pcln.Pcdata[i], cursym, "pctopcdata", pctopcdata, interface{}(uint32(i))) } // funcdata diff --git a/src/cmd/internal/objfile/goobj.go b/src/cmd/internal/objfile/goobj.go index e838f58aed..8eecebb1df 100644 --- a/src/cmd/internal/objfile/goobj.go +++ b/src/cmd/internal/objfile/goobj.go @@ -236,7 +236,15 @@ func (f *goobjFile) PCToLine(pc uint64) (string, int, *gosym.Func) { if arch == nil { return "", 0, nil } - pcdataBase := r.PcdataBase() + getSymData := func(s goobj.SymRef) []byte { + if s.PkgIdx != goobj.PkgIdxHashed { + // We don't need the data for non-hashed symbols, yet. + panic("not supported") + } + i := uint32(s.SymIdx + uint32(r.NSym()+r.NHashed64def())) + return r.BytesAt(r.DataOff(i), r.DataSize(i)) + } + ndef := uint32(r.NSym() + r.NHashed64def() + r.NHasheddef() + r.NNonpkgdef()) for i := uint32(0); i < ndef; i++ { osym := r.Sym(i) @@ -262,11 +270,9 @@ func (f *goobjFile) PCToLine(pc uint64) (string, int, *gosym.Func) { b := r.BytesAt(r.DataOff(isym), r.DataSize(isym)) var info *goobj.FuncInfo lengths := info.ReadFuncInfoLengths(b) - off, end := info.ReadPcline(b) - pcline := r.BytesAt(pcdataBase+off, int(end-off)) + pcline := getSymData(info.ReadPcline(b)) line := int(pcValue(pcline, pc-addr, arch)) - off, end = info.ReadPcfile(b) - pcfile := r.BytesAt(pcdataBase+off, int(end-off)) + pcfile := getSymData(info.ReadPcfile(b)) fileID := pcValue(pcfile, pc-addr, arch) globalFileID := info.ReadFile(b, lengths.FileOff, uint32(fileID)) fileName := r.File(int(globalFileID)) diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index d1f2ac583d..2b95ad5a67 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -1421,7 +1421,7 @@ func (d *dwctxt) writeframes(fs loader.Sym) dwarfSecInfo { deltaBuf = dwarf.AppendUleb128(deltaBuf, uint64(thearch.Dwarfreglr)) } - for pcsp.Init(fpcsp); !pcsp.Done; pcsp.Next() { + for pcsp.Init(d.linkctxt.loader.Data(fpcsp)); !pcsp.Done; pcsp.Next() { nextpc := pcsp.NextPC // pciterinit goes up to the end of the function, diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 09c7bbfb53..caa4566190 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -2252,7 +2252,7 @@ func (sc *stkChk) check(up *chain, depth int) int { var ch1 chain pcsp := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) ri := 0 - for pcsp.Init(info.Pcsp()); !pcsp.Done; pcsp.Next() { + for pcsp.Init(ldr.Data(info.Pcsp())); !pcsp.Done; pcsp.Next() { // pcsp.value is in effect for [pcsp.pc, pcsp.nextpc). // Check stack size in effect for this span. diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index c7535f6a61..e9fd5937e7 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -592,9 +592,8 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { fi := ldr.FuncInfo(s) if fi.Valid() { fi.Preload() - npc := fi.NumPcdata() - for i := uint32(0); i < npc; i++ { - pcdata = append(pcdata, sym.Pcdata{P: fi.Pcdata(int(i))}) + for _, dataSym := range fi.Pcdata() { + pcdata = append(pcdata, sym.Pcdata{P: ldr.Data(dataSym)}) } nfd := fi.NumFuncdataoff() for i := uint32(0); i < nfd; i++ { @@ -666,15 +665,15 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { cu := ldr.SymUnit(s) if fi.Valid() { - pcsp = sym.Pcdata{P: fi.Pcsp()} - pcfile = sym.Pcdata{P: fi.Pcfile()} - pcline = sym.Pcdata{P: fi.Pcline()} + pcsp = sym.Pcdata{P: ldr.Data(fi.Pcsp())} + pcfile = sym.Pcdata{P: ldr.Data(fi.Pcfile())} + pcline = sym.Pcdata{P: ldr.Data(fi.Pcline())} } if fi.Valid() && fi.NumInlTree() > 0 { its := oldState.genInlTreeSym(cu, fi, ctxt.Arch, state) funcdata[objabi.FUNCDATA_InlTree] = its - pcdata[objabi.PCDATA_InlTreeIndex] = sym.Pcdata{P: fi.Pcinline()} + pcdata[objabi.PCDATA_InlTreeIndex] = sym.Pcdata{P: ldr.Data(fi.Pcinline())} } // pcdata diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 8fd10b0848..f149e3c831 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -1878,19 +1878,24 @@ func (fi *FuncInfo) FuncID() objabi.FuncID { return objabi.FuncID((*goobj.FuncInfo)(nil).ReadFuncID(fi.data)) } -func (fi *FuncInfo) Pcsp() []byte { - pcsp, end := (*goobj.FuncInfo)(nil).ReadPcsp(fi.data) - return fi.r.BytesAt(fi.r.PcdataBase()+pcsp, int(end-pcsp)) +func (fi *FuncInfo) Pcsp() Sym { + sym := (*goobj.FuncInfo)(nil).ReadPcsp(fi.data) + return fi.l.resolve(fi.r, sym) } -func (fi *FuncInfo) Pcfile() []byte { - pcf, end := (*goobj.FuncInfo)(nil).ReadPcfile(fi.data) - return fi.r.BytesAt(fi.r.PcdataBase()+pcf, int(end-pcf)) +func (fi *FuncInfo) Pcfile() Sym { + sym := (*goobj.FuncInfo)(nil).ReadPcfile(fi.data) + return fi.l.resolve(fi.r, sym) } -func (fi *FuncInfo) Pcline() []byte { - pcln, end := (*goobj.FuncInfo)(nil).ReadPcline(fi.data) - return fi.r.BytesAt(fi.r.PcdataBase()+pcln, int(end-pcln)) +func (fi *FuncInfo) Pcline() Sym { + sym := (*goobj.FuncInfo)(nil).ReadPcline(fi.data) + return fi.l.resolve(fi.r, sym) +} + +func (fi *FuncInfo) Pcinline() Sym { + sym := (*goobj.FuncInfo)(nil).ReadPcinline(fi.data) + return fi.l.resolve(fi.r, sym) } // Preload has to be called prior to invoking the various methods @@ -1899,27 +1904,16 @@ func (fi *FuncInfo) Preload() { fi.lengths = (*goobj.FuncInfo)(nil).ReadFuncInfoLengths(fi.data) } -func (fi *FuncInfo) Pcinline() []byte { +func (fi *FuncInfo) Pcdata() []Sym { if !fi.lengths.Initialized { panic("need to call Preload first") } - pcinl, end := (*goobj.FuncInfo)(nil).ReadPcinline(fi.data, fi.lengths.PcdataOff) - return fi.r.BytesAt(fi.r.PcdataBase()+pcinl, int(end-pcinl)) -} - -func (fi *FuncInfo) NumPcdata() uint32 { - if !fi.lengths.Initialized { - panic("need to call Preload first") + syms := (*goobj.FuncInfo)(nil).ReadPcdata(fi.data) + ret := make([]Sym, len(syms)) + for i := range ret { + ret[i] = fi.l.resolve(fi.r, syms[i]) } - return fi.lengths.NumPcdata -} - -func (fi *FuncInfo) Pcdata(k int) []byte { - if !fi.lengths.Initialized { - panic("need to call Preload first") - } - pcdat, end := (*goobj.FuncInfo)(nil).ReadPcdata(fi.data, fi.lengths.PcdataOff, uint32(k)) - return fi.r.BytesAt(fi.r.PcdataBase()+pcdat, int(end-pcdat)) + return ret } func (fi *FuncInfo) NumFuncdataoff() uint32 { From 26407b22129e2e54db269c1a92826521addd8d56 Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Wed, 12 Aug 2020 19:26:53 -0400 Subject: [PATCH 5/9] [dev.link] cmd/{compile,link}: remove pcdata tables from pclntab_old MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the pctables out of pclntab_old. Creates a new generator symbol, runtime.pctab, which holds all the deduplicated pctables. Also, tightens up some of the types in runtime. Darwin, cmd/compile statistics: alloc/op Pclntab_GC 26.4MB ± 0% 13.8MB ± 0% allocs/op Pclntab_GC 89.9k ± 0% 86.4k ± 0% liveB Pclntab_GC 25.5M ± 0% 24.2M ± 0% No significant change in binary size. Change-Id: I1560fd4421f8a210f8d4b508fbc54e1780e338f9 Reviewed-on: https://go-review.googlesource.com/c/go/+/248332 Run-TryBot: Jeremy Faller TryBot-Result: Gobot Gobot Reviewed-by: Cherry Zhang --- src/cmd/link/internal/ld/data.go | 2 + src/cmd/link/internal/ld/pcln.go | 143 ++++++++++++------ src/cmd/link/internal/ld/symtab.go | 4 + src/cmd/link/internal/loader/symbolbuilder.go | 9 ++ src/cmd/link/internal/sym/symbol.go | 4 - src/debug/gosym/pclntab.go | 10 +- src/runtime/runtime2.go | 8 +- src/runtime/symtab.go | 28 ++-- 8 files changed, 136 insertions(+), 72 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index a551d46403..2aecbfbeb5 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1925,6 +1925,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.funcnametab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.cutab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect) + ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pctab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab_old", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect) if ctxt.HeadType == objabi.Haix { @@ -2511,6 +2512,7 @@ func (ctxt *Link) address() []*sym.Segment { ctxt.defineInternal("runtime.funcnametab", sym.SRODATA) ctxt.defineInternal("runtime.cutab", sym.SRODATA) ctxt.defineInternal("runtime.filetab", sym.SRODATA) + ctxt.defineInternal("runtime.pctab", sym.SRODATA) ctxt.defineInternal("runtime.pclntab_old", sym.SRODATA) ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length)) ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr)) diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index e9fd5937e7..576f1c3780 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -43,6 +43,7 @@ type pclntab struct { findfunctab loader.Sym cutab loader.Sym filetab loader.Sym + pctab loader.Sym // The number of functions + number of TEXT sections - 1. This is such an // unexpected value because platforms that have more than one TEXT section @@ -273,10 +274,11 @@ func (state *pclntab) generatePCHeader(ctxt *Link) { off = writeSymOffset(off, state.funcnametab) off = writeSymOffset(off, state.cutab) off = writeSymOffset(off, state.filetab) + off = writeSymOffset(off, state.pctab) off = writeSymOffset(off, state.pclntab) } - size := int64(8 + 6*ctxt.Arch.PtrSize) + size := int64(8 + 7*ctxt.Arch.PtrSize) state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader) } @@ -463,6 +465,68 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat return cuOffsets } +// generatePctab creates the runtime.pctab variable, holding all the +// deduplicated pcdata. +func (state *pclntab) generatePctab(ctxt *Link, container loader.Bitmap) { + ldr := ctxt.loader + + // Pctab offsets of 0 are considered invalid in the runtime. We respect + // that by just padding a single byte at the beginning of runtime.pctab, + // that way no real offsets can be zero. + size := int64(1) + + // Walk the functions, finding offset to store each pcdata. + seen := make(map[loader.Sym]struct{}) + saveOffset := func(pcSym loader.Sym) { + if _, ok := seen[pcSym]; !ok { + datSize := ldr.SymSize(pcSym) + if datSize != 0 { + ldr.SetSymValue(pcSym, size) + } else { + // Invalid PC data, record as zero. + ldr.SetSymValue(pcSym, 0) + } + size += datSize + seen[pcSym] = struct{}{} + } + } + for _, s := range ctxt.Textp { + if !emitPcln(ctxt, s, container) { + continue + } + fi := ldr.FuncInfo(s) + if !fi.Valid() { + continue + } + fi.Preload() + + pcSyms := []loader.Sym{fi.Pcsp(), fi.Pcfile(), fi.Pcline()} + for _, pcSym := range pcSyms { + saveOffset(pcSym) + } + for _, pcSym := range fi.Pcdata() { + saveOffset(pcSym) + } + if fi.NumInlTree() > 0 { + saveOffset(fi.Pcinline()) + } + } + + // TODO: There is no reason we need a generator for this variable, and it + // could be moved to a carrier symbol. However, carrier symbols containing + // carrier symbols don't work yet (as of Aug 2020). Once this is fixed, + // runtime.pctab could just be a carrier sym. + writePctab := func(ctxt *Link, s loader.Sym) { + ldr := ctxt.loader + sb := ldr.MakeSymbolUpdater(s) + for sym := range seen { + sb.SetBytesAt(ldr.SymValue(sym), ldr.Data(sym)) + } + } + + state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab) +} + // pclntab initializes the pclntab symbol with // runtime function and file name information. @@ -494,6 +558,9 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // runtime.filetab // []null terminated filename strings // + // runtime.pctab + // []byte of deduplicated pc data. + // // runtime.pclntab_old // function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes] // end PC [thearch.ptrsize bytes] @@ -514,6 +581,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { state.generatePCHeader(ctxt) state.generateFuncnametab(ctxt, container) cuOffsets := state.generateFilenameTabs(ctxt, compUnits, container) + state.generatePctab(ctxt, container) funcdataBytes := int64(0) ldr.SetCarrierSym(state.pclntab, state.carrier) @@ -525,21 +593,6 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { ftab.Grow(int64(state.nfunc)*2*int64(ctxt.Arch.PtrSize) + int64(ctxt.Arch.PtrSize) + 4) - szHint := len(ctxt.Textp) * 2 - pctaboff := make(map[string]uint32, szHint) - writepctab := func(off int32, p []byte) int32 { - start, ok := pctaboff[string(p)] - if !ok { - if len(p) > 0 { - start = uint32(len(ftab.Data())) - ftab.AddBytes(p) - } - pctaboff[string(p)] = start - } - newoff := int32(ftab.SetUint32(ctxt.Arch, int64(off), start)) - return newoff - } - setAddr := (*loader.SymbolBuilder).SetAddrPlus if ctxt.IsExe() && ctxt.IsInternal() { // Internal linking static executable. At this point the function @@ -555,10 +608,6 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { } } - pcsp := sym.Pcdata{} - pcfile := sym.Pcdata{} - pcline := sym.Pcdata{} - pcdata := []sym.Pcdata{} funcdata := []loader.Sym{} funcdataoff := []int64{} @@ -583,18 +632,13 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { } prevFunc = s - pcsp.P = pcsp.P[:0] - pcline.P = pcline.P[:0] - pcfile.P = pcfile.P[:0] - pcdata = pcdata[:0] + var numPCData int32 funcdataoff = funcdataoff[:0] funcdata = funcdata[:0] fi := ldr.FuncInfo(s) if fi.Valid() { fi.Preload() - for _, dataSym := range fi.Pcdata() { - pcdata = append(pcdata, sym.Pcdata{P: ldr.Data(dataSym)}) - } + numPCData = int32(len(fi.Pcdata())) nfd := fi.NumFuncdataoff() for i := uint32(0); i < nfd; i++ { funcdataoff = append(funcdataoff, fi.Funcdataoff(int(i))) @@ -602,15 +646,12 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { funcdata = fi.Funcdata(funcdata) } + writeInlPCData := false if fi.Valid() && fi.NumInlTree() > 0 { - - if len(pcdata) <= objabi.PCDATA_InlTreeIndex { - // Create inlining pcdata table. - newpcdata := make([]sym.Pcdata, objabi.PCDATA_InlTreeIndex+1) - copy(newpcdata, pcdata) - pcdata = newpcdata + writeInlPCData = true + if numPCData <= objabi.PCDATA_InlTreeIndex { + numPCData = objabi.PCDATA_InlTreeIndex + 1 } - if len(funcdataoff) <= objabi.FUNCDATA_InlTree { // Create inline tree funcdata. newfuncdata := make([]loader.Sym, objabi.FUNCDATA_InlTree+1) @@ -635,7 +676,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // fixed size of struct, checked below off := funcstart - end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + int32(len(pcdata))*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize) + end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + numPCData*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize) if len(funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) { end += 4 } @@ -664,23 +705,21 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn)) cu := ldr.SymUnit(s) - if fi.Valid() { - pcsp = sym.Pcdata{P: ldr.Data(fi.Pcsp())} - pcfile = sym.Pcdata{P: ldr.Data(fi.Pcfile())} - pcline = sym.Pcdata{P: ldr.Data(fi.Pcline())} - } if fi.Valid() && fi.NumInlTree() > 0 { its := oldState.genInlTreeSym(cu, fi, ctxt.Arch, state) funcdata[objabi.FUNCDATA_InlTree] = its - pcdata[objabi.PCDATA_InlTreeIndex] = sym.Pcdata{P: ldr.Data(fi.Pcinline())} } // pcdata - off = writepctab(off, pcsp.P) - off = writepctab(off, pcfile.P) - off = writepctab(off, pcline.P) - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(len(pcdata)))) + if fi.Valid() { + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcsp())))) + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcfile())))) + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcline())))) + } else { + off += 12 + } + off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(numPCData))) // Store the offset to compilation unit's file table. cuIdx := ^uint32(0) @@ -700,9 +739,17 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // nfuncdata must be the final entry. off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(len(funcdata)))) - for i := range pcdata { - off = writepctab(off, pcdata[i].P) + + // Output the pcdata. + if fi.Valid() { + for i, pcSym := range fi.Pcdata() { + ftab.SetUint32(ctxt.Arch, int64(off+int32(i*4)), uint32(ldr.SymValue(pcSym))) + } + if writeInlPCData { + ftab.SetUint32(ctxt.Arch, int64(off+objabi.PCDATA_InlTreeIndex*4), uint32(ldr.SymValue(fi.Pcinline()))) + } } + off += numPCData * 4 // funcdata, must be pointer-aligned and we're only int32-aligned. // Missing funcdata will be 0 (nil pointer). @@ -724,7 +771,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { } if off != end { - ctxt.Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, len(pcdata), len(funcdata), ctxt.Arch.PtrSize) + ctxt.Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, numPCData, len(funcdata), ctxt.Arch.PtrSize) errorexit() } diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index d05b98f04a..520aaa44c2 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -627,6 +627,10 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind { moduledata.AddAddr(ctxt.Arch, pcln.filetab) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab))) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.filetab))) + // The pctab slice + moduledata.AddAddr(ctxt.Arch, pcln.pctab) + moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pctab))) + moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pctab))) // The pclntab slice moduledata.AddAddr(ctxt.Arch, pcln.pclntab) moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(pcln.pclntab))) diff --git a/src/cmd/link/internal/loader/symbolbuilder.go b/src/cmd/link/internal/loader/symbolbuilder.go index e14d89a927..c0c723d7f0 100644 --- a/src/cmd/link/internal/loader/symbolbuilder.go +++ b/src/cmd/link/internal/loader/symbolbuilder.go @@ -336,6 +336,15 @@ func (sb *SymbolBuilder) Addstring(str string) int64 { return r } +func (sb *SymbolBuilder) SetBytesAt(off int64, b []byte) int64 { + datLen := int64(len(b)) + if off+datLen > int64(len(sb.data)) { + panic("attempt to write past end of buffer") + } + copy(sb.data[off:off+datLen], b) + return off + datLen +} + func (sb *SymbolBuilder) addSymRef(tgt Sym, add int64, typ objabi.RelocType, rsize int) int64 { if sb.kind == 0 { sb.kind = sym.SDATA diff --git a/src/cmd/link/internal/sym/symbol.go b/src/cmd/link/internal/sym/symbol.go index 1a4165ebf7..70cf36a87e 100644 --- a/src/cmd/link/internal/sym/symbol.go +++ b/src/cmd/link/internal/sym/symbol.go @@ -33,7 +33,3 @@ func VersionToABI(v int) (obj.ABI, bool) { } return ^obj.ABI(0), false } - -type Pcdata struct { - P []byte -} diff --git a/src/debug/gosym/pclntab.go b/src/debug/gosym/pclntab.go index 21edddda20..a72f9847d7 100644 --- a/src/debug/gosym/pclntab.go +++ b/src/debug/gosym/pclntab.go @@ -58,6 +58,7 @@ type LineTable struct { functab []byte nfunctab uint32 filetab []byte + pctab []byte // points to the pctables. nfiletab uint32 funcNames map[uint32]string // cache the function names strings map[uint32]string // interned substrings of Data, keyed by offset @@ -235,6 +236,8 @@ func (t *LineTable) parsePclnTab() { offset = t.uintptr(t.Data[8+4*t.ptrsize:]) t.filetab = t.Data[offset:] offset = t.uintptr(t.Data[8+5*t.ptrsize:]) + t.pctab = t.Data[offset:] + offset = t.uintptr(t.Data[8+6*t.ptrsize:]) t.funcdata = t.Data[offset:] t.functab = t.Data[offset:] functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize @@ -244,6 +247,7 @@ func (t *LineTable) parsePclnTab() { t.funcdata = t.Data t.funcnametab = t.Data t.functab = t.Data[8+t.ptrsize:] + t.pctab = t.Data functabsize := t.nfunctab*2*t.ptrsize + t.ptrsize fileoff := t.binary.Uint32(t.functab[functabsize:]) t.functab = t.functab[:functabsize] @@ -373,7 +377,7 @@ func (t *LineTable) step(p *[]byte, pc *uint64, val *int32, first bool) bool { // off is the offset to the beginning of the pc-value table, // and entry is the start PC for the corresponding function. func (t *LineTable) pcvalue(off uint32, entry, targetpc uint64) int32 { - p := t.funcdata[off:] + p := t.pctab[off:] val := int32(-1) pc := entry @@ -396,8 +400,8 @@ func (t *LineTable) findFileLine(entry uint64, filetab, linetab uint32, filenum, return 0 } - fp := t.funcdata[filetab:] - fl := t.funcdata[linetab:] + fp := t.pctab[filetab:] + fl := t.pctab[linetab:] fileVal := int32(-1) filePC := entry lineVal := int32(-1) diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index 5a79c7e6ec..755c409078 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -800,10 +800,10 @@ type _func struct { args int32 // in/out args size deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any. - pcsp int32 - pcfile int32 - pcln int32 - npcdata int32 + pcsp uint32 + pcfile uint32 + pcln uint32 + npcdata uint32 cuOffset uint32 // runtime.cutab offset of this function's CU funcID funcID // set for certain special runtime functions _ [2]byte // pad diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index fbd9315522..0610f75179 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -345,6 +345,7 @@ type pcHeader struct { funcnameOffset uintptr // offset to the funcnametab variable from pcHeader cuOffset uintptr // offset to the cutab variable from pcHeader filetabOffset uintptr // offset to the filetab variable from pcHeader + pctabOffset uintptr // offset to the pctab varible from pcHeader pclnOffset uintptr // offset to the pclntab variable from pcHeader } @@ -358,6 +359,7 @@ type moduledata struct { funcnametab []byte cutab []uint32 filetab []byte + pctab []byte pclntable []byte ftab []functab findfunctab uintptr @@ -721,7 +723,7 @@ type pcvalueCache struct { type pcvalueCacheEnt struct { // targetpc and off together are the key of this cache entry. targetpc uintptr - off int32 + off uint32 // val is the value of this cached pcvalue entry. val int32 } @@ -736,7 +738,7 @@ func pcvalueCacheKey(targetpc uintptr) uintptr { // Returns the PCData value, and the PC where this value starts. // TODO: the start PC is returned only when cache is nil. -func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) { +func pcvalue(f funcInfo, off uint32, targetpc uintptr, cache *pcvalueCache, strict bool) (int32, uintptr) { if off == 0 { return -1, 0 } @@ -770,7 +772,7 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric return -1, 0 } datap := f.datap - p := datap.pclntable[off:] + p := datap.pctab[off:] pc := f.entry prevpc := pc val := int32(-1) @@ -812,7 +814,7 @@ func pcvalue(f funcInfo, off int32, targetpc uintptr, cache *pcvalueCache, stric print("runtime: invalid pc-encoded table f=", funcname(f), " pc=", hex(pc), " targetpc=", hex(targetpc), " tab=", p, "\n") - p = datap.pclntable[off:] + p = datap.pctab[off:] pc = f.entry val = -1 for { @@ -893,7 +895,7 @@ func funcspdelta(f funcInfo, targetpc uintptr, cache *pcvalueCache) int32 { // funcMaxSPDelta returns the maximum spdelta at any point in f. func funcMaxSPDelta(f funcInfo) int32 { datap := f.datap - p := datap.pclntable[f.pcsp:] + p := datap.pctab[f.pcsp:] pc := f.entry val := int32(-1) max := int32(0) @@ -909,20 +911,20 @@ func funcMaxSPDelta(f funcInfo) int32 { } } -func pcdatastart(f funcInfo, table int32) int32 { - return *(*int32)(add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(table)*4)) +func pcdatastart(f funcInfo, table uint32) uint32 { + return *(*uint32)(add(unsafe.Pointer(&f.nfuncdata), unsafe.Sizeof(f.nfuncdata)+uintptr(table)*4)) } -func pcdatavalue(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache) int32 { - if table < 0 || table >= f.npcdata { +func pcdatavalue(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache) int32 { + if table >= f.npcdata { return -1 } r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, true) return r } -func pcdatavalue1(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 { - if table < 0 || table >= f.npcdata { +func pcdatavalue1(f funcInfo, table uint32, targetpc uintptr, cache *pcvalueCache, strict bool) int32 { + if table >= f.npcdata { return -1 } r, _ := pcvalue(f, pcdatastart(f, table), targetpc, cache, strict) @@ -931,8 +933,8 @@ func pcdatavalue1(f funcInfo, table int32, targetpc uintptr, cache *pcvalueCache // Like pcdatavalue, but also return the start PC of this PCData value. // It doesn't take a cache. -func pcdatavalue2(f funcInfo, table int32, targetpc uintptr) (int32, uintptr) { - if table < 0 || table >= f.npcdata { +func pcdatavalue2(f funcInfo, table uint32, targetpc uintptr) (int32, uintptr) { + if table >= f.npcdata { return -1, 0 } return pcvalue(f, pcdatastart(f, table), targetpc, nil, true) From ac5c406ef0ab20e2a11f57470271266ef4265221 Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Thu, 13 Aug 2020 12:21:18 -0400 Subject: [PATCH 6/9] [dev.link] cmd/link: clean up some pclntab state Clean up some pclntab state, specifically: 1) Remove the oldPclnState type. 2) Move a structure out of pclnState, that was holding some memory. 3) Stop passing container around everywhere and calling emitPcln. Use a slice of function symbols instead. Change-Id: I74e916564cd769a706750d024e55ee0d811a79da Reviewed-on: https://go-review.googlesource.com/c/go/+/248379 Run-TryBot: Jeremy Faller TryBot-Result: Gobot Gobot Reviewed-by: Austin Clements Reviewed-by: Cherry Zhang --- src/cmd/link/internal/ld/pcln.go | 135 +++++++++++-------------------- 1 file changed, 47 insertions(+), 88 deletions(-) diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index 576f1c3780..33476ec292 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -16,17 +16,6 @@ import ( "strings" ) -// oldPclnState holds state information used during pclntab generation. Here -// 'ldr' is just a pointer to the context's loader, 'deferReturnSym' is the -// index for the symbol "runtime.deferreturn", -// -// NB: This is deprecated, and will be eliminated when pclntab_old is -// eliminated. -type oldPclnState struct { - ldr *loader.Loader - deferReturnSym loader.Sym -} - // pclntab holds the state needed for pclntab generation. type pclntab struct { // The first and last functions found. @@ -56,12 +45,6 @@ type pclntab struct { // The number of filenames in runtime.filetab. nfiles uint32 - - // maps the function symbol to offset in runtime.funcnametab - // This doesn't need to reside in the state once pclntab_old's been - // deleted -- it can live in generateFuncnametab. - // TODO(jfaller): Delete me! - funcNameOffset map[loader.Sym]int32 } // addGeneratedSym adds a generator symbol to pclntab, returning the new Sym. @@ -76,35 +59,26 @@ func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, f gen return s } -func makeOldPclnState(ctxt *Link) *oldPclnState { - ldr := ctxt.loader - drs := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal) - state := &oldPclnState{ - ldr: ldr, - deferReturnSym: drs, - } - - return state -} - // makePclntab makes a pclntab object, and assembles all the compilation units -// we'll need to write pclntab. -func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit) { +// we'll need to write pclntab. Returns the pclntab structure, a slice of the +// CompilationUnits we need, and a slice of the function symbols we need to +// generate pclntab. +func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit, []loader.Sym) { ldr := ctxt.loader - state := &pclntab{ - funcNameOffset: make(map[loader.Sym]int32), - } + state := &pclntab{} // Gather some basic stats and info. seenCUs := make(map[*sym.CompilationUnit]struct{}) prevSect := ldr.SymSect(ctxt.Textp[0]) compUnits := []*sym.CompilationUnit{} + funcs := []loader.Sym{} for _, s := range ctxt.Textp { if !emitPcln(ctxt, s, container) { continue } + funcs = append(funcs, s) state.nfunc++ if state.firstFunc == 0 { state.firstFunc = s @@ -130,15 +104,7 @@ func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.Compilat compUnits = append(compUnits, cu) } } - return state, compUnits -} - -func ftabaddstring(ftab *loader.SymbolBuilder, s string) int32 { - start := len(ftab.Data()) - ftab.Grow(int64(start + len(s) + 1)) // make room for s plus trailing NUL - ftd := ftab.Data() - copy(ftd[start:], s) - return int32(start) + return state, compUnits, funcs } // onlycsymbol looks at a symbol's name to report whether this is a @@ -163,11 +129,13 @@ func emitPcln(ctxt *Link, s loader.Sym, container loader.Bitmap) bool { return !container.Has(s) } -func (state *oldPclnState) computeDeferReturn(target *Target, s loader.Sym) uint32 { +func computeDeferReturn(ctxt *Link, deferReturnSym, s loader.Sym) uint32 { + ldr := ctxt.loader + target := ctxt.Target deferreturn := uint32(0) lastWasmAddr := uint32(0) - relocs := state.ldr.Relocs(s) + relocs := ldr.Relocs(s) for ri := 0; ri < relocs.Count(); ri++ { r := relocs.At(ri) if target.IsWasm() && r.Type() == objabi.R_ADDR { @@ -178,7 +146,7 @@ func (state *oldPclnState) computeDeferReturn(target *Target, s loader.Sym) uint // set the resumption point to PC_B. lastWasmAddr = uint32(r.Add()) } - if r.Type().IsDirectCall() && (r.Sym() == state.deferReturnSym || state.ldr.IsDeferReturnTramp(r.Sym())) { + if r.Type().IsDirectCall() && (r.Sym() == deferReturnSym || ldr.IsDeferReturnTramp(r.Sym())) { if target.IsWasm() { deferreturn = lastWasmAddr - 1 } else { @@ -211,8 +179,8 @@ func (state *oldPclnState) computeDeferReturn(target *Target, s loader.Sym) uint // genInlTreeSym generates the InlTree sym for a function with the // specified FuncInfo. -func (state *oldPclnState) genInlTreeSym(cu *sym.CompilationUnit, fi loader.FuncInfo, arch *sys.Arch, newState *pclntab) loader.Sym { - ldr := state.ldr +func genInlTreeSym(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, arch *sys.Arch, nameOffsets map[loader.Sym]uint32) loader.Sym { + ldr := ctxt.loader its := ldr.CreateExtSym("", 0) inlTreeSym := ldr.MakeSymbolUpdater(its) // Note: the generated symbol is given a type of sym.SGOFUNC, as a @@ -225,7 +193,7 @@ func (state *oldPclnState) genInlTreeSym(cu *sym.CompilationUnit, fi loader.Func for i := 0; i < int(ninl); i++ { call := fi.InlTree(i) val := call.File - nameoff, ok := newState.funcNameOffset[call.Func] + nameoff, ok := nameOffsets[call.Func] if !ok { panic("couldn't find function name offset") } @@ -282,16 +250,12 @@ func (state *pclntab) generatePCHeader(ctxt *Link) { state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader) } -// walkFuncs iterates over the Textp, calling a function for each unique +// walkFuncs iterates over the funcs, calling a function for each unique // function and inlined function. -func (state *pclntab) walkFuncs(ctxt *Link, container loader.Bitmap, f func(loader.Sym)) { +func walkFuncs(ctxt *Link, funcs []loader.Sym, f func(loader.Sym)) { ldr := ctxt.loader seen := make(map[loader.Sym]struct{}) - for _, ls := range ctxt.Textp { - s := loader.Sym(ls) - if !emitPcln(ctxt, s, container) { - continue - } + for _, s := range funcs { if _, ok := seen[s]; !ok { f(s) seen[s] = struct{}{} @@ -312,37 +276,37 @@ func (state *pclntab) walkFuncs(ctxt *Link, container loader.Bitmap, f func(load } } -// generateFuncnametab creates the function name table. -func (state *pclntab) generateFuncnametab(ctxt *Link, container loader.Bitmap) { +// generateFuncnametab creates the function name table. Returns a map of +// func symbol to the name offset in runtime.funcnamtab. +func (state *pclntab) generateFuncnametab(ctxt *Link, funcs []loader.Sym) map[loader.Sym]uint32 { + nameOffsets := make(map[loader.Sym]uint32, state.nfunc) + // Write the null terminated strings. writeFuncNameTab := func(ctxt *Link, s loader.Sym) { symtab := ctxt.loader.MakeSymbolUpdater(s) - for s, off := range state.funcNameOffset { + for s, off := range nameOffsets { symtab.AddStringAt(int64(off), ctxt.loader.SymName(s)) } } // Loop through the CUs, and calculate the size needed. var size int64 - state.walkFuncs(ctxt, container, func(s loader.Sym) { - state.funcNameOffset[s] = int32(size) + walkFuncs(ctxt, funcs, func(s loader.Sym) { + nameOffsets[s] = uint32(size) size += int64(ctxt.loader.SymNameLen(s)) + 1 // NULL terminate }) state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab) + return nameOffsets } -// walkFilenames walks the filenames in the all reachable functions. -func walkFilenames(ctxt *Link, container loader.Bitmap, f func(*sym.CompilationUnit, goobj.CUFileIndex)) { +// walkFilenames walks funcs, calling a function for each filename used in each +// function's line table. +func walkFilenames(ctxt *Link, funcs []loader.Sym, f func(*sym.CompilationUnit, goobj.CUFileIndex)) { ldr := ctxt.loader // Loop through all functions, finding the filenames we need. - for _, ls := range ctxt.Textp { - s := loader.Sym(ls) - if !emitPcln(ctxt, s, container) { - continue - } - + for _, s := range funcs { fi := ldr.FuncInfo(s) if !fi.Valid() { continue @@ -382,7 +346,7 @@ func walkFilenames(ctxt *Link, container loader.Bitmap, f func(*sym.CompilationU // 1) Get Func.CUIndex: M := func.cuOffset // 2) Find filename offset: fileOffset := runtime.cutab[M+K] // 3) Get the filename: getcstring(runtime.filetab[fileOffset]) -func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, container loader.Bitmap) []uint32 { +func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.CompilationUnit, funcs []loader.Sym) []uint32 { // On a per-CU basis, keep track of all the filenames we need. // // Note, that we store the filenames in a separate section in the object @@ -402,7 +366,7 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat // file index we've seen per CU so we can calculate how large the // CU->global table needs to be. var fileSize int64 - walkFilenames(ctxt, container, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) { + walkFilenames(ctxt, funcs, func(cu *sym.CompilationUnit, i goobj.CUFileIndex) { // Note we use the raw filename for lookup, but use the expanded filename // when we save the size. filename := cu.FileTable[i] @@ -467,7 +431,7 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat // generatePctab creates the runtime.pctab variable, holding all the // deduplicated pcdata. -func (state *pclntab) generatePctab(ctxt *Link, container loader.Bitmap) { +func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) { ldr := ctxt.loader // Pctab offsets of 0 are considered invalid in the runtime. We respect @@ -490,10 +454,7 @@ func (state *pclntab) generatePctab(ctxt *Link, container loader.Bitmap) { seen[pcSym] = struct{}{} } } - for _, s := range ctxt.Textp { - if !emitPcln(ctxt, s, container) { - continue - } + for _, s := range funcs { fi := ldr.FuncInfo(s) if !fi.Valid() { continue @@ -566,8 +527,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // end PC [thearch.ptrsize bytes] // func structures, pcdata tables. - oldState := makeOldPclnState(ctxt) - state, compUnits := makePclntab(ctxt, container) + state, compUnits, funcs := makePclntab(ctxt, container) ldr := ctxt.loader state.carrier = ldr.LookupOrCreateSym("runtime.pclntab", 0) @@ -579,9 +539,12 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // rational form. state.pclntab = ldr.LookupOrCreateSym("runtime.pclntab_old", 0) state.generatePCHeader(ctxt) - state.generateFuncnametab(ctxt, container) - cuOffsets := state.generateFilenameTabs(ctxt, compUnits, container) - state.generatePctab(ctxt, container) + nameOffsets := state.generateFuncnametab(ctxt, funcs) + cuOffsets := state.generateFilenameTabs(ctxt, compUnits, funcs) + state.generatePctab(ctxt, funcs) + + // Used to when computing defer return. + deferReturnSym := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal) funcdataBytes := int64(0) ldr.SetCarrierSym(state.pclntab, state.carrier) @@ -613,11 +576,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { var nfunc int32 prevFunc := ctxt.Textp[0] - for _, s := range ctxt.Textp { - if !emitPcln(ctxt, s, container) { - continue - } - + for _, s := range funcs { thisSect := ldr.SymSect(s) prevSect := ldr.SymSect(prevFunc) if thisSect != prevSect { @@ -686,7 +645,7 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { off = int32(setAddr(ftab, ctxt.Arch, int64(off), s, 0)) // name int32 - nameoff, ok := state.funcNameOffset[s] + nameoff, ok := nameOffsets[s] if !ok { panic("couldn't find function name offset") } @@ -701,13 +660,13 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { off = int32(ftab.SetUint32(ctxt.Arch, int64(off), args)) // deferreturn - deferreturn := oldState.computeDeferReturn(&ctxt.Target, s) + deferreturn := computeDeferReturn(ctxt, deferReturnSym, s) off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn)) cu := ldr.SymUnit(s) if fi.Valid() && fi.NumInlTree() > 0 { - its := oldState.genInlTreeSym(cu, fi, ctxt.Arch, state) + its := genInlTreeSym(ctxt, cu, fi, ctxt.Arch, nameOffsets) funcdata[objabi.FUNCDATA_InlTree] = its } From 5402d40d5b041399392b29e4543f5fc4506197bd Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Tue, 18 Aug 2020 16:35:26 -0400 Subject: [PATCH 7/9] [dev.link] cmd/link: fix memory growth on dev.link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CL 247399 caused memory growth in the linker. Fix this by adjusting how we preallocate the number of symbols we'll need. cmd/compile (Darwin), alloc/op: Loadlib_GC 33.5MB ± 0% 27.3MB ± 0% Change-Id: I34997329ea4412716114df97fc9dad6ad0c171ee Reviewed-on: https://go-review.googlesource.com/c/go/+/249024 Run-TryBot: Jeremy Faller Reviewed-by: Cherry Zhang Reviewed-by: Austin Clements TryBot-Result: Gobot Gobot --- src/cmd/link/internal/ld/lib.go | 2 +- src/cmd/link/internal/loader/loader.go | 36 +++++++++++++++++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index caa4566190..a01bdefa37 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -543,7 +543,7 @@ func (ctxt *Link) loadlib() { } // Add non-package symbols and references of externally defined symbols. - ctxt.loader.LoadNonpkgSyms(ctxt.Arch) + ctxt.loader.LoadSyms(ctxt.Arch) // Load symbols from shared libraries, after all Go object symbols are loaded. for _, lib := range ctxt.Library { diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index f149e3c831..ea9cd1bd2e 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -328,7 +328,7 @@ func NewLoader(flags uint32, elfsetstring elfsetstringFunc, reporter *ErrorRepor ldr := &Loader{ start: make(map[*oReader]Sym), objs: []objIdx{{}, {extReader, 0}}, // reserve index 0 for nil symbol, 1 for external symbols - objSyms: make([]objSym, 1, 100000), // reserve index 0 for nil symbol + objSyms: make([]objSym, 1, 1), // This will get overwritten later. extReader: extReader, symsByName: [2]map[string]Sym{make(map[string]Sym, 80000), make(map[string]Sym, 50000)}, // preallocate ~2MB for ABI0 and ~1MB for ABI1 symbols objByPkg: make(map[string]*oReader), @@ -2016,8 +2016,9 @@ func (l *Loader) FuncInfo(i Sym) FuncInfo { return FuncInfo{} } -// Preload a package: add autolibs, add defined package symbols to the symbol table. -// Does not add non-package symbols yet, which will be done in LoadNonpkgSyms. +// Preload a package: adds autolib. +// Does not add defined package or non-packaged symbols to the symbol table. +// These are done in LoadSyms. // Does not read symbol data. // Returns the fingerprint of the object. func (l *Loader) Preload(localSymVersion int, f *bio.Reader, lib *sym.Library, unit *sym.CompilationUnit, length int64) goobj.FingerprintType { @@ -2060,8 +2061,6 @@ func (l *Loader) Preload(localSymVersion int, f *bio.Reader, lib *sym.Library, u } l.addObj(lib.Pkg, or) - st := loadState{l: l} - st.preloadSyms(or, pkgDef) // The caller expects us consuming all the data f.MustSeek(length, os.SEEK_CUR) @@ -2144,17 +2143,30 @@ func (st *loadState) preloadSyms(r *oReader, kind int) { } } -// Add hashed (content-addressable) symbols, non-package symbols, and +// Add syms, hashed (content-addressable) symbols, non-package symbols, and // references to external symbols (which are always named). -func (l *Loader) LoadNonpkgSyms(arch *sys.Arch) { +func (l *Loader) LoadSyms(arch *sys.Arch) { + // Allocate space for symbols, making a guess as to how much space we need. + // This function was determined empirically by looking at the cmd/compile on + // Darwin, and picking factors for hashed and hashed64 syms. + var symSize, hashedSize, hashed64Size int + for _, o := range l.objs[goObjStart:] { + symSize += o.r.ndef + o.r.nhasheddef/2 + o.r.nhashed64def/2 + o.r.NNonpkgdef() + hashedSize += o.r.nhasheddef / 2 + hashed64Size += o.r.nhashed64def / 2 + } + // Index 0 is invalid for symbols. + l.objSyms = make([]objSym, 1, symSize) + l.npkgsyms = l.NSym() - // Preallocate some space (a few hundreds KB) for some symbols. - // As of Go 1.15, linking cmd/compile has ~8000 hashed64 symbols and - // ~13000 hashed symbols. st := loadState{ l: l, - hashed64Syms: make(map[uint64]symAndSize, 10000), - hashedSyms: make(map[goobj.HashType]symAndSize, 15000), + hashed64Syms: make(map[uint64]symAndSize, hashed64Size), + hashedSyms: make(map[goobj.HashType]symAndSize, hashedSize), + } + + for _, o := range l.objs[goObjStart:] { + st.preloadSyms(o.r, pkgDef) } for _, o := range l.objs[goObjStart:] { st.preloadSyms(o.r, hashed64Def) From e674b7703e48e357cd46939413620f21cb84027d Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Mon, 28 Sep 2020 14:11:02 -0400 Subject: [PATCH 8/9] [dev.link] cmd/link run generators in parallel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Small runtime win: Stats for darwin, building cmd/compile: Asmb 20.7ms ±14% 18.3ms ±14% -11.54% (p=0.002 n=10+10) TotalTime 365ms ±10% 351ms ± 2% ~ (p=0.211 n=10+9) Change-Id: Ia8afdf6948111d59b0c89e52cb50557a10f33c40 Reviewed-on: https://go-review.googlesource.com/c/go/+/257964 Trust: Jeremy Faller Run-TryBot: Jeremy Faller TryBot-Result: Go Bot Reviewed-by: Austin Clements --- src/cmd/link/internal/ld/main.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index 6f4ccbfb7a..778b0e9245 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -36,12 +36,14 @@ import ( "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/benchmark" + "cmd/link/internal/loader" "flag" "log" "os" "runtime" "runtime/pprof" "strings" + "sync" ) var ( @@ -324,9 +326,15 @@ func Main(arch *sys.Arch, theArch Arch) { bench.Start("Asmb") asmb(ctxt) // Generate large symbols. + var wg sync.WaitGroup for s, f := range ctxt.generatorSyms { - f(ctxt, s) + wg.Add(1) + go func(f generatorFunc, s loader.Sym) { + defer wg.Done() + f(ctxt, s) + }(f, s) } + wg.Wait() bench.Start("Asmb2") asmb2(ctxt) From c863e14a6c15e174ac0979ddd7f9530d6a4ec9cc Mon Sep 17 00:00:00 2001 From: Jeremy Faller Date: Tue, 18 Aug 2020 13:38:04 -0400 Subject: [PATCH 9/9] [dev.link] cmd/link: use generator symbols for the rest of pclntab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the rest of pclntab creation to generator symbols. Any savings in pclntab generation CPU time is eaten by the generators run in Asmb phase. Stats for Darwin, cmd/compile: alloc/op: Pclntab_GC 13.9MB ± 0% 6.4MB ± 0% -53.68% (p=0.000 n=10+10) allocs/op Pclntab_GC 86.5k ± 0% 61.5k ± 0% -28.90% (p=0.000 n=10+10) liveB: Pclntab_GC 24.3M ± 0% 22.9M ± 0% -5.57% (p=0.000 n=10+10) Timing: Pclntab 32.1ms ± 2% 24.2ms ± 2% -24.35% (p=0.000 n=9+9) Asmb 18.3ms ±14% 27.4ms ± 9% +49.55% (p=0.000 n=10+10) TotalTime 351ms ± 2% 347ms ± 3% ~ (p=0.200 n=9+8) Change-Id: I5c6b6df5953f6f255240e07578f1c9f8c5f68500 Reviewed-on: https://go-review.googlesource.com/c/go/+/249023 Trust: Jeremy Faller Run-TryBot: Jeremy Faller TryBot-Result: Go Bot Reviewed-by: Austin Clements --- src/cmd/link/internal/ld/data.go | 4 +- src/cmd/link/internal/ld/pcln.go | 558 +++++++++++++++++++------------ 2 files changed, 350 insertions(+), 212 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 23357e4c1b..5aecdf29b7 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1932,7 +1932,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.cutab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.filetab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pctab", 0), sect) - ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.pclntab_old", 0), sect) + ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.functab", 0), sect) ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.epclntab", 0), sect) if ctxt.HeadType == objabi.Haix { xcoffUpdateOuterSize(ctxt, int64(sect.Length), sym.SPCLNTAB) @@ -2519,7 +2519,7 @@ func (ctxt *Link) address() []*sym.Segment { ctxt.defineInternal("runtime.cutab", sym.SRODATA) ctxt.defineInternal("runtime.filetab", sym.SRODATA) ctxt.defineInternal("runtime.pctab", sym.SRODATA) - ctxt.defineInternal("runtime.pclntab_old", sym.SRODATA) + ctxt.defineInternal("runtime.functab", sym.SRODATA) ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length)) ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr)) ctxt.xdefine("runtime.enoptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr+noptr.Length)) diff --git a/src/cmd/link/internal/ld/pcln.go b/src/cmd/link/internal/ld/pcln.go index 33476ec292..75e63248df 100644 --- a/src/cmd/link/internal/ld/pcln.go +++ b/src/cmd/link/internal/ld/pcln.go @@ -18,6 +18,9 @@ import ( // pclntab holds the state needed for pclntab generation. type pclntab struct { + // The size of the func object in the runtime. + funcSize uint32 + // The first and last functions found. firstFunc, lastFunc loader.Sym @@ -66,7 +69,10 @@ func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, f gen func makePclntab(ctxt *Link, container loader.Bitmap) (*pclntab, []*sym.CompilationUnit, []loader.Sym) { ldr := ctxt.loader - state := &pclntab{} + state := &pclntab{ + // This is the size of the _func object in runtime/runtime2.go. + funcSize: uint32(ctxt.Arch.PtrSize + 9*4), + } // Gather some basic stats and info. seenCUs := make(map[*sym.CompilationUnit]struct{}) @@ -216,6 +222,22 @@ func genInlTreeSym(ctxt *Link, cu *sym.CompilationUnit, fi loader.FuncInfo, arch return its } +// makeInlSyms returns a map of loader.Sym that are created inlSyms. +func makeInlSyms(ctxt *Link, funcs []loader.Sym, nameOffsets map[loader.Sym]uint32) map[loader.Sym]loader.Sym { + ldr := ctxt.loader + // Create the inline symbols we need. + inlSyms := make(map[loader.Sym]loader.Sym) + for _, s := range funcs { + if fi := ldr.FuncInfo(s); fi.Valid() { + fi.Preload() + if fi.NumInlTree() > 0 { + inlSyms[s] = genInlTreeSym(ctxt, ldr.SymUnit(s), fi, ctxt.Arch, nameOffsets) + } + } + } + return inlSyms +} + // generatePCHeader creates the runtime.pcheader symbol, setting it up as a // generator to fill in its data later. func (state *pclntab) generatePCHeader(ctxt *Link) { @@ -488,6 +510,327 @@ func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) { state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab) } +// numPCData returns the number of PCData syms for the FuncInfo. +// NB: Preload must be called on valid FuncInfos before calling this function. +func numPCData(fi loader.FuncInfo) uint32 { + if !fi.Valid() { + return 0 + } + numPCData := uint32(len(fi.Pcdata())) + if fi.NumInlTree() > 0 { + if numPCData < objabi.PCDATA_InlTreeIndex+1 { + numPCData = objabi.PCDATA_InlTreeIndex + 1 + } + } + return numPCData +} + +// Helper types for iterating pclntab. +type pclnSetAddr func(*loader.SymbolBuilder, *sys.Arch, int64, loader.Sym, int64) int64 +type pclnSetUint func(*loader.SymbolBuilder, *sys.Arch, int64, uint64) int64 + +// generateFunctab creates the runtime.functab +// +// runtime.functab contains two things: +// +// - pc->func look up table. +// - array of func objects, interleaved with pcdata and funcdata +// +// Because of timing in the linker, generating this table takes two passes. +// The first pass is executed early in the link, and it creates any needed +// relocations to layout the data. The pieces that need relocations are: +// 1) the PC->func table. +// 2) The entry points in the func objects. +// 3) The funcdata. +// (1) and (2) are handled in walkPCToFunc. (3) is handled in walkFuncdata. +// +// After relocations, once we know where to write things in the output buffer, +// we execute the second pass, which is actually writing the data. +func (state *pclntab) generateFunctab(ctxt *Link, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) { + // Calculate the size of the table. + size, startLocations := state.calculateFunctabSize(ctxt, funcs) + + // If we are internally linking a static executable, the function addresses + // are known, so we can just use them instead of emitting relocations. For + // other cases we still need to emit relocations. + // + // This boolean just helps us figure out which callback to use. + useSymValue := ctxt.IsExe() && ctxt.IsInternal() + + writePcln := func(ctxt *Link, s loader.Sym) { + ldr := ctxt.loader + sb := ldr.MakeSymbolUpdater(s) + + // Create our callbacks. + var setAddr pclnSetAddr + if useSymValue { + // We need to write the offset. + setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { + if v := ldr.SymValue(tgt); v != 0 { + s.SetUint(arch, off, uint64(v+add)) + } + return 0 + } + } else { + // We already wrote relocations. + setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { return 0 } + } + + // Write the data. + writePcToFunc(ctxt, sb, funcs, startLocations, setAddr, (*loader.SymbolBuilder).SetUint) + writeFuncs(ctxt, sb, funcs, inlSyms, startLocations, cuOffsets, nameOffsets) + state.writeFuncData(ctxt, sb, funcs, inlSyms, startLocations, setAddr, (*loader.SymbolBuilder).SetUint) + } + + state.pclntab = state.addGeneratedSym(ctxt, "runtime.functab", size, writePcln) + + // Create the relocations we need. + ldr := ctxt.loader + sb := ldr.MakeSymbolUpdater(state.pclntab) + + var setAddr pclnSetAddr + if useSymValue { + // If we should use the symbol value, and we don't have one, write a relocation. + setAddr = func(sb *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { + if v := ldr.SymValue(tgt); v == 0 { + sb.SetAddrPlus(arch, off, tgt, add) + } + return 0 + } + } else { + // If we're externally linking, write a relocation. + setAddr = (*loader.SymbolBuilder).SetAddrPlus + } + setUintNOP := func(*loader.SymbolBuilder, *sys.Arch, int64, uint64) int64 { return 0 } + writePcToFunc(ctxt, sb, funcs, startLocations, setAddr, setUintNOP) + if !useSymValue { + // Generate relocations for funcdata when externally linking. + state.writeFuncData(ctxt, sb, funcs, inlSyms, startLocations, setAddr, setUintNOP) + } +} + +// funcData returns the funcdata and offsets for the FuncInfo. +// The funcdata and offsets are written into runtime.functab after each func +// object. This is a helper function to make querying the FuncInfo object +// cleaner. +// +// Note, the majority of fdOffsets are 0, meaning there is no offset between +// the compiler's generated symbol, and what the runtime needs. They are +// plumbed through for no loss of generality. +// +// NB: Preload must be called on the FuncInfo before calling. +// NB: fdSyms and fdOffs are used as scratch space. +func funcData(fi loader.FuncInfo, inlSym loader.Sym, fdSyms []loader.Sym, fdOffs []int64) ([]loader.Sym, []int64) { + fdSyms, fdOffs = fdSyms[:0], fdOffs[:0] + if fi.Valid() { + numOffsets := int(fi.NumFuncdataoff()) + for i := 0; i < numOffsets; i++ { + fdOffs = append(fdOffs, fi.Funcdataoff(i)) + } + fdSyms = fi.Funcdata(fdSyms) + if fi.NumInlTree() > 0 { + if len(fdSyms) < objabi.FUNCDATA_InlTree+1 { + fdSyms = append(fdSyms, make([]loader.Sym, objabi.FUNCDATA_InlTree+1-len(fdSyms))...) + fdOffs = append(fdOffs, make([]int64, objabi.FUNCDATA_InlTree+1-len(fdOffs))...) + } + fdSyms[objabi.FUNCDATA_InlTree] = inlSym + } + } + return fdSyms, fdOffs +} + +// calculateFunctabSize calculates the size of the pclntab, and the offsets in +// the output buffer for individual func entries. +func (state pclntab) calculateFunctabSize(ctxt *Link, funcs []loader.Sym) (int64, []uint32) { + ldr := ctxt.loader + startLocations := make([]uint32, len(funcs)) + + // Allocate space for the pc->func table. This structure consists of a pc + // and an offset to the func structure. After that, we have a single pc + // value that marks the end of the last function in the binary. + size := int64(int(state.nfunc)*2*ctxt.Arch.PtrSize + ctxt.Arch.PtrSize) + + // Now find the space for the func objects. We do this in a running manner, + // so that we can find individual starting locations, and because funcdata + // requires alignment. + for i, s := range funcs { + size = Rnd(size, int64(ctxt.Arch.PtrSize)) + startLocations[i] = uint32(size) + fi := ldr.FuncInfo(s) + size += int64(state.funcSize) + if fi.Valid() { + fi.Preload() + numFuncData := int(fi.NumFuncdataoff()) + if fi.NumInlTree() > 0 { + if numFuncData < objabi.FUNCDATA_InlTree+1 { + numFuncData = objabi.FUNCDATA_InlTree + 1 + } + } + size += int64(numPCData(fi) * 4) + if numFuncData > 0 { // Func data is aligned. + size = Rnd(size, int64(ctxt.Arch.PtrSize)) + } + size += int64(numFuncData * ctxt.Arch.PtrSize) + } + } + + return size, startLocations +} + +// writePcToFunc writes the PC->func lookup table. +// This function walks the pc->func lookup table, executing callbacks +// to generate relocations and writing the values for the table. +func writePcToFunc(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, startLocations []uint32, setAddr pclnSetAddr, setUint pclnSetUint) { + ldr := ctxt.loader + var prevFunc loader.Sym + prevSect := ldr.SymSect(funcs[0]) + funcIndex := 0 + for i, s := range funcs { + if thisSect := ldr.SymSect(s); thisSect != prevSect { + // With multiple text sections, there may be a hole here in the + // address space. We use an invalid funcoff value to mark the hole. + // See also runtime/symtab.go:findfunc + prevFuncSize := int64(ldr.SymSize(prevFunc)) + setAddr(sb, ctxt.Arch, int64(funcIndex*2*ctxt.Arch.PtrSize), prevFunc, prevFuncSize) + setUint(sb, ctxt.Arch, int64((funcIndex*2+1)*ctxt.Arch.PtrSize), ^uint64(0)) + funcIndex++ + prevSect = thisSect + } + prevFunc = s + // TODO: We don't actually need these relocations, provided we go to a + // module->func look-up-table like we do for filenames. We could have a + // single relocation for the module, and have them all laid out as + // offsets from the beginning of that module. + setAddr(sb, ctxt.Arch, int64(funcIndex*2*ctxt.Arch.PtrSize), s, 0) + setUint(sb, ctxt.Arch, int64((funcIndex*2+1)*ctxt.Arch.PtrSize), uint64(startLocations[i])) + funcIndex++ + + // Write the entry location. + setAddr(sb, ctxt.Arch, int64(startLocations[i]), s, 0) + } + + // Final entry of table is just end pc. + setAddr(sb, ctxt.Arch, int64(funcIndex)*2*int64(ctxt.Arch.PtrSize), prevFunc, ldr.SymSize(prevFunc)) +} + +// writeFuncData writes the funcdata tables. +// +// This function executes a callback for each funcdata needed in +// runtime.functab. It should be called once for internally linked static +// binaries, or twice (once to generate the needed relocations) for other +// build modes. +// +// Note the output of this function is interwoven with writeFuncs, but this is +// a separate function, because it's needed in different passes in +// generateFunctab. +func (state *pclntab) writeFuncData(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, startLocations []uint32, setAddr pclnSetAddr, setUint pclnSetUint) { + ldr := ctxt.loader + funcdata, funcdataoff := []loader.Sym{}, []int64{} + for i, s := range funcs { + fi := ldr.FuncInfo(s) + if !fi.Valid() { + continue + } + fi.Preload() + + // funcdata, must be pointer-aligned and we're only int32-aligned. + // Missing funcdata will be 0 (nil pointer). + funcdata, funcdataoff := funcData(fi, inlSyms[s], funcdata, funcdataoff) + if len(funcdata) > 0 { + off := int64(startLocations[i] + state.funcSize + numPCData(fi)*4) + off = Rnd(off, int64(ctxt.Arch.PtrSize)) + for j := range funcdata { + dataoff := off + int64(ctxt.Arch.PtrSize*j) + if funcdata[j] == 0 { + setUint(sb, ctxt.Arch, dataoff, uint64(funcdataoff[j])) + continue + } + // TODO: Does this need deduping? + setAddr(sb, ctxt.Arch, dataoff, funcdata[j], funcdataoff[j]) + } + } + } +} + +// writeFuncs writes the func structures and pcdata to runtime.functab. +func writeFuncs(ctxt *Link, sb *loader.SymbolBuilder, funcs []loader.Sym, inlSyms map[loader.Sym]loader.Sym, startLocations, cuOffsets []uint32, nameOffsets map[loader.Sym]uint32) { + ldr := ctxt.loader + deferReturnSym := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal) + funcdata, funcdataoff := []loader.Sym{}, []int64{} + + // Write the individual func objects. + for i, s := range funcs { + fi := ldr.FuncInfo(s) + if fi.Valid() { + fi.Preload() + } + + // Note we skip the space for the entry value -- that's handled inn + // walkPCToFunc. We don't write it here, because it might require a + // relocation. + off := startLocations[i] + uint32(ctxt.Arch.PtrSize) // entry + + // name int32 + nameoff, ok := nameOffsets[s] + if !ok { + panic("couldn't find function name offset") + } + off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(nameoff))) + + // args int32 + // TODO: Move into funcinfo. + args := uint32(0) + if fi.Valid() { + args = uint32(fi.Args()) + } + off = uint32(sb.SetUint32(ctxt.Arch, int64(off), args)) + + // deferreturn + deferreturn := computeDeferReturn(ctxt, deferReturnSym, s) + off = uint32(sb.SetUint32(ctxt.Arch, int64(off), deferreturn)) + + // pcdata + if fi.Valid() { + off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcsp())))) + off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcfile())))) + off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcline())))) + } else { + off += 12 + } + off = uint32(sb.SetUint32(ctxt.Arch, int64(off), uint32(numPCData(fi)))) + + // Store the offset to compilation unit's file table. + cuIdx := ^uint32(0) + if cu := ldr.SymUnit(s); cu != nil { + cuIdx = cuOffsets[cu.PclnIndex] + } + off = uint32(sb.SetUint32(ctxt.Arch, int64(off), cuIdx)) + + // funcID uint8 + var funcID objabi.FuncID + if fi.Valid() { + funcID = fi.FuncID() + } + off = uint32(sb.SetUint8(ctxt.Arch, int64(off), uint8(funcID))) + + off += 2 // pad + + // nfuncdata must be the final entry. + funcdata, funcdataoff = funcData(fi, 0, funcdata, funcdataoff) + off = uint32(sb.SetUint8(ctxt.Arch, int64(off), uint8(len(funcdata)))) + + // Output the pcdata. + if fi.Valid() { + for j, pcSym := range fi.Pcdata() { + sb.SetUint32(ctxt.Arch, int64(off+uint32(j*4)), uint32(ldr.SymValue(pcSym))) + } + if fi.NumInlTree() > 0 { + sb.SetUint32(ctxt.Arch, int64(off+objabi.PCDATA_InlTreeIndex*4), uint32(ldr.SymValue(fi.Pcinline()))) + } + } + } +} + // pclntab initializes the pclntab symbol with // runtime function and file name information. @@ -522,10 +865,10 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { // runtime.pctab // []byte of deduplicated pc data. // - // runtime.pclntab_old + // runtime.functab // function table, alternating PC and offset to func struct [each entry thearch.ptrsize bytes] // end PC [thearch.ptrsize bytes] - // func structures, pcdata tables. + // func structures, pcdata offsets, func data. state, compUnits, funcs := makePclntab(ctxt, container) @@ -534,217 +877,12 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab { ldr.MakeSymbolUpdater(state.carrier).SetType(sym.SPCLNTAB) ldr.SetAttrReachable(state.carrier, true) - // runtime.pclntab_old is just a placeholder,and will eventually be deleted. - // It contains the pieces of runtime.pclntab that haven't moved to a more - // rational form. - state.pclntab = ldr.LookupOrCreateSym("runtime.pclntab_old", 0) state.generatePCHeader(ctxt) nameOffsets := state.generateFuncnametab(ctxt, funcs) cuOffsets := state.generateFilenameTabs(ctxt, compUnits, funcs) state.generatePctab(ctxt, funcs) - - // Used to when computing defer return. - deferReturnSym := ldr.Lookup("runtime.deferreturn", sym.SymVerABIInternal) - - funcdataBytes := int64(0) - ldr.SetCarrierSym(state.pclntab, state.carrier) - ldr.SetAttrNotInSymbolTable(state.pclntab, true) - ftab := ldr.MakeSymbolUpdater(state.pclntab) - ftab.SetValue(state.size) - ftab.SetType(sym.SPCLNTAB) - ftab.SetReachable(true) - - ftab.Grow(int64(state.nfunc)*2*int64(ctxt.Arch.PtrSize) + int64(ctxt.Arch.PtrSize) + 4) - - setAddr := (*loader.SymbolBuilder).SetAddrPlus - if ctxt.IsExe() && ctxt.IsInternal() { - // Internal linking static executable. At this point the function - // addresses are known, so we can just use them instead of emitting - // relocations. - // For other cases we are generating a relocatable binary so we - // still need to emit relocations. - setAddr = func(s *loader.SymbolBuilder, arch *sys.Arch, off int64, tgt loader.Sym, add int64) int64 { - if v := ldr.SymValue(tgt); v != 0 { - return s.SetUint(arch, off, uint64(v+add)) - } - return s.SetAddrPlus(arch, off, tgt, add) - } - } - - funcdata := []loader.Sym{} - funcdataoff := []int64{} - - var nfunc int32 - prevFunc := ctxt.Textp[0] - for _, s := range funcs { - thisSect := ldr.SymSect(s) - prevSect := ldr.SymSect(prevFunc) - if thisSect != prevSect { - // With multiple text sections, there may be a hole here - // in the address space (see the comment above). We use an - // invalid funcoff value to mark the hole. See also - // runtime/symtab.go:findfunc - prevFuncSize := int64(ldr.SymSize(prevFunc)) - setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), prevFunc, prevFuncSize) - ftab.SetUint(ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), ^uint64(0)) - nfunc++ - } - prevFunc = s - - var numPCData int32 - funcdataoff = funcdataoff[:0] - funcdata = funcdata[:0] - fi := ldr.FuncInfo(s) - if fi.Valid() { - fi.Preload() - numPCData = int32(len(fi.Pcdata())) - nfd := fi.NumFuncdataoff() - for i := uint32(0); i < nfd; i++ { - funcdataoff = append(funcdataoff, fi.Funcdataoff(int(i))) - } - funcdata = fi.Funcdata(funcdata) - } - - writeInlPCData := false - if fi.Valid() && fi.NumInlTree() > 0 { - writeInlPCData = true - if numPCData <= objabi.PCDATA_InlTreeIndex { - numPCData = objabi.PCDATA_InlTreeIndex + 1 - } - if len(funcdataoff) <= objabi.FUNCDATA_InlTree { - // Create inline tree funcdata. - newfuncdata := make([]loader.Sym, objabi.FUNCDATA_InlTree+1) - newfuncdataoff := make([]int64, objabi.FUNCDATA_InlTree+1) - copy(newfuncdata, funcdata) - copy(newfuncdataoff, funcdataoff) - funcdata = newfuncdata - funcdataoff = newfuncdataoff - } - } - - dSize := len(ftab.Data()) - funcstart := int32(dSize) - funcstart += int32(-dSize) & (int32(ctxt.Arch.PtrSize) - 1) // align to ptrsize - - setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), s, 0) - ftab.SetUint(ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize)+int64(ctxt.Arch.PtrSize), uint64(funcstart)) - - // Write runtime._func. Keep in sync with ../../../../runtime/runtime2.go:/_func - // and package debug/gosym. - - // fixed size of struct, checked below - off := funcstart - - end := funcstart + int32(ctxt.Arch.PtrSize) + 3*4 + 6*4 + numPCData*4 + int32(len(funcdata))*int32(ctxt.Arch.PtrSize) - if len(funcdata) > 0 && (end&int32(ctxt.Arch.PtrSize-1) != 0) { - end += 4 - } - ftab.Grow(int64(end)) - - // entry uintptr - off = int32(setAddr(ftab, ctxt.Arch, int64(off), s, 0)) - - // name int32 - nameoff, ok := nameOffsets[s] - if !ok { - panic("couldn't find function name offset") - } - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(nameoff))) - - // args int32 - // TODO: Move into funcinfo. - args := uint32(0) - if fi.Valid() { - args = uint32(fi.Args()) - } - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), args)) - - // deferreturn - deferreturn := computeDeferReturn(ctxt, deferReturnSym, s) - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), deferreturn)) - - cu := ldr.SymUnit(s) - - if fi.Valid() && fi.NumInlTree() > 0 { - its := genInlTreeSym(ctxt, cu, fi, ctxt.Arch, nameOffsets) - funcdata[objabi.FUNCDATA_InlTree] = its - } - - // pcdata - if fi.Valid() { - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcsp())))) - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcfile())))) - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(ldr.SymValue(fi.Pcline())))) - } else { - off += 12 - } - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), uint32(numPCData))) - - // Store the offset to compilation unit's file table. - cuIdx := ^uint32(0) - if cu := ldr.SymUnit(s); cu != nil { - cuIdx = cuOffsets[cu.PclnIndex] - } - off = int32(ftab.SetUint32(ctxt.Arch, int64(off), cuIdx)) - - // funcID uint8 - var funcID objabi.FuncID - if fi.Valid() { - funcID = fi.FuncID() - } - off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(funcID))) - - off += 2 // pad - - // nfuncdata must be the final entry. - off = int32(ftab.SetUint8(ctxt.Arch, int64(off), uint8(len(funcdata)))) - - // Output the pcdata. - if fi.Valid() { - for i, pcSym := range fi.Pcdata() { - ftab.SetUint32(ctxt.Arch, int64(off+int32(i*4)), uint32(ldr.SymValue(pcSym))) - } - if writeInlPCData { - ftab.SetUint32(ctxt.Arch, int64(off+objabi.PCDATA_InlTreeIndex*4), uint32(ldr.SymValue(fi.Pcinline()))) - } - } - off += numPCData * 4 - - // funcdata, must be pointer-aligned and we're only int32-aligned. - // Missing funcdata will be 0 (nil pointer). - if len(funcdata) > 0 { - if off&int32(ctxt.Arch.PtrSize-1) != 0 { - off += 4 - } - for i := range funcdata { - dataoff := int64(off) + int64(ctxt.Arch.PtrSize)*int64(i) - if funcdata[i] == 0 { - ftab.SetUint(ctxt.Arch, dataoff, uint64(funcdataoff[i])) - continue - } - // TODO: Dedup. - funcdataBytes += int64(len(ldr.Data(funcdata[i]))) - setAddr(ftab, ctxt.Arch, dataoff, funcdata[i], funcdataoff[i]) - } - off += int32(len(funcdata)) * int32(ctxt.Arch.PtrSize) - } - - if off != end { - ctxt.Errorf(s, "bad math in functab: funcstart=%d off=%d but end=%d (npcdata=%d nfuncdata=%d ptrsize=%d)", funcstart, off, end, numPCData, len(funcdata), ctxt.Arch.PtrSize) - errorexit() - } - - nfunc++ - } - - // Final entry of table is just end pc. - setAddr(ftab, ctxt.Arch, int64(nfunc)*2*int64(ctxt.Arch.PtrSize), state.lastFunc, ldr.SymSize(state.lastFunc)) - - ftab.SetSize(int64(len(ftab.Data()))) - - if ctxt.Debugvlog != 0 { - ctxt.Logf("pclntab=%d bytes, funcdata total %d bytes\n", ftab.Size(), funcdataBytes) - } + inlSyms := makeInlSyms(ctxt, funcs, nameOffsets) + state.generateFunctab(ctxt, funcs, inlSyms, cuOffsets, nameOffsets) return state }