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)