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 }