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 }