github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/link/ld/outbuf.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ld
     6  
     7  import (
     8  	"encoding/binary"
     9  	"errors"
    10  	"log"
    11  	"os"
    12  
    13  	"github.com/go-asm/go/cmd/link/loader"
    14  	"github.com/go-asm/go/cmd/sys"
    15  )
    16  
    17  // If fallocate is not supported on this platform, return this error. The error
    18  // is ignored where needed, and OutBuf writes to heap memory.
    19  var errNoFallocate = errors.New("operation not supported")
    20  
    21  const outbufMode = 0775
    22  
    23  // OutBuf is a buffered file writer.
    24  //
    25  // It is similar to the Writer in github.com/go-asm/go/cmd/bio with a few small differences.
    26  //
    27  // First, it tracks the output architecture and uses it to provide
    28  // endian helpers.
    29  //
    30  // Second, it provides a very cheap offset counter that doesn't require
    31  // any system calls to read the value.
    32  //
    33  // Third, it also mmaps the output file (if available). The intended usage is:
    34  //   - Mmap the output file
    35  //   - Write the content
    36  //   - possibly apply any edits in the output buffer
    37  //   - possibly write more content to the file. These writes take place in a heap
    38  //     backed buffer that will get synced to disk.
    39  //   - Munmap the output file
    40  //
    41  // And finally, it provides a mechanism by which you can multithread the
    42  // writing of output files. This mechanism is accomplished by copying a OutBuf,
    43  // and using it in the thread/goroutine.
    44  //
    45  // Parallel OutBuf is intended to be used like:
    46  //
    47  //	func write(out *OutBuf) {
    48  //	  var wg sync.WaitGroup
    49  //	  for i := 0; i < 10; i++ {
    50  //	    wg.Add(1)
    51  //	    view, err := out.View(start[i])
    52  //	    if err != nil {
    53  //	       // handle output
    54  //	       continue
    55  //	    }
    56  //	    go func(out *OutBuf, i int) {
    57  //	      // do output
    58  //	      wg.Done()
    59  //	    }(view, i)
    60  //	  }
    61  //	  wg.Wait()
    62  //	}
    63  type OutBuf struct {
    64  	arch *sys.Arch
    65  	off  int64
    66  
    67  	buf  []byte // backing store of mmap'd output file
    68  	heap []byte // backing store for non-mmapped data
    69  
    70  	name   string
    71  	f      *os.File
    72  	encbuf [8]byte // temp buffer used by WriteN methods
    73  	isView bool    // true if created from View()
    74  }
    75  
    76  func (out *OutBuf) Open(name string) error {
    77  	if out.f != nil {
    78  		return errors.New("cannot open more than one file")
    79  	}
    80  	f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, outbufMode)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	out.off = 0
    85  	out.name = name
    86  	out.f = f
    87  	return nil
    88  }
    89  
    90  func NewOutBuf(arch *sys.Arch) *OutBuf {
    91  	return &OutBuf{
    92  		arch: arch,
    93  	}
    94  }
    95  
    96  var viewError = errors.New("output not mmapped")
    97  
    98  func (out *OutBuf) View(start uint64) (*OutBuf, error) {
    99  	return &OutBuf{
   100  		arch:   out.arch,
   101  		name:   out.name,
   102  		buf:    out.buf,
   103  		heap:   out.heap,
   104  		off:    int64(start),
   105  		isView: true,
   106  	}, nil
   107  }
   108  
   109  var viewCloseError = errors.New("cannot Close OutBuf from View")
   110  
   111  func (out *OutBuf) Close() error {
   112  	if out.isView {
   113  		return viewCloseError
   114  	}
   115  	if out.isMmapped() {
   116  		out.copyHeap()
   117  		out.purgeSignatureCache()
   118  		out.munmap()
   119  	}
   120  	if out.f == nil {
   121  		return nil
   122  	}
   123  	if len(out.heap) != 0 {
   124  		if _, err := out.f.Write(out.heap); err != nil {
   125  			return err
   126  		}
   127  	}
   128  	if err := out.f.Close(); err != nil {
   129  		return err
   130  	}
   131  	out.f = nil
   132  	return nil
   133  }
   134  
   135  // ErrorClose closes the output file (if any).
   136  // It is supposed to be called only at exit on error, so it doesn't do
   137  // any clean up or buffer flushing, just closes the file.
   138  func (out *OutBuf) ErrorClose() {
   139  	if out.isView {
   140  		panic(viewCloseError)
   141  	}
   142  	if out.f == nil {
   143  		return
   144  	}
   145  	out.f.Close() // best effort, ignore error
   146  	out.f = nil
   147  }
   148  
   149  // isMmapped returns true if the OutBuf is mmaped.
   150  func (out *OutBuf) isMmapped() bool {
   151  	return len(out.buf) != 0
   152  }
   153  
   154  // Data returns the whole written OutBuf as a byte slice.
   155  func (out *OutBuf) Data() []byte {
   156  	if out.isMmapped() {
   157  		out.copyHeap()
   158  		return out.buf
   159  	}
   160  	return out.heap
   161  }
   162  
   163  // copyHeap copies the heap to the mmapped section of memory, returning true if
   164  // a copy takes place.
   165  func (out *OutBuf) copyHeap() bool {
   166  	if !out.isMmapped() { // only valuable for mmapped OutBufs.
   167  		return false
   168  	}
   169  	if out.isView {
   170  		panic("can't copyHeap a view")
   171  	}
   172  
   173  	bufLen := len(out.buf)
   174  	heapLen := len(out.heap)
   175  	total := uint64(bufLen + heapLen)
   176  	if heapLen != 0 {
   177  		if err := out.Mmap(total); err != nil { // Mmap will copy out.heap over to out.buf
   178  			Exitf("mapping output file failed: %v", err)
   179  		}
   180  	}
   181  	return true
   182  }
   183  
   184  // maxOutBufHeapLen limits the growth of the heap area.
   185  const maxOutBufHeapLen = 10 << 20
   186  
   187  // writeLoc determines the write location if a buffer is mmaped.
   188  // We maintain two write buffers, an mmapped section, and a heap section for
   189  // writing. When the mmapped section is full, we switch over the heap memory
   190  // for writing.
   191  func (out *OutBuf) writeLoc(lenToWrite int64) (int64, []byte) {
   192  	// See if we have enough space in the mmaped area.
   193  	bufLen := int64(len(out.buf))
   194  	if out.off+lenToWrite <= bufLen {
   195  		return out.off, out.buf
   196  	}
   197  
   198  	// Not enough space in the mmaped area, write to heap area instead.
   199  	heapPos := out.off - bufLen
   200  	heapLen := int64(len(out.heap))
   201  	lenNeeded := heapPos + lenToWrite
   202  	if lenNeeded > heapLen { // do we need to grow the heap storage?
   203  		// The heap variables aren't protected by a mutex. For now, just bomb if you
   204  		// try to use OutBuf in parallel. (Note this probably could be fixed.)
   205  		if out.isView {
   206  			panic("cannot write to heap in parallel")
   207  		}
   208  		// See if our heap would grow to be too large, and if so, copy it to the end
   209  		// of the mmapped area.
   210  		if heapLen > maxOutBufHeapLen && out.copyHeap() {
   211  			heapPos -= heapLen
   212  			lenNeeded = heapPos + lenToWrite
   213  			heapLen = 0
   214  		}
   215  		out.heap = append(out.heap, make([]byte, lenNeeded-heapLen)...)
   216  	}
   217  	return heapPos, out.heap
   218  }
   219  
   220  func (out *OutBuf) SeekSet(p int64) {
   221  	out.off = p
   222  }
   223  
   224  func (out *OutBuf) Offset() int64 {
   225  	return out.off
   226  }
   227  
   228  // Write writes the contents of v to the buffer.
   229  func (out *OutBuf) Write(v []byte) (int, error) {
   230  	n := len(v)
   231  	pos, buf := out.writeLoc(int64(n))
   232  	copy(buf[pos:], v)
   233  	out.off += int64(n)
   234  	return n, nil
   235  }
   236  
   237  func (out *OutBuf) Write8(v uint8) {
   238  	pos, buf := out.writeLoc(1)
   239  	buf[pos] = v
   240  	out.off++
   241  }
   242  
   243  // WriteByte is an alias for Write8 to fulfill the io.ByteWriter interface.
   244  func (out *OutBuf) WriteByte(v byte) error {
   245  	out.Write8(v)
   246  	return nil
   247  }
   248  
   249  func (out *OutBuf) Write16(v uint16) {
   250  	out.arch.ByteOrder.PutUint16(out.encbuf[:], v)
   251  	out.Write(out.encbuf[:2])
   252  }
   253  
   254  func (out *OutBuf) Write32(v uint32) {
   255  	out.arch.ByteOrder.PutUint32(out.encbuf[:], v)
   256  	out.Write(out.encbuf[:4])
   257  }
   258  
   259  func (out *OutBuf) Write32b(v uint32) {
   260  	binary.BigEndian.PutUint32(out.encbuf[:], v)
   261  	out.Write(out.encbuf[:4])
   262  }
   263  
   264  func (out *OutBuf) Write64(v uint64) {
   265  	out.arch.ByteOrder.PutUint64(out.encbuf[:], v)
   266  	out.Write(out.encbuf[:8])
   267  }
   268  
   269  func (out *OutBuf) Write64b(v uint64) {
   270  	binary.BigEndian.PutUint64(out.encbuf[:], v)
   271  	out.Write(out.encbuf[:8])
   272  }
   273  
   274  func (out *OutBuf) WriteString(s string) {
   275  	pos, buf := out.writeLoc(int64(len(s)))
   276  	n := copy(buf[pos:], s)
   277  	if n != len(s) {
   278  		log.Fatalf("WriteString truncated. buffer size: %d, offset: %d, len(s)=%d", len(out.buf), out.off, len(s))
   279  	}
   280  	out.off += int64(n)
   281  }
   282  
   283  // WriteStringN writes the first n bytes of s.
   284  // If n is larger than len(s) then it is padded with zero bytes.
   285  func (out *OutBuf) WriteStringN(s string, n int) {
   286  	out.WriteStringPad(s, n, zeros[:])
   287  }
   288  
   289  // WriteStringPad writes the first n bytes of s.
   290  // If n is larger than len(s) then it is padded with the bytes in pad (repeated as needed).
   291  func (out *OutBuf) WriteStringPad(s string, n int, pad []byte) {
   292  	if len(s) >= n {
   293  		out.WriteString(s[:n])
   294  	} else {
   295  		out.WriteString(s)
   296  		n -= len(s)
   297  		for n > len(pad) {
   298  			out.Write(pad)
   299  			n -= len(pad)
   300  
   301  		}
   302  		out.Write(pad[:n])
   303  	}
   304  }
   305  
   306  // WriteSym writes the content of a Symbol, and returns the output buffer
   307  // that we just wrote, so we can apply further edit to the symbol content.
   308  // For generator symbols, it also sets the symbol's Data to the output
   309  // buffer.
   310  func (out *OutBuf) WriteSym(ldr *loader.Loader, s loader.Sym) []byte {
   311  	if !ldr.IsGeneratedSym(s) {
   312  		P := ldr.Data(s)
   313  		n := int64(len(P))
   314  		pos, buf := out.writeLoc(n)
   315  		copy(buf[pos:], P)
   316  		out.off += n
   317  		ldr.FreeData(s)
   318  		return buf[pos : pos+n]
   319  	} else {
   320  		n := ldr.SymSize(s)
   321  		pos, buf := out.writeLoc(n)
   322  		out.off += n
   323  		ldr.MakeSymbolUpdater(s).SetData(buf[pos : pos+n])
   324  		return buf[pos : pos+n]
   325  	}
   326  }