github.com/higress-group/nottinygc@v0.0.0-20231101025119-e93c4c2f8520/intmap.go (about)

     1  // Copyright wasilibs authors
     2  // SPDX-License-Identifier: MIT
     3  
     4  package nottinygc
     5  
     6  import "C"
     7  import "unsafe"
     8  
     9  // Aim for initial over on the order of a few kilobytes.
    10  
    11  const (
    12  	initialBuckets = 512
    13  	numEmbedded    = 8
    14  )
    15  
    16  type item struct {
    17  	key uintptr
    18  	val uintptr
    19  }
    20  
    21  type extraNode struct {
    22  	next *extraNode
    23  	item item
    24  }
    25  
    26  type bucket struct {
    27  	embedded [numEmbedded]item
    28  	extra    *extraNode
    29  	count    byte
    30  }
    31  
    32  // intMap is a map from int to int. As it is used to cache descriptors within
    33  // allocation, it cannot itself allocate using the Go heap and uses malloc
    34  // instead. It also takes advantage of knowing we never replace values, so it
    35  // does not support replacement.
    36  type intMap struct {
    37  	buckets []bucket
    38  	count   int
    39  }
    40  
    41  func newIntMap() intMap {
    42  	return intMap{
    43  		buckets: newBuckets(initialBuckets),
    44  		count:   0,
    45  	}
    46  }
    47  
    48  func (m *intMap) put(key uintptr, val uintptr) {
    49  	if float64(m.count+1) > float64(len(m.buckets))*0.75 {
    50  		m.resize()
    51  	}
    52  	doPut(m.buckets, key, val)
    53  	m.count++
    54  }
    55  
    56  func doPut(buckets []bucket, key uintptr, val uintptr) {
    57  	pos := hash(key) % uintptr(len(buckets))
    58  	b := &buckets[pos]
    59  	if b.count < numEmbedded {
    60  		b.embedded[b.count] = item{key: key, val: val}
    61  	} else {
    62  		e := newExtraNode()
    63  		e.item = item{key: key, val: val}
    64  		e.next = b.extra
    65  		b.extra = e
    66  	}
    67  	b.count++
    68  }
    69  
    70  func (m *intMap) resize() {
    71  	newSz := len(m.buckets) * 2
    72  	newBkts := newBuckets(newSz)
    73  	for i := 0; i < len(m.buckets); i++ {
    74  		b := &m.buckets[i]
    75  		for j := 0; j < int(b.count); j++ {
    76  			if j < numEmbedded {
    77  				doPut(newBkts, b.embedded[j].key, b.embedded[j].val)
    78  			} else {
    79  				for n := b.extra; n != nil; {
    80  					doPut(newBkts, n.item.key, n.item.val)
    81  					next := n.next
    82  					cfree(unsafe.Pointer(n))
    83  					n = next
    84  				}
    85  			}
    86  		}
    87  	}
    88  	cfree(unsafe.Pointer(&m.buckets[0]))
    89  	m.buckets = newBkts
    90  }
    91  
    92  func (m *intMap) get(key uintptr) (uintptr, bool) {
    93  	pos := hash(key) % uintptr(len(m.buckets))
    94  	b := &m.buckets[pos]
    95  	for i := 0; i < int(b.count); i++ {
    96  		if i < numEmbedded {
    97  			if b.embedded[i].key == key {
    98  				return b.embedded[i].val, true
    99  			}
   100  		} else {
   101  			for n := b.extra; n != nil; n = n.next {
   102  				if n.item.key == key {
   103  					return n.item.val, true
   104  				}
   105  			}
   106  			break
   107  		}
   108  	}
   109  	return 0, false
   110  }
   111  
   112  func hash(key uintptr) uintptr {
   113  	// Use Java algorithm for cheap and easy handling of aligned values.
   114  	// There are better ones with more operations, we can try later if needed.
   115  	return key ^ (key >> 16)
   116  }
   117  
   118  func newBuckets(size int) []bucket {
   119  	sz := unsafe.Sizeof(bucket{}) * uintptr(size)
   120  	bucketsArr := cmalloc(sz)
   121  	for i := uintptr(0); i < sz; i++ {
   122  		*(*byte)(unsafe.Pointer(uintptr(bucketsArr) + i)) = 0
   123  	}
   124  	buckets := unsafe.Slice((*bucket)(bucketsArr), size)
   125  	return buckets
   126  }
   127  
   128  func newExtraNode() *extraNode {
   129  	sz := unsafe.Sizeof(extraNode{})
   130  	arr := cmalloc(sz)
   131  	for i := uintptr(0); i < sz; i++ {
   132  		*(*byte)(unsafe.Pointer(uintptr(arr) + i)) = 0
   133  	}
   134  	return (*extraNode)(arr)
   135  }