From 0d7404c8527dc45469644c0f6b4becfd59c2a4d9 Mon Sep 17 00:00:00 2001 From: Cherry Zhang Date: Fri, 27 Sep 2019 14:49:44 -0400 Subject: [PATCH] [dev.link] cmd/link, cmd/internal/goobj2: mmap object file in -newobj mode With the old object file format, we use mmap (if supported) to read object files and back symbol data with mapped read-only memory. Do the same with the new object file format. This also significantly reduces number of syscalls made to read object files. Currently we still do mmap in object file level, not archive level. This is probably ok, as there shouldn't be many archives that contain more than one object. If this is a problem we can change that later. Change-Id: Icae3ef14d8ed6adbee1b5b48d420e2af22fd9604 Reviewed-on: https://go-review.googlesource.com/c/go/+/197797 Run-TryBot: Cherry Zhang TryBot-Result: Gobot Gobot Reviewed-by: Than McIntosh --- src/cmd/internal/goobj2/objfile.go | 81 +++++++++++++++-------- src/cmd/link/internal/ld/lib.go | 2 +- src/cmd/link/internal/objfile/objfile2.go | 9 ++- 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/src/cmd/internal/goobj2/objfile.go b/src/cmd/internal/goobj2/objfile.go index eb9290a699..4c1bbe83f0 100644 --- a/src/cmd/internal/goobj2/objfile.go +++ b/src/cmd/internal/goobj2/objfile.go @@ -7,11 +7,13 @@ package goobj2 // TODO: replace the goobj package? import ( + "bytes" "cmd/internal/bio" "encoding/binary" "errors" "fmt" "io" + "unsafe" ) // New object file format. @@ -354,6 +356,9 @@ func (w *Writer) Offset() uint32 { } type Reader struct { + b []byte // mmapped bytes, if not nil + readonly bool // whether b is backed with read-only memory + rd io.ReaderAt start uint32 h Header // keep block offsets @@ -368,10 +373,25 @@ func NewReader(rd io.ReaderAt, off uint32) *Reader { return r } +func NewReaderFromBytes(b []byte, readonly bool) *Reader { + r := &Reader{b: b, readonly: readonly, rd: bytes.NewReader(b), start: 0} + err := r.h.Read(r) + if err != nil { + return nil + } + return r +} + func (r *Reader) BytesAt(off uint32, len int) []byte { - // TODO: read from mapped memory + if len == 0 { + return nil + } + if r.b != nil { + end := int(off) + len + return r.b[int(off):end:end] + } b := make([]byte, len) - _, err := r.rd.ReadAt(b[:], int64(r.start+off)) + _, err := r.rd.ReadAt(b, int64(r.start+off)) if err != nil { panic("corrupted input") } @@ -379,12 +399,8 @@ func (r *Reader) BytesAt(off uint32, len int) []byte { } func (r *Reader) uint64At(off uint32) uint64 { - var b [8]byte - n, err := r.rd.ReadAt(b[:], int64(r.start+off)) - if n != 8 || err != nil { - panic("corrupted input") - } - return binary.LittleEndian.Uint64(b[:]) + b := r.BytesAt(off, 8) + return binary.LittleEndian.Uint64(b) } func (r *Reader) int64At(off uint32) int64 { @@ -392,12 +408,8 @@ func (r *Reader) int64At(off uint32) int64 { } func (r *Reader) uint32At(off uint32) uint32 { - var b [4]byte - n, err := r.rd.ReadAt(b[:], int64(r.start+off)) - if n != 4 || err != nil { - panic("corrupted input") - } - return binary.LittleEndian.Uint32(b[:]) + b := r.BytesAt(off, 4) + return binary.LittleEndian.Uint32(b) } func (r *Reader) int32At(off uint32) int32 { @@ -405,26 +417,24 @@ func (r *Reader) int32At(off uint32) int32 { } func (r *Reader) uint16At(off uint32) uint16 { - var b [2]byte - n, err := r.rd.ReadAt(b[:], int64(r.start+off)) - if n != 2 || err != nil { - panic("corrupted input") - } - return binary.LittleEndian.Uint16(b[:]) + b := r.BytesAt(off, 2) + return binary.LittleEndian.Uint16(b) } func (r *Reader) uint8At(off uint32) uint8 { - var b [1]byte - n, err := r.rd.ReadAt(b[:], int64(r.start+off)) - if n != 1 || err != nil { - panic("corrupted input") - } + b := r.BytesAt(off, 1) return b[0] } func (r *Reader) StringAt(off uint32) string { - // TODO: have some way to construct a string without copy l := r.uint32At(off) + if r.b != nil { + b := r.b[off+4 : off+4+l] + if r.readonly { + return toString(b) // backed by RO memory, ok to make unsafe string + } + return string(b) + } b := make([]byte, l) n, err := r.rd.ReadAt(b, int64(r.start+off+4)) if n != int(l) || err != nil { @@ -433,6 +443,20 @@ func (r *Reader) StringAt(off uint32) string { return string(b) } +func toString(b []byte) string { + type stringHeader struct { + str unsafe.Pointer + len int + } + + if len(b) == 0 { + return "" + } + ss := stringHeader{str: unsafe.Pointer(&b[0]), len: len(b)} + s := *(*string)(unsafe.Pointer(&ss)) + return s +} + func (r *Reader) StringRef(off uint32) string { return r.StringAt(r.uint32At(off)) } @@ -511,3 +535,8 @@ func (r *Reader) DataSize(i int) int { func (r *Reader) PcdataBase() uint32 { return r.h.Offsets[BlkPcdata] } + +// ReadOnly returns whether r.BytesAt returns read-only bytes. +func (r *Reader) ReadOnly() bool { + return r.readonly +} diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 2ebd5d333c..d10933ae43 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -835,7 +835,7 @@ func loadobjfile(ctxt *Link, lib *sym.Library) { if err != nil { Exitf("cannot open file %s: %v", lib.File, err) } - //defer f.Close() + defer f.Close() defer func() { if pkg == "main" && !lib.Main { Exitf("%s: not package main", lib.File) diff --git a/src/cmd/link/internal/objfile/objfile2.go b/src/cmd/link/internal/objfile/objfile2.go index a5bd91d3ab..252615febc 100644 --- a/src/cmd/link/internal/objfile/objfile2.go +++ b/src/cmd/link/internal/objfile/objfile2.go @@ -124,8 +124,11 @@ func (l *Loader) Lookup(name string, ver int) int { // Preload a package: add autolibs, add symbols to the symbol table. // Does not read symbol data yet. func LoadNew(l *Loader, arch *sys.Arch, syms *sym.Symbols, f *bio.Reader, lib *sym.Library, unit *sym.CompilationUnit, length int64, pn string, flags int) { - start := f.Offset() - r := goobj2.NewReader(f.File(), uint32(start)) + roObject, readonly, err := f.Slice(uint64(length)) + if err != nil { + log.Fatal("cannot read object file:", err) + } + r := goobj2.NewReaderFromBytes(roObject, readonly) if r == nil { panic("cannot read object file") } @@ -314,6 +317,7 @@ func LoadReloc(l *Loader, r *goobj2.Reader, lib *sym.Library, localSymVersion in // XXX deadcode needs symbol data for type symbols. Read it now. if strings.HasPrefix(name, "type.") { s.P = r.BytesAt(r.DataOff(i), r.DataSize(i)) + s.Attr.Set(sym.AttrReadOnly, r.ReadOnly()) s.Size = int64(osym.Siz) } @@ -422,6 +426,7 @@ func LoadFull(l *Loader, r *goobj2.Reader, lib *sym.Library, localSymVersion int // Symbol data s.P = r.BytesAt(r.DataOff(i), datasize) + s.Attr.Set(sym.AttrReadOnly, r.ReadOnly()) // Aux symbol info isym := -1 -- 2.48.1