github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/asm/buffer.go (about)

     1  package asm
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"unsafe"
     7  
     8  	"github.com/wasilibs/wazerox/internal/platform"
     9  )
    10  
    11  var zero [16]byte
    12  
    13  // CodeSegment represents a memory mapped segment where native CPU instructions
    14  // are written.
    15  //
    16  // To construct code segments, the program must call Next to obtain a buffer
    17  // view capable of writing data at the end of the segment. Next must be called
    18  // before generating the code of a function because it aligns the next write on
    19  // 16 bytes.
    20  //
    21  // Instances of CodeSegment hold references to memory which is NOT managed by
    22  // the garbage collector and therefore must be released *manually* by calling
    23  // their Unmap method to prevent memory leaks.
    24  //
    25  // The zero value is a valid, empty code segment, equivalent to being
    26  // constructed by calling NewCodeSegment(nil).
    27  type CodeSegment struct {
    28  	code []byte
    29  	size int
    30  }
    31  
    32  // NewCodeSegment constructs a CodeSegment value from a byte slice.
    33  //
    34  // No validation is made that the byte slice is a memory mapped region which can
    35  // be unmapped on Close.
    36  func NewCodeSegment(code []byte) *CodeSegment {
    37  	return &CodeSegment{code: code, size: len(code)}
    38  }
    39  
    40  // Map allocates a memory mapping of the given size to the code segment.
    41  //
    42  // Note that programs only need to use this method to initialize the code
    43  // segment to a specific content (e.g. when loading pre-compiled code from a
    44  // file), otherwise the backing memory mapping is allocated on demand when code
    45  // is written to the code segment via Buffers returned by calls to Next.
    46  //
    47  // The method errors is the segment is already backed by a memory mapping.
    48  func (seg *CodeSegment) Map(size int) error {
    49  	if seg.code != nil {
    50  		return fmt.Errorf("code segment already initialized to memory mapping of size %d", len(seg.code))
    51  	}
    52  	b, err := platform.MmapCodeSegment(size)
    53  	if err != nil {
    54  		return err
    55  	}
    56  	seg.code = b
    57  	seg.size = size
    58  	return nil
    59  }
    60  
    61  // Close unmaps the underlying memory region held by the code segment, clearing
    62  // its state back to an empty code segment.
    63  //
    64  // The value is still usable after unmapping its memory, a new memory area can
    65  // be allocated by calling Map or writing to the segment.
    66  func (seg *CodeSegment) Unmap() error {
    67  	if seg.code != nil {
    68  		if err := platform.MunmapCodeSegment(seg.code[:cap(seg.code)]); err != nil {
    69  			return err
    70  		}
    71  		seg.code = nil
    72  		seg.size = 0
    73  	}
    74  	return nil
    75  }
    76  
    77  // Addr returns the address of the beginning of the code segment as a uintptr.
    78  func (seg *CodeSegment) Addr() uintptr {
    79  	if len(seg.code) > 0 {
    80  		return uintptr(unsafe.Pointer(&seg.code[0]))
    81  	}
    82  	return 0
    83  }
    84  
    85  // Size returns the size of code segment, which is less or equal to the length
    86  // of the byte slice returned by Len or Bytes.
    87  func (seg *CodeSegment) Size() uintptr {
    88  	return uintptr(seg.size)
    89  }
    90  
    91  // Len returns the length of the byte slice referencing the memory mapping of
    92  // the code segment.
    93  func (seg *CodeSegment) Len() int {
    94  	return len(seg.code)
    95  }
    96  
    97  // Bytes returns a byte slice to the memory mapping of the code segment.
    98  //
    99  // The returned slice remains valid until more bytes are written to a buffer
   100  // of the code segment, or Unmap is called.
   101  func (seg *CodeSegment) Bytes() []byte {
   102  	return seg.code
   103  }
   104  
   105  // Next returns a buffer pointed at the end of the code segment to support
   106  // writing more code instructions to it.
   107  //
   108  // Buffers are passed by value, but they hold a reference to the code segment
   109  // that they were created from.
   110  func (seg *CodeSegment) NextCodeSection() Buffer {
   111  	// Align 16-bytes boundary.
   112  	seg.AppendBytes(zero[:seg.size&15])
   113  	return Buffer{CodeSegment: seg, off: seg.size}
   114  }
   115  
   116  // Append appends n bytes to the code segment, returning a slice to the appended
   117  // memory region.
   118  //
   119  // The underlying code segment may be reallocated if it was too short to hold
   120  // n more bytes, which invalidates any addresses previously returned by calls
   121  // to Addr.
   122  func (seg *CodeSegment) Append(n int) []byte {
   123  	seg.size += n
   124  	if seg.size > len(seg.code) {
   125  		seg.growToSize()
   126  	}
   127  	return seg.code[seg.size-n:]
   128  }
   129  
   130  // AppendByte appends a single byte to the code segment.
   131  //
   132  // The underlying code segment may be reallocated if it was too short to hold
   133  // one more byte, which invalidates any addresses previously returned by calls
   134  // to Addr.
   135  func (seg *CodeSegment) AppendByte(b byte) {
   136  	seg.size++
   137  	if seg.size > len(seg.code) {
   138  		seg.growToSize()
   139  	}
   140  	seg.code[seg.size-1] = b
   141  }
   142  
   143  // AppendBytes appends a copy of b to the code segment.
   144  //
   145  // The underlying code segment may be reallocated if it was too short to hold
   146  // len(b) more bytes, which invalidates any addresses previously returned by
   147  // calls to Addr.
   148  func (seg *CodeSegment) AppendBytes(b []byte) {
   149  	copy(seg.Append(len(b)), b)
   150  }
   151  
   152  // AppendUint32 appends a 32 bits integer to the code segment.
   153  //
   154  // The underlying code segment may be reallocated if it was too short to hold
   155  // four more bytes, which invalidates any addresses previously returned by calls
   156  // to Addr.
   157  func (seg *CodeSegment) AppendUint32(u uint32) {
   158  	seg.size += 4
   159  	if seg.size > len(seg.code) {
   160  		seg.growToSize()
   161  	}
   162  	// This can be replaced by an unsafe operation to assign the uint32, which
   163  	// keeps the function cost below the inlining threshold. However, it did not
   164  	// show any improvements in arm64 benchmarks so we retained this safer code.
   165  	binary.LittleEndian.PutUint32(seg.code[seg.size-4:], u)
   166  }
   167  
   168  // growMode grows the code segment so that another section can be added to it.
   169  //
   170  // The method is marked go:noinline so that it doesn't get inline in Append,
   171  // and AppendByte, which keeps the inlining score of those methods low enough
   172  // that they can be inlined at the call sites.
   173  //
   174  //go:noinline
   175  func (seg *CodeSegment) growToSize() {
   176  	seg.Grow(0)
   177  }
   178  
   179  // Grow ensure that the capacity of the code segment is large enough to hold n
   180  // more bytes.
   181  //
   182  // The underlying code segment may be reallocated if it was too short, which
   183  // invalidates any addresses previously returned by calls to Addr.
   184  func (seg *CodeSegment) Grow(n int) {
   185  	size := len(seg.code)
   186  	want := seg.size + n
   187  	if size >= want {
   188  		return
   189  	}
   190  	if size == 0 {
   191  		size = 65536
   192  	}
   193  	for size < want {
   194  		size *= 2
   195  	}
   196  	b, err := platform.RemapCodeSegment(seg.code, size)
   197  	if err != nil {
   198  		// The only reason for growing the buffer to error is if we run
   199  		// out of memory, so panic for now as it greatly simplifies error
   200  		// handling to assume writing to the buffer would never fail.
   201  		panic(err)
   202  	}
   203  	seg.code = b
   204  }
   205  
   206  // Buffer is a reference type representing a section beginning at the end of a
   207  // code segment where new instructions can be written.
   208  type Buffer struct {
   209  	*CodeSegment
   210  	off int
   211  }
   212  
   213  func (buf Buffer) Cap() int {
   214  	return len(buf.code) - buf.off
   215  }
   216  
   217  func (buf Buffer) Len() int {
   218  	return buf.size - buf.off
   219  }
   220  
   221  func (buf Buffer) Bytes() []byte {
   222  	return buf.code[buf.off:buf.size:buf.size]
   223  }
   224  
   225  func (buf Buffer) Reset() {
   226  	buf.size = buf.off
   227  }
   228  
   229  func (buf Buffer) Truncate(n int) {
   230  	buf.size = buf.off + n
   231  }
   232  
   233  func (buf Buffer) Append4Bytes(a, b, c, d byte) {
   234  	buf.AppendUint32(uint32(a) | uint32(b)<<8 | uint32(c)<<16 | uint32(d)<<24)
   235  }