github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/wazevoapi/pool.go (about)

     1  package wazevoapi
     2  
     3  const poolPageSize = 128
     4  
     5  // Pool is a pool of T that can be allocated and reset.
     6  // This is useful to avoid unnecessary allocations.
     7  type Pool[T any] struct {
     8  	pages            []*[poolPageSize]T
     9  	resetFn          func(*T)
    10  	allocated, index int
    11  }
    12  
    13  // NewPool returns a new Pool.
    14  // resetFn is called when a new T is allocated in Pool.Allocate.
    15  func NewPool[T any](resetFn func(*T)) Pool[T] {
    16  	var ret Pool[T]
    17  	ret.resetFn = resetFn
    18  	ret.Reset()
    19  	return ret
    20  }
    21  
    22  // Allocated returns the number of allocated T currently in the pool.
    23  func (p *Pool[T]) Allocated() int {
    24  	return p.allocated
    25  }
    26  
    27  // Allocate allocates a new T from the pool.
    28  func (p *Pool[T]) Allocate() *T {
    29  	if p.index == poolPageSize {
    30  		if len(p.pages) == cap(p.pages) {
    31  			p.pages = append(p.pages, new([poolPageSize]T))
    32  		} else {
    33  			i := len(p.pages)
    34  			p.pages = p.pages[:i+1]
    35  			if p.pages[i] == nil {
    36  				p.pages[i] = new([poolPageSize]T)
    37  			}
    38  		}
    39  		p.index = 0
    40  	}
    41  	ret := &p.pages[len(p.pages)-1][p.index]
    42  	if p.resetFn != nil {
    43  		p.resetFn(ret)
    44  	}
    45  	p.index++
    46  	p.allocated++
    47  	return ret
    48  }
    49  
    50  // View returns the pointer to i-th item from the pool.
    51  func (p *Pool[T]) View(i int) *T {
    52  	page, index := i/poolPageSize, i%poolPageSize
    53  	return &p.pages[page][index]
    54  }
    55  
    56  // Reset resets the pool.
    57  func (p *Pool[T]) Reset() {
    58  	p.pages = p.pages[:0]
    59  	p.index = poolPageSize
    60  	p.allocated = 0
    61  }
    62  
    63  // IDedPool is a pool of T that can be allocated and reset, with a way to get T by an ID.
    64  type IDedPool[T any] struct {
    65  	pool             Pool[T]
    66  	idToItems        []*T
    67  	maxIDEncountered int
    68  }
    69  
    70  // NewIDedPool returns a new IDedPool.
    71  func NewIDedPool[T any](resetFn func(*T)) IDedPool[T] {
    72  	return IDedPool[T]{pool: NewPool[T](resetFn)}
    73  }
    74  
    75  // GetOrAllocate returns the T with the given id.
    76  func (p *IDedPool[T]) GetOrAllocate(id int) *T {
    77  	if p.maxIDEncountered < id {
    78  		p.maxIDEncountered = id
    79  	}
    80  	if id >= len(p.idToItems) {
    81  		p.idToItems = append(p.idToItems, make([]*T, id-len(p.idToItems)+1)...)
    82  	}
    83  	if p.idToItems[id] == nil {
    84  		p.idToItems[id] = p.pool.Allocate()
    85  	}
    86  	return p.idToItems[id]
    87  }
    88  
    89  // Get returns the T with the given id, or nil if it's not allocated.
    90  func (p *IDedPool[T]) Get(id int) *T {
    91  	if id >= len(p.idToItems) {
    92  		return nil
    93  	}
    94  	return p.idToItems[id]
    95  }
    96  
    97  // Reset resets the pool.
    98  func (p *IDedPool[T]) Reset() {
    99  	p.pool.Reset()
   100  	for i := range p.idToItems {
   101  		p.idToItems[i] = nil
   102  	}
   103  	p.maxIDEncountered = -1
   104  }
   105  
   106  // MaxIDEncountered returns the maximum id encountered so far.
   107  func (p *IDedPool[T]) MaxIDEncountered() int {
   108  	return p.maxIDEncountered
   109  }
   110  
   111  // arraySize is the size of the array used in VarLengthPool's arrayPool.
   112  // This is chosen to be 8, which is empirically a good number among 8, 12, 16 and 20.
   113  const arraySize = 8
   114  
   115  // VarLengthPool is a pool of VarLength[T] that can be allocated and reset.
   116  type (
   117  	VarLengthPool[T any] struct {
   118  		arrayPool Pool[varLengthPoolArray[T]]
   119  		slicePool Pool[[]T]
   120  	}
   121  	// varLengthPoolArray wraps an array and keeps track of the next index to be used to avoid the heap allocation.
   122  	varLengthPoolArray[T any] struct {
   123  		arr  [arraySize]T
   124  		next int
   125  	}
   126  )
   127  
   128  // VarLength is a variable length array that can be reused via a pool.
   129  type VarLength[T any] struct {
   130  	arr *varLengthPoolArray[T]
   131  	slc *[]T
   132  }
   133  
   134  // NewVarLengthPool returns a new VarLengthPool.
   135  func NewVarLengthPool[T any]() VarLengthPool[T] {
   136  	return VarLengthPool[T]{
   137  		arrayPool: NewPool[varLengthPoolArray[T]](func(v *varLengthPoolArray[T]) {
   138  			v.next = 0
   139  		}),
   140  		slicePool: NewPool[[]T](func(i *[]T) {
   141  			*i = (*i)[:0]
   142  		}),
   143  	}
   144  }
   145  
   146  // NewNilVarLength returns a new VarLength[T] with a nil backing.
   147  func NewNilVarLength[T any]() VarLength[T] {
   148  	return VarLength[T]{}
   149  }
   150  
   151  // Allocate allocates a new VarLength[T] from the pool.
   152  func (p *VarLengthPool[T]) Allocate(knownMin int) VarLength[T] {
   153  	if knownMin <= arraySize {
   154  		arr := p.arrayPool.Allocate()
   155  		return VarLength[T]{arr: arr}
   156  	}
   157  	slc := p.slicePool.Allocate()
   158  	return VarLength[T]{slc: slc}
   159  }
   160  
   161  // Reset resets the pool.
   162  func (p *VarLengthPool[T]) Reset() {
   163  	p.arrayPool.Reset()
   164  	p.slicePool.Reset()
   165  }
   166  
   167  // Append appends items to the backing slice just like the `append` builtin function in Go.
   168  func (i VarLength[T]) Append(p *VarLengthPool[T], items ...T) VarLength[T] {
   169  	if i.slc != nil {
   170  		*i.slc = append(*i.slc, items...)
   171  		return i
   172  	}
   173  
   174  	if i.arr == nil {
   175  		i.arr = p.arrayPool.Allocate()
   176  	}
   177  
   178  	arr := i.arr
   179  	if arr.next+len(items) <= arraySize {
   180  		for _, item := range items {
   181  			arr.arr[arr.next] = item
   182  			arr.next++
   183  		}
   184  	} else {
   185  		slc := p.slicePool.Allocate()
   186  		// Copy the array to the slice.
   187  		for ptr := 0; ptr < arr.next; ptr++ {
   188  			*slc = append(*slc, arr.arr[ptr])
   189  		}
   190  		i.slc = slc
   191  		*i.slc = append(*i.slc, items...)
   192  	}
   193  	return i
   194  }
   195  
   196  // View returns the backing slice.
   197  func (i VarLength[T]) View() []T {
   198  	if i.slc != nil {
   199  		return *i.slc
   200  	} else if i.arr != nil {
   201  		arr := i.arr
   202  		return arr.arr[:arr.next]
   203  	}
   204  	return nil
   205  }
   206  
   207  // Cut cuts the backing slice to the given length.
   208  // Precondition: n <= len(i.backing).
   209  func (i VarLength[T]) Cut(n int) {
   210  	if i.slc != nil {
   211  		*i.slc = (*i.slc)[:n]
   212  	} else if i.arr != nil {
   213  		i.arr.next = n
   214  	}
   215  }