From 9ac471a87d8a7fdd1741bdfd48399e7215023989 Mon Sep 17 00:00:00 2001 From: Than McIntosh Date: Thu, 25 Apr 2019 10:35:47 -0400 Subject: [PATCH] cmd/link: use read-only mmap to back selected symbol name strings When reading symbol names from an object file, if a name does not need fixup (conversion of "". to package path), then generate a string whose backing store is in read-only memory (from an mmap of the object file), avoiding the need for an allocation. This yields a modest reduction in total linker heap use. Change-Id: I95719c93026b6cc82eb6947a9d14063cf3a6679c Reviewed-on: https://go-review.googlesource.com/c/go/+/173938 Run-TryBot: Than McIntosh Reviewed-by: Brad Fitzpatrick Reviewed-by: Cherry Zhang TryBot-Result: Gobot Gobot --- src/cmd/internal/bio/buf.go | 12 ++++++ src/cmd/link/internal/objfile/objfile.go | 48 +++++++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/cmd/internal/bio/buf.go b/src/cmd/internal/bio/buf.go index 388105c3c7..544f7edca9 100644 --- a/src/cmd/internal/bio/buf.go +++ b/src/cmd/internal/bio/buf.go @@ -129,3 +129,15 @@ func (r *Reader) Slice(length uint64) ([]byte, bool, error) { } return data, false, nil } + +// SliceRO returns a slice containing the next length bytes of r +// backed by a read-only mmap'd data. If the mmap cannot be +// established (limit exceeded, region too small, etc) a nil slice +// will be returned. If mmap succeeds, it will never be unmapped. +func (r *Reader) SliceRO(length uint64) []byte { + data, ok := r.sliceOS(length) + if ok { + return data + } + return nil +} diff --git a/src/cmd/link/internal/objfile/objfile.go b/src/cmd/link/internal/objfile/objfile.go index 4f30f58dfc..7db4ac974b 100644 --- a/src/cmd/link/internal/objfile/objfile.go +++ b/src/cmd/link/internal/objfile/objfile.go @@ -23,6 +23,7 @@ import ( "os" "strconv" "strings" + "unsafe" ) const ( @@ -58,6 +59,9 @@ type objReader struct { funcdataoff []int64 file []*sym.Symbol + roObject []byte // from read-only mmap of object file + objFileOffset int64 // offset of object data from start of file + dataReadOnly bool // whether data is backed by read-only memory } @@ -78,6 +82,10 @@ const ( // The symbols loaded are added to syms. func Load(arch *sys.Arch, syms *sym.Symbols, f *bio.Reader, lib *sym.Library, length int64, pn string, flags int) int { start := f.Offset() + roObject := f.SliceRO(uint64(length)) + if roObject != nil { + f.Seek(int64(-length), os.SEEK_CUR) + } r := &objReader{ rd: f, lib: lib, @@ -87,6 +95,8 @@ func Load(arch *sys.Arch, syms *sym.Symbols, f *bio.Reader, lib *sym.Library, le dupSym: &sym.Symbol{Name: ".dup"}, localSymVersion: syms.IncVersion(), flags: flags, + roObject: roObject, + objFileOffset: start, } r.loadObjFile() if f.Offset() != start+length { @@ -136,7 +146,7 @@ func (r *objReader) loadObjFile() { r.readSlices() // Data section - r.data, r.dataReadOnly, err = r.rd.Slice(uint64(r.dataSize)) + err = r.readDataSection() if err != nil { log.Fatalf("%s: error reading %s", r.pn, err) } @@ -176,6 +186,18 @@ func (r *objReader) readSlices() { r.file = make([]*sym.Symbol, n) } +func (r *objReader) readDataSection() (err error) { + if r.roObject != nil { + dOffset := r.rd.Offset() - r.objFileOffset + r.data, r.dataReadOnly, err = + r.roObject[dOffset:dOffset+int64(r.dataSize)], true, nil + r.rd.Seek(int64(r.dataSize), os.SEEK_CUR) + return + } + r.data, r.dataReadOnly, err = r.rd.Slice(uint64(r.dataSize)) + return +} + // Symbols are prefixed so their content doesn't get confused with the magic footer. const symPrefix = 0xfe @@ -544,6 +566,20 @@ func (r *objReader) readData() []byte { return p } +type stringHeader struct { + str unsafe.Pointer + len int +} + +func mkROString(rodata []byte) string { + if len(rodata) == 0 { + return "" + } + ss := stringHeader{str: unsafe.Pointer(&rodata[0]), len: len(rodata)} + s := *(*string)(unsafe.Pointer(&ss)) + return s +} + // readSymName reads a symbol name, replacing all "". with pkg. func (r *objReader) readSymName() string { pkg := objabi.PathToPrefix(r.lib.Pkg) @@ -555,6 +591,7 @@ func (r *objReader) readSymName() string { if cap(r.rdBuf) < n { r.rdBuf = make([]byte, 2*n) } + sOffset := r.rd.Offset() - r.objFileOffset origName, err := r.rd.Peek(n) if err == bufio.ErrBufferFull { // Long symbol names are rare but exist. One source is type @@ -565,10 +602,16 @@ func (r *objReader) readSymName() string { log.Fatalf("%s: error reading symbol: %v", r.pn, err) } adjName := r.rdBuf[:0] + nPkgRefs := 0 for { i := bytes.Index(origName, emptyPkg) if i == -1 { - s := string(append(adjName, origName...)) + var s string + if r.roObject != nil && nPkgRefs == 0 { + s = mkROString(r.roObject[sOffset : sOffset+int64(n)]) + } else { + s = string(append(adjName, origName...)) + } // Read past the peeked origName, now that we're done with it, // using the rfBuf (also no longer used) as the scratch space. // TODO: use bufio.Reader.Discard if available instead? @@ -578,6 +621,7 @@ func (r *objReader) readSymName() string { r.rdBuf = adjName[:0] // in case 2*n wasn't enough return s } + nPkgRefs++ adjName = append(adjName, origName[:i]...) adjName = append(adjName, pkg...) adjName = append(adjName, '.') -- 2.50.0