diff options
Diffstat (limited to 'vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go')
-rw-r--r-- | vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go b/vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go new file mode 100644 index 000000000..542958bc7 --- /dev/null +++ b/vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go @@ -0,0 +1,164 @@ +package descriptor + +import "math/bits" + +// Table is a data structure mapping 32 bit descriptor to items. +// +// # Negative keys are invalid. +// +// Negative keys (e.g. -1) are invalid inputs and will return a corresponding +// not-found value. This matches POSIX behavior of file descriptors. +// See https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html#tag_16_90 +// +// # Data structure design +// +// The data structure optimizes for memory density and lookup performance, +// trading off compute at insertion time. This is a useful compromise for the +// use cases we employ it with: items are usually accessed a lot more often +// than they are inserted, each operation requires a table lookup, so we are +// better off spending extra compute to insert items in the table in order to +// get cheaper lookups. Memory efficiency is also crucial to support scaling +// with programs that maintain thousands of items: having a high or non-linear +// memory-to-item ratio could otherwise be used as an attack vector by +// malicious applications attempting to damage performance of the host. +type Table[Key ~int32, Item any] struct { + masks []uint64 + items []Item +} + +// Len returns the number of items stored in the table. +func (t *Table[Key, Item]) Len() (n int) { + // We could make this a O(1) operation if we cached the number of items in + // the table. More state usually means more problems, so until we have a + // clear need for this, the simple implementation may be a better trade off. + for _, mask := range t.masks { + n += bits.OnesCount64(mask) + } + return n +} + +// grow ensures that t has enough room for n items, potentially reallocating the +// internal buffers if their capacity was too small to hold this many items. +func (t *Table[Key, Item]) grow(n int) { + // Round up to a multiple of 64 since this is the smallest increment due to + // using 64 bits masks. + n = (n*64 + 63) / 64 + + if n > len(t.masks) { + masks := make([]uint64, n) + copy(masks, t.masks) + + items := make([]Item, n*64) + copy(items, t.items) + + t.masks = masks + t.items = items + } +} + +// Insert inserts the given item to the table, returning the key that it is +// mapped to or false if the table was full. +// +// The method does not perform deduplication, it is possible for the same item +// to be inserted multiple times, each insertion will return a different key. +func (t *Table[Key, Item]) Insert(item Item) (key Key, ok bool) { + offset := 0 +insert: + // Note: this loop could be made a lot more efficient using vectorized + // operations: 256 bits vector registers would yield a theoretical 4x + // speed up (e.g. using AVX2). + for index, mask := range t.masks[offset:] { + if ^mask != 0 { // not full? + shift := bits.TrailingZeros64(^mask) + index += offset + key = Key(index)*64 + Key(shift) + t.items[key] = item + t.masks[index] = mask | uint64(1<<shift) + return key, key >= 0 + } + } + + offset = len(t.masks) + n := 2 * len(t.masks) + if n == 0 { + n = 1 + } + + t.grow(n) + goto insert +} + +// Lookup returns the item associated with the given key (may be nil). +func (t *Table[Key, Item]) Lookup(key Key) (item Item, found bool) { + if key < 0 { // invalid key + return + } + if i := int(key); i >= 0 && i < len(t.items) { + index := uint(key) / 64 + shift := uint(key) % 64 + if (t.masks[index] & (1 << shift)) != 0 { + item, found = t.items[i], true + } + } + return +} + +// InsertAt inserts the given `item` at the item descriptor `key`. This returns +// false if the insert was impossible due to negative key. +func (t *Table[Key, Item]) InsertAt(item Item, key Key) bool { + if key < 0 { + return false + } + if diff := int(key) - t.Len(); diff > 0 { + t.grow(diff) + } + index := uint(key) / 64 + shift := uint(key) % 64 + t.masks[index] |= 1 << shift + t.items[key] = item + return true +} + +// Delete deletes the item stored at the given key from the table. +func (t *Table[Key, Item]) Delete(key Key) { + if key < 0 { // invalid key + return + } + if index, shift := key/64, key%64; int(index) < len(t.masks) { + mask := t.masks[index] + if (mask & (1 << shift)) != 0 { + var zero Item + t.items[key] = zero + t.masks[index] = mask & ^uint64(1<<shift) + } + } +} + +// Range calls f for each item and its associated key in the table. The function +// f might return false to interupt the iteration. +func (t *Table[Key, Item]) Range(f func(Key, Item) bool) { + for i, mask := range t.masks { + if mask == 0 { + continue + } + for j := Key(0); j < 64; j++ { + if (mask & (1 << j)) == 0 { + continue + } + if key := Key(i)*64 + j; !f(key, t.items[key]) { + return + } + } + } +} + +// Reset clears the content of the table. +func (t *Table[Key, Item]) Reset() { + for i := range t.masks { + t.masks[i] = 0 + } + var zero Item + for i := range t.items { + t.items[i] = zero + } +} |