diff options
Diffstat (limited to 'src/cmd/link/internal/ld/outbuf.go')
-rw-r--r-- | src/cmd/link/internal/ld/outbuf.go | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/src/cmd/link/internal/ld/outbuf.go b/src/cmd/link/internal/ld/outbuf.go new file mode 100644 index 0000000..530836e --- /dev/null +++ b/src/cmd/link/internal/ld/outbuf.go @@ -0,0 +1,311 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ld + +import ( + "cmd/internal/sys" + "cmd/link/internal/loader" + "encoding/binary" + "errors" + "log" + "os" +) + +// If fallocate is not supported on this platform, return this error. The error +// is ignored where needed, and OutBuf writes to heap memory. +var errNoFallocate = errors.New("operation not supported") + +const outbufMode = 0775 + +// OutBuf is a buffered file writer. +// +// It is simlar to the Writer in cmd/internal/bio with a few small differences. +// +// First, it tracks the output architecture and uses it to provide +// endian helpers. +// +// Second, it provides a very cheap offset counter that doesn't require +// any system calls to read the value. +// +// Third, it also mmaps the output file (if available). The intended usage is: +// - Mmap the output file +// - Write the content +// - possibly apply any edits in the output buffer +// - possibly write more content to the file. These writes take place in a heap +// backed buffer that will get synced to disk. +// - Munmap the output file +// +// And finally, it provides a mechanism by which you can multithread the +// writing of output files. This mechanism is accomplished by copying a OutBuf, +// and using it in the thread/goroutine. +// +// Parallel OutBuf is intended to be used like: +// +// func write(out *OutBuf) { +// var wg sync.WaitGroup +// for i := 0; i < 10; i++ { +// wg.Add(1) +// view, err := out.View(start[i]) +// if err != nil { +// // handle output +// continue +// } +// go func(out *OutBuf, i int) { +// // do output +// wg.Done() +// }(view, i) +// } +// wg.Wait() +// } +type OutBuf struct { + arch *sys.Arch + off int64 + + buf []byte // backing store of mmap'd output file + heap []byte // backing store for non-mmapped data + + name string + f *os.File + encbuf [8]byte // temp buffer used by WriteN methods + isView bool // true if created from View() +} + +func (out *OutBuf) Open(name string) error { + if out.f != nil { + return errors.New("cannot open more than one file") + } + f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, outbufMode) + if err != nil { + return err + } + out.off = 0 + out.name = name + out.f = f + return nil +} + +func NewOutBuf(arch *sys.Arch) *OutBuf { + return &OutBuf{ + arch: arch, + } +} + +var viewError = errors.New("output not mmapped") + +func (out *OutBuf) View(start uint64) (*OutBuf, error) { + return &OutBuf{ + arch: out.arch, + name: out.name, + buf: out.buf, + heap: out.heap, + off: int64(start), + isView: true, + }, nil +} + +var viewCloseError = errors.New("cannot Close OutBuf from View") + +func (out *OutBuf) Close() error { + if out.isView { + return viewCloseError + } + if out.isMmapped() { + out.copyHeap() + out.purgeSignatureCache() + out.munmap() + } + if out.f == nil { + return nil + } + if len(out.heap) != 0 { + if _, err := out.f.Write(out.heap); err != nil { + return err + } + } + if err := out.f.Close(); err != nil { + return err + } + out.f = nil + return nil +} + +// isMmapped returns true if the OutBuf is mmaped. +func (out *OutBuf) isMmapped() bool { + return len(out.buf) != 0 +} + +// Data returns the whole written OutBuf as a byte slice. +func (out *OutBuf) Data() []byte { + if out.isMmapped() { + out.copyHeap() + return out.buf + } + return out.heap +} + +// copyHeap copies the heap to the mmapped section of memory, returning true if +// a copy takes place. +func (out *OutBuf) copyHeap() bool { + if !out.isMmapped() { // only valuable for mmapped OutBufs. + return false + } + if out.isView { + panic("can't copyHeap a view") + } + + bufLen := len(out.buf) + heapLen := len(out.heap) + total := uint64(bufLen + heapLen) + if heapLen != 0 { + if err := out.Mmap(total); err != nil { // Mmap will copy out.heap over to out.buf + panic(err) + } + } + return true +} + +// maxOutBufHeapLen limits the growth of the heap area. +const maxOutBufHeapLen = 10 << 20 + +// writeLoc determines the write location if a buffer is mmaped. +// We maintain two write buffers, an mmapped section, and a heap section for +// writing. When the mmapped section is full, we switch over the heap memory +// for writing. +func (out *OutBuf) writeLoc(lenToWrite int64) (int64, []byte) { + // See if we have enough space in the mmaped area. + bufLen := int64(len(out.buf)) + if out.off+lenToWrite <= bufLen { + return out.off, out.buf + } + + // Not enough space in the mmaped area, write to heap area instead. + heapPos := out.off - bufLen + heapLen := int64(len(out.heap)) + lenNeeded := heapPos + lenToWrite + if lenNeeded > heapLen { // do we need to grow the heap storage? + // The heap variables aren't protected by a mutex. For now, just bomb if you + // try to use OutBuf in parallel. (Note this probably could be fixed.) + if out.isView { + panic("cannot write to heap in parallel") + } + // See if our heap would grow to be too large, and if so, copy it to the end + // of the mmapped area. + if heapLen > maxOutBufHeapLen && out.copyHeap() { + heapPos -= heapLen + lenNeeded = heapPos + lenToWrite + heapLen = 0 + } + out.heap = append(out.heap, make([]byte, lenNeeded-heapLen)...) + } + return heapPos, out.heap +} + +func (out *OutBuf) SeekSet(p int64) { + out.off = p +} + +func (out *OutBuf) Offset() int64 { + return out.off +} + +// Write writes the contents of v to the buffer. +func (out *OutBuf) Write(v []byte) (int, error) { + n := len(v) + pos, buf := out.writeLoc(int64(n)) + copy(buf[pos:], v) + out.off += int64(n) + return n, nil +} + +func (out *OutBuf) Write8(v uint8) { + pos, buf := out.writeLoc(1) + buf[pos] = v + out.off++ +} + +// WriteByte is an alias for Write8 to fulfill the io.ByteWriter interface. +func (out *OutBuf) WriteByte(v byte) error { + out.Write8(v) + return nil +} + +func (out *OutBuf) Write16(v uint16) { + out.arch.ByteOrder.PutUint16(out.encbuf[:], v) + out.Write(out.encbuf[:2]) +} + +func (out *OutBuf) Write32(v uint32) { + out.arch.ByteOrder.PutUint32(out.encbuf[:], v) + out.Write(out.encbuf[:4]) +} + +func (out *OutBuf) Write32b(v uint32) { + binary.BigEndian.PutUint32(out.encbuf[:], v) + out.Write(out.encbuf[:4]) +} + +func (out *OutBuf) Write64(v uint64) { + out.arch.ByteOrder.PutUint64(out.encbuf[:], v) + out.Write(out.encbuf[:8]) +} + +func (out *OutBuf) Write64b(v uint64) { + binary.BigEndian.PutUint64(out.encbuf[:], v) + out.Write(out.encbuf[:8]) +} + +func (out *OutBuf) WriteString(s string) { + pos, buf := out.writeLoc(int64(len(s))) + n := copy(buf[pos:], s) + if n != len(s) { + log.Fatalf("WriteString truncated. buffer size: %d, offset: %d, len(s)=%d", len(out.buf), out.off, len(s)) + } + out.off += int64(n) +} + +// WriteStringN writes the first n bytes of s. +// If n is larger than len(s) then it is padded with zero bytes. +func (out *OutBuf) WriteStringN(s string, n int) { + out.WriteStringPad(s, n, zeros[:]) +} + +// WriteStringPad writes the first n bytes of s. +// If n is larger than len(s) then it is padded with the bytes in pad (repeated as needed). +func (out *OutBuf) WriteStringPad(s string, n int, pad []byte) { + if len(s) >= n { + out.WriteString(s[:n]) + } else { + out.WriteString(s) + n -= len(s) + for n > len(pad) { + out.Write(pad) + n -= len(pad) + + } + out.Write(pad[:n]) + } +} + +// WriteSym writes the content of a Symbol, and returns the output buffer +// that we just wrote, so we can apply further edit to the symbol content. +// For generator symbols, it also sets the symbol's Data to the output +// buffer. +func (out *OutBuf) WriteSym(ldr *loader.Loader, s loader.Sym) []byte { + if !ldr.IsGeneratedSym(s) { + P := ldr.Data(s) + n := int64(len(P)) + pos, buf := out.writeLoc(n) + copy(buf[pos:], P) + out.off += n + ldr.FreeData(s) + return buf[pos : pos+n] + } else { + n := ldr.SymSize(s) + pos, buf := out.writeLoc(n) + out.off += n + ldr.MakeSymbolUpdater(s).SetData(buf[pos : pos+n]) + return buf[pos : pos+n] + } +} |