github.com/iDigitalFlame/xmt@v0.5.4/data/chunk_heap.go (about) 1 //go:build windows && heap 2 // +build windows,heap 3 4 // Copyright (C) 2020 - 2023 iDigitalFlame 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program. If not, see <https://www.gnu.org/licenses/>. 18 // 19 20 package data 21 22 import ( 23 "io" 24 "sync" 25 "sync/atomic" 26 "unsafe" 27 28 "github.com/iDigitalFlame/xmt/util/bugtrack" 29 ) 30 31 var heapBase uintptr 32 var heapBaseInit sync.Once 33 34 // Chunk is a low level data container. Chunks allow for simple read/write 35 // operations on static containers. 36 // 37 // Chunk fulfils the Reader, Seeker, Writer, Flusher and Closer interfaces. 38 // Seeking on Chunks is only supported in a read-only fashion. 39 // 40 // If the underlying device is running Windows and the "heap" build tag is used, 41 // Chunks will be created on the Process Heap not managed by Go. This prevents 42 // over-allocation and relieves pressure on the GC. By default, this is off. 43 type Chunk struct { 44 h uintptr 45 buf []byte 46 rpos, wpos int 47 48 Limit int 49 id uint32 50 } 51 type header struct { 52 Data unsafe.Pointer 53 Len int 54 Cap int 55 } 56 57 // Reset resets the Chunk buffer to be empty but retains the underlying storage 58 // for use by future writes. 59 func (c *Chunk) Reset() { 60 if atomic.LoadUintptr(&c.h) == 0 { 61 return 62 } 63 // NOTE(dij): Not sure if this is needed, this would allow us to zero out 64 // the Chunk data. 65 /*if c.wpos == 0 { 66 for i := 0; i < c.rpos; i++ { 67 c.buf[i] = 0 68 } 69 } else { 70 for i := 0; i < c.wpos; i++ { 71 c.buf[i] = 0 72 } 73 }*/ 74 c.rpos, c.wpos = 0, 0 75 } 76 func heapBaseInitFunc() { 77 var err error 78 if heapBase, err = heapCreate(0); err != nil { 79 if bugtrack.Enabled { 80 bugtrack.Track("data.heapBaseInitFunc(): Failed with error: %s!", err) 81 } 82 panic(err) 83 } 84 if bugtrack.Enabled { 85 bugtrack.Track("data.heapBaseInitFunc(): Created heapBase at 0x%X!", heapBase) 86 } 87 } 88 89 // Clear is similar to Reset, but discards the buffer, which must be allocated 90 // again. If using the buffer the 'Reset' function is preferable. 91 func (c *Chunk) Clear() { 92 if atomic.LoadUintptr(&c.h) == 0 { 93 return 94 } 95 if err := heapFree(heapBase, c.h); err != nil { 96 if bugtrack.Enabled { 97 bugtrack.Track("data.(*Chunk).Close(): Failed to free Chunk 0x%X: %s!", c.h, err) 98 } 99 panic(err) 100 } 101 if atomic.StoreUintptr(&c.h, 0); bugtrack.Enabled { 102 bugtrack.Track("data.(*Chunk).Close(): Freed Chunk 0x%X.", c.h) 103 } 104 c.rpos, c.wpos, c.buf = 0, 0, nil 105 } 106 107 // Size returns the internal size of the backing buffer, similar to len(b). 108 func (c *Chunk) Size() int { 109 return c.wpos 110 } 111 112 // Empty returns true if this Chunk's buffer is empty or has been drained by 113 // reads. 114 func (c *Chunk) Empty() bool { 115 return c == nil || atomic.LoadUintptr(&c.h) == 0 || c.wpos <= c.rpos 116 } 117 118 // NewChunk creates a new Chunk struct and will use the provided byte array as 119 // the underlying backing buffer. 120 func NewChunk(b []byte) *Chunk { 121 var c Chunk 122 if _, err := c.Write(b); err != nil { 123 if bugtrack.Enabled { 124 bugtrack.Track("data.NewChunk(): Creating a new []byte-based Chunk failed: %s!", err) 125 } 126 panic(err) 127 } 128 return &c 129 } 130 131 //go:linkname heapFree github.com/iDigitalFlame/xmt/device/winapi.heapFree 132 func heapFree(h, m uintptr) error 133 func (c *Chunk) grow(n int) error { 134 if heapBaseInit.Do(heapBaseInitFunc); atomic.LoadUintptr(&c.h) == 0 { 135 v := uint64(n) * 2 136 if v < 4096 { 137 // NOTE(dij): 4096 is chosen as it's a good middleground to prevent 138 // a lot of reallocations. 139 v = 4096 140 } 141 var r bool 142 if c.Limit > 0 && int(v) > c.Limit { 143 v, r = uint64(c.Limit), true 144 } 145 h, err := heapAlloc(heapBase, v, true) 146 if err != nil { 147 if bugtrack.Enabled { 148 bugtrack.Track("data.(*Chunk).grow(): Creating a new Chunk of size %d failed: %s!", v, err) 149 } 150 return err 151 } 152 if atomic.StoreUintptr(&c.h, h); bugtrack.Enabled { 153 bugtrack.Track("data.(*Chunk).grow(): Created a new Chunk 0x%X with size %d.", c.h, v) 154 } 155 c.buf, c.wpos, c.rpos = *(*[]byte)(unsafe.Pointer(&header{Data: unsafe.Pointer(h), Len: int(v), Cap: int(v)})), 0, 0 156 if r { 157 return ErrLimit 158 } 159 return nil 160 } 161 if c.wpos+n < len(c.buf) { 162 return nil 163 } 164 var ( 165 v = uint64(len(c.buf)+n) * 2 166 r bool 167 ) 168 if c.Limit > 0 && int(v) > c.Limit { 169 v, r = uint64(c.Limit), true 170 } 171 h, err := heapReAlloc(heapBase, c.h, v, true) 172 if err != nil { 173 if bugtrack.Enabled { 174 bugtrack.Track("data.(*Chunk).grow(): Reallocating a Chunk 0x%X with size %d failed: %s!", c.h, v, err) 175 } 176 return err 177 } 178 if bugtrack.Enabled { 179 bugtrack.Track("data.(*Chunk).grow(): Reallocated Chunk 0x%X with a new size of %d.", c.h, v) 180 } 181 c.buf = nil 182 c.buf = *(*[]byte)(unsafe.Pointer(&header{Data: unsafe.Pointer(h), Len: int(v), Cap: int(v)})) 183 if atomic.StoreUintptr(&c.h, h); r { 184 return ErrLimit 185 } 186 return nil 187 } 188 189 // Grow grows the Chunk's buffer capacity, if necessary, to guarantee space for 190 // another n bytes. 191 func (c *Chunk) Grow(n int) error { 192 if n <= 0 { 193 return ErrInvalidIndex 194 } 195 if err := c.grow(n); err != nil { 196 return err 197 } 198 return nil 199 } 200 201 // Truncate discards all but the first n unread bytes from the Chunk but 202 // continues to use the same allocated storage. 203 // 204 // This will return an error if n is negative or greater than the length of the 205 // buffer. 206 func (c *Chunk) Truncate(n int) error { 207 if n == 0 { 208 if c.Empty() { 209 return nil 210 } 211 c.Reset() 212 return nil 213 } 214 if c.Empty() || n < 0 || n > len(c.buf)-c.rpos { 215 return ErrInvalidIndex 216 } 217 for i := c.rpos + n; i < len(c.buf); i++ { 218 c.buf[i] = 0 219 } 220 return nil 221 } 222 func (c *Chunk) checkBounds(n int) bool { 223 return c.rpos+n > len(c.buf) || c.rpos+n > c.wpos 224 } 225 226 //go:linkname heapCreate github.com/iDigitalFlame/xmt/device/winapi.heapCreate 227 func heapCreate(n uint64) (uintptr, error) 228 func (c *Chunk) reslice(n int) (int, bool) { 229 if c.Empty() { 230 return 0, false 231 } 232 if c.wpos+n < len(c.buf) { 233 return c.wpos, true 234 } 235 return 0, false 236 } 237 func (c *Chunk) quickSlice(n int) (int, error) { 238 if x, ok := c.reslice(n); ok { 239 c.wpos += n 240 return x, nil 241 } 242 err, v := c.grow(n), c.wpos 243 c.wpos += n 244 return v, err 245 } 246 247 // UnmarshalStream reads the Chunk data from a binary data representation. This 248 // function will return an error if any part of the read fails. 249 func (c *Chunk) UnmarshalStream(r Reader) error { 250 // NOTE(dij): We have to re-write this here as we don't want the runtime 251 // to directly allocate our new buffer and instead we want to read it directly 252 // into the heap. 253 if bugtrack.Enabled { 254 if _, ok := r.(*Chunk); ok { 255 bugtrack.Track("data.(*Chunk).UnmarshalStream(): UnmarshalStream was called from a Chunk to a Chunk!") 256 } 257 } 258 if !c.Empty() { 259 c.Reset() 260 } 261 t, err := r.Uint8() 262 if err != nil { 263 return err 264 } 265 var l uint64 266 switch t { 267 case 0: 268 return nil 269 case 1, 2: 270 n, err2 := r.Uint8() 271 if err2 != nil { 272 return err2 273 } 274 l = uint64(n) 275 case 3, 4: 276 n, err2 := r.Uint16() 277 if err2 != nil { 278 return err2 279 } 280 l = uint64(n) 281 case 5, 6: 282 n, err2 := r.Uint32() 283 if err2 != nil { 284 return err2 285 } 286 l = uint64(n) 287 case 7, 8: 288 n, err2 := r.Uint64() 289 if err2 != nil { 290 return err2 291 } 292 l = n 293 default: 294 return ErrInvalidType 295 } 296 if l == 0 { 297 return io.ErrUnexpectedEOF 298 } 299 if l > MaxSlice { 300 return ErrTooLarge 301 } 302 if err = c.grow(int(l)); err != nil { 303 return err 304 } 305 x, err := r.Read(c.buf[0:l]) 306 if c.rpos, c.wpos, c.wpos = 0, 0, x; x != int(l) { 307 return io.ErrUnexpectedEOF 308 } 309 return err 310 } 311 312 //go:linkname heapAlloc github.com/iDigitalFlame/xmt/device/winapi.heapAlloc 313 func heapAlloc(h uintptr, s uint64, z bool) (uintptr, error) 314 315 //go:linkname heapReAlloc github.com/iDigitalFlame/xmt/device/winapi.heapReAlloc 316 func heapReAlloc(h, m uintptr, s uint64, z bool) (uintptr, error)