[dev.link] cmd/link: stop renumbering files for pclntab generation

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 <jeremy@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Austin Clements <austin@google.com>
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index dc7096e..a551d46 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -1923,6 +1923,8 @@
 	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 @@
 	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 a2c8552..f26d051 100644
--- a/src/cmd/link/internal/ld/link.go
+++ b/src/cmd/link/internal/ld/link.go
@@ -71,7 +71,6 @@
 	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 30e0bdc..c7535f6 100644
--- a/src/cmd/link/internal/ld/pcln.go
+++ b/src/cmd/link/internal/ld/pcln.go
@@ -6,16 +6,11 @@
 
 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 @@
 
 // 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 @@
 	// 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 @@
 	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 @@
 	// 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 @@
 	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 @@
 	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 @@
 	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 @@
 		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 @@
 	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 @@
 	// 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 @@
 	//        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 @@
 	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 @@
 		// 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 @@
 			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 @@
 		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 @@
 		}
 		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 @@
 	// 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 bc88095..d05b98f 100644
--- a/src/cmd/link/internal/ld/symtab.go
+++ b/src/cmd/link/internal/ld/symtab.go
@@ -619,6 +619,14 @@
 	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 @@
 	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 e5c5052..e383ea4 100644
--- a/src/debug/gosym/pclntab.go
+++ b/src/debug/gosym/pclntab.go
@@ -53,6 +53,7 @@
 	quantum     uint32
 	ptrsize     uint32
 	funcnametab []byte
+	cutab       []byte
 	funcdata    []byte
 	functab     []byte
 	nfunctab    uint32
@@ -223,17 +224,18 @@
 	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 @@
 	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 @@
 	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 @@
 	}
 	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 0bddcaa..5a79c7e 100644
--- a/src/runtime/runtime2.go
+++ b/src/runtime/runtime2.go
@@ -804,9 +804,10 @@
 	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 ddb5ea8..fbd9315 100644
--- a/src/runtime/symtab.go
+++ b/src/runtime/symtab.go
@@ -334,14 +334,17 @@
 	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 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 @@
 	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 @@
 		// 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 @@
 	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)