aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/builtin/map.v
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/vlib/builtin/map.v')
-rw-r--r--v_windows/v/vlib/builtin/map.v716
1 files changed, 716 insertions, 0 deletions
diff --git a/v_windows/v/vlib/builtin/map.v b/v_windows/v/vlib/builtin/map.v
new file mode 100644
index 0000000..5e67d79
--- /dev/null
+++ b/v_windows/v/vlib/builtin/map.v
@@ -0,0 +1,716 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module builtin
+
+/*
+This is a highly optimized hashmap implementation. It has several traits that
+in combination makes it very fast and memory efficient. Here is a short expl-
+anation of each trait. After reading this you should have a basic understand-
+ing of how it functions:
+
+1. Hash-function: Wyhash. Wyhash is the fastest hash-function for short keys
+passing SMHasher, so it was an obvious choice.
+
+2. Open addressing: Robin Hood Hashing. With this method, a hash-collision is
+resolved by probing. As opposed to linear probing, Robin Hood hashing has a
+simple but clever twist: As new keys are inserted, old keys are shifted arou-
+nd in a way such that all keys stay reasonably close to the slot they origin-
+ally hash to. A new key may displace a key already inserted if its probe cou-
+nt is larger than that of the key at the current position.
+
+3. Memory layout: key-value pairs are stored in a `DenseArray`. This is a dy-
+namic array with a very low volume of unused memory, at the cost of more rea-
+llocations when inserting elements. It also preserves the order of the key-v-
+alues. This array is named `key_values`. Instead of probing a new key-value,
+this map probes two 32-bit numbers collectively. The first number has its 8
+most significant bits reserved for the probe-count and the remaining 24 bits
+are cached bits from the hash which are utilized for faster re-hashing. This
+number is often referred to as `meta`. The other 32-bit number is the index
+at which the key-value was pushed to in `key_values`. Both of these numbers
+are stored in a sparse array `metas`. The `meta`s and `kv_index`s are stored
+at even and odd indices, respectively:
+
+metas = [meta, kv_index, 0, 0, meta, kv_index, 0, 0, meta, kv_index, ...]
+key_values = [kv, kv, kv, ...]
+
+4. The size of metas is a power of two. This enables the use of bitwise AND
+to convert the 64-bit hash to a bucket/index that doesn't overflow metas. If
+the size is power of two you can use "hash & (SIZE - 1)" instead of "hash %
+SIZE". Modulo is extremely expensive so using '&' is a big performance impro-
+vement. The general concern with this approach is that you only make use of
+the lower bits of the hash which can cause more collisions. This is solved by
+using a well-dispersed hash-function.
+
+5. The hashmap keeps track of the highest probe_count. The trick is to alloc-
+ate `extra_metas` > max(probe_count), so you never have to do any bounds-che-
+cking since the extra meta memory ensures that a meta will never go beyond
+the last index.
+
+6. Cached rehashing. When the `load_factor` of the map exceeds the `max_load_
+factor` the size of metas is doubled and all the key-values are "rehashed" to
+find the index for their meta's in the new array. Instead of rehashing compl-
+etely, it simply uses the cached-hashbits stored in the meta, resulting in
+much faster rehashing.
+*/
+const (
+ // Number of bits from the hash stored for each entry
+ hashbits = 24
+ // Number of bits from the hash stored for rehashing
+ max_cached_hashbits = 16
+ // Initial log-number of buckets in the hashtable
+ init_log_capicity = 5
+ // Initial number of buckets in the hashtable
+ init_capicity = 1 << init_log_capicity
+ // Maximum load-factor (len / capacity)
+ max_load_factor = 0.8
+ // Initial highest even index in metas
+ init_even_index = init_capicity - 2
+ // Used for incrementing `extra_metas` when max
+ // probe count is too high, to avoid overflow
+ extra_metas_inc = 4
+ // Bitmask to select all the hashbits
+ hash_mask = u32(0x00FFFFFF)
+ // Used for incrementing the probe-count
+ probe_inc = u32(0x01000000)
+)
+
+// DenseArray represents a dynamic array with very low growth factor
+struct DenseArray {
+ key_bytes int
+ value_bytes int
+mut:
+ cap int
+ len int
+ deletes u32 // count
+ // array allocated (with `cap` bytes) on first deletion
+ // has non-zero element when key deleted
+ all_deleted &byte
+ values &byte
+ keys &byte
+}
+
+[inline]
+fn new_dense_array(key_bytes int, value_bytes int) DenseArray {
+ cap := 8
+ return DenseArray{
+ key_bytes: key_bytes
+ value_bytes: value_bytes
+ cap: cap
+ len: 0
+ deletes: 0
+ all_deleted: 0
+ keys: unsafe { malloc(cap * key_bytes) }
+ values: unsafe { malloc(cap * value_bytes) }
+ }
+}
+
+[inline]
+fn (d &DenseArray) key(i int) voidptr {
+ return unsafe { d.keys + i * d.key_bytes }
+}
+
+// for cgen
+[inline]
+fn (d &DenseArray) value(i int) voidptr {
+ return unsafe { d.values + i * d.value_bytes }
+}
+
+[inline]
+fn (d &DenseArray) has_index(i int) bool {
+ return d.deletes == 0 || unsafe { d.all_deleted[i] } == 0
+}
+
+// Make space to append an element and return index
+// The growth-factor is roughly 1.125 `(x + (x >> 3))`
+[inline]
+fn (mut d DenseArray) expand() int {
+ old_cap := d.cap
+ old_value_size := d.value_bytes * old_cap
+ old_key_size := d.key_bytes * old_cap
+ if d.cap == d.len {
+ d.cap += d.cap >> 3
+ unsafe {
+ d.keys = realloc_data(d.keys, old_key_size, d.key_bytes * d.cap)
+ d.values = realloc_data(d.values, old_value_size, d.value_bytes * d.cap)
+ if d.deletes != 0 {
+ d.all_deleted = realloc_data(d.all_deleted, old_cap, d.cap)
+ vmemset(d.all_deleted + d.len, 0, d.cap - d.len)
+ }
+ }
+ }
+ push_index := d.len
+ unsafe {
+ if d.deletes != 0 {
+ d.all_deleted[push_index] = 0
+ }
+ }
+ d.len++
+ return push_index
+}
+
+type MapHashFn = fn (voidptr) u64
+
+type MapEqFn = fn (voidptr, voidptr) bool
+
+type MapCloneFn = fn (voidptr, voidptr)
+
+type MapFreeFn = fn (voidptr)
+
+// map is the internal representation of a V `map` type.
+pub struct map {
+ // Number of bytes of a key
+ key_bytes int
+ // Number of bytes of a value
+ value_bytes int
+mut:
+ // Highest even index in the hashtable
+ even_index u32
+ // Number of cached hashbits left for rehashing
+ cached_hashbits byte
+ // Used for right-shifting out used hashbits
+ shift byte
+ // Array storing key-values (ordered)
+ key_values DenseArray
+ // Pointer to meta-data:
+ // - Odd indices store kv_index.
+ // - Even indices store probe_count and hashbits.
+ metas &u32
+ // Extra metas that allows for no ranging when incrementing
+ // index in the hashmap
+ extra_metas u32
+ has_string_keys bool
+ hash_fn MapHashFn
+ key_eq_fn MapEqFn
+ clone_fn MapCloneFn
+ free_fn MapFreeFn
+pub mut:
+ // Number of key-values currently in the hashmap
+ len int
+}
+
+fn map_eq_string(a voidptr, b voidptr) bool {
+ return fast_string_eq(*unsafe { &string(a) }, *unsafe { &string(b) })
+}
+
+fn map_eq_int_1(a voidptr, b voidptr) bool {
+ return unsafe { *&byte(a) == *&byte(b) }
+}
+
+fn map_eq_int_2(a voidptr, b voidptr) bool {
+ return unsafe { *&u16(a) == *&u16(b) }
+}
+
+fn map_eq_int_4(a voidptr, b voidptr) bool {
+ return unsafe { *&u32(a) == *&u32(b) }
+}
+
+fn map_eq_int_8(a voidptr, b voidptr) bool {
+ return unsafe { *&u64(a) == *&u64(b) }
+}
+
+fn map_clone_string(dest voidptr, pkey voidptr) {
+ unsafe {
+ s := *&string(pkey)
+ (*&string(dest)) = s.clone()
+ }
+}
+
+fn map_clone_int_1(dest voidptr, pkey voidptr) {
+ unsafe {
+ *&byte(dest) = *&byte(pkey)
+ }
+}
+
+fn map_clone_int_2(dest voidptr, pkey voidptr) {
+ unsafe {
+ *&u16(dest) = *&u16(pkey)
+ }
+}
+
+fn map_clone_int_4(dest voidptr, pkey voidptr) {
+ unsafe {
+ *&u32(dest) = *&u32(pkey)
+ }
+}
+
+fn map_clone_int_8(dest voidptr, pkey voidptr) {
+ unsafe {
+ *&u64(dest) = *&u64(pkey)
+ }
+}
+
+fn map_free_string(pkey voidptr) {
+ unsafe {
+ (*&string(pkey)).free()
+ }
+}
+
+fn map_free_nop(_ voidptr) {
+}
+
+fn new_map(key_bytes int, value_bytes int, hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn) map {
+ metasize := int(sizeof(u32) * (init_capicity + extra_metas_inc))
+ // for now assume anything bigger than a pointer is a string
+ has_string_keys := key_bytes > sizeof(voidptr)
+ return map{
+ key_bytes: key_bytes
+ value_bytes: value_bytes
+ even_index: init_even_index
+ cached_hashbits: max_cached_hashbits
+ shift: init_log_capicity
+ key_values: new_dense_array(key_bytes, value_bytes)
+ metas: unsafe { &u32(vcalloc_noscan(metasize)) }
+ extra_metas: extra_metas_inc
+ len: 0
+ has_string_keys: has_string_keys
+ hash_fn: hash_fn
+ key_eq_fn: key_eq_fn
+ clone_fn: clone_fn
+ free_fn: free_fn
+ }
+}
+
+fn new_map_init(hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_fn MapFreeFn, n int, key_bytes int, value_bytes int, keys voidptr, values voidptr) map {
+ mut out := new_map(key_bytes, value_bytes, hash_fn, key_eq_fn, clone_fn, free_fn)
+ // TODO pre-allocate n slots
+ mut pkey := &byte(keys)
+ mut pval := &byte(values)
+ for _ in 0 .. n {
+ unsafe {
+ out.set(pkey, pval)
+ pkey = pkey + key_bytes
+ pval = pval + value_bytes
+ }
+ }
+ return out
+}
+
+pub fn (mut m map) move() map {
+ r := *m
+ unsafe {
+ vmemset(m, 0, int(sizeof(map)))
+ }
+ return r
+}
+
+[inline]
+fn (m &map) key_to_index(pkey voidptr) (u32, u32) {
+ hash := m.hash_fn(pkey)
+ index := hash & m.even_index
+ meta := ((hash >> m.shift) & hash_mask) | probe_inc
+ return u32(index), u32(meta)
+}
+
+[inline]
+fn (m &map) meta_less(_index u32, _metas u32) (u32, u32) {
+ mut index := _index
+ mut meta := _metas
+ for meta < unsafe { m.metas[index] } {
+ index += 2
+ meta += probe_inc
+ }
+ return index, meta
+}
+
+[inline]
+fn (mut m map) meta_greater(_index u32, _metas u32, kvi u32) {
+ mut meta := _metas
+ mut index := _index
+ mut kv_index := kvi
+ for unsafe { m.metas[index] } != 0 {
+ if meta > unsafe { m.metas[index] } {
+ unsafe {
+ tmp_meta := m.metas[index]
+ m.metas[index] = meta
+ meta = tmp_meta
+ tmp_index := m.metas[index + 1]
+ m.metas[index + 1] = kv_index
+ kv_index = tmp_index
+ }
+ }
+ index += 2
+ meta += probe_inc
+ }
+ unsafe {
+ m.metas[index] = meta
+ m.metas[index + 1] = kv_index
+ }
+ probe_count := (meta >> hashbits) - 1
+ m.ensure_extra_metas(probe_count)
+}
+
+[inline]
+fn (mut m map) ensure_extra_metas(probe_count u32) {
+ if (probe_count << 1) == m.extra_metas {
+ size_of_u32 := sizeof(u32)
+ old_mem_size := (m.even_index + 2 + m.extra_metas)
+ m.extra_metas += extra_metas_inc
+ mem_size := (m.even_index + 2 + m.extra_metas)
+ unsafe {
+ x := realloc_data(&byte(m.metas), int(size_of_u32 * old_mem_size), int(size_of_u32 * mem_size))
+ m.metas = &u32(x)
+ vmemset(m.metas + mem_size - extra_metas_inc, 0, int(sizeof(u32) * extra_metas_inc))
+ }
+ // Should almost never happen
+ if probe_count == 252 {
+ panic('Probe overflow')
+ }
+ }
+}
+
+// Insert new element to the map. The element is inserted if its key is
+// not equivalent to the key of any other element already in the container.
+// If the key already exists, its value is changed to the value of the new element.
+fn (mut m map) set(key voidptr, value voidptr) {
+ load_factor := f32(m.len << 1) / f32(m.even_index)
+ if load_factor > max_load_factor {
+ m.expand()
+ }
+ mut index, mut meta := m.key_to_index(key)
+ index, meta = m.meta_less(index, meta)
+ // While we might have a match
+ for meta == unsafe { m.metas[index] } {
+ kv_index := int(unsafe { m.metas[index + 1] })
+ pkey := unsafe { m.key_values.key(kv_index) }
+ if m.key_eq_fn(key, pkey) {
+ unsafe {
+ pval := m.key_values.value(kv_index)
+ vmemcpy(pval, value, m.value_bytes)
+ }
+ return
+ }
+ index += 2
+ meta += probe_inc
+ }
+ kv_index := m.key_values.expand()
+ unsafe {
+ pkey := m.key_values.key(kv_index)
+ pvalue := m.key_values.value(kv_index)
+ m.clone_fn(pkey, key)
+ vmemcpy(&byte(pvalue), value, m.value_bytes)
+ }
+ m.meta_greater(index, meta, u32(kv_index))
+ m.len++
+}
+
+// Doubles the size of the hashmap
+fn (mut m map) expand() {
+ old_cap := m.even_index
+ m.even_index = ((m.even_index + 2) << 1) - 2
+ // Check if any hashbits are left
+ if m.cached_hashbits == 0 {
+ m.shift += max_cached_hashbits
+ m.cached_hashbits = max_cached_hashbits
+ m.rehash()
+ } else {
+ m.cached_rehash(old_cap)
+ m.cached_hashbits--
+ }
+}
+
+// A rehash is the reconstruction of the hash table:
+// All the elements in the container are rearranged according
+// to their hash value into the newly sized key-value container.
+// Rehashes are performed when the load_factor is going to surpass
+// the max_load_factor in an operation.
+fn (mut m map) rehash() {
+ meta_bytes := sizeof(u32) * (m.even_index + 2 + m.extra_metas)
+ unsafe {
+ // TODO: use realloc_data here too
+ x := v_realloc(&byte(m.metas), int(meta_bytes))
+ m.metas = &u32(x)
+ vmemset(m.metas, 0, int(meta_bytes))
+ }
+ for i := 0; i < m.key_values.len; i++ {
+ if !m.key_values.has_index(i) {
+ continue
+ }
+ pkey := unsafe { m.key_values.key(i) }
+ mut index, mut meta := m.key_to_index(pkey)
+ index, meta = m.meta_less(index, meta)
+ m.meta_greater(index, meta, u32(i))
+ }
+}
+
+// This method works like rehash. However, instead of rehashing the
+// key completely, it uses the bits cached in `metas`.
+fn (mut m map) cached_rehash(old_cap u32) {
+ old_metas := m.metas
+ metasize := int(sizeof(u32) * (m.even_index + 2 + m.extra_metas))
+ m.metas = unsafe { &u32(vcalloc(metasize)) }
+ old_extra_metas := m.extra_metas
+ for i := u32(0); i <= old_cap + old_extra_metas; i += 2 {
+ if unsafe { old_metas[i] } == 0 {
+ continue
+ }
+ old_meta := unsafe { old_metas[i] }
+ old_probe_count := ((old_meta >> hashbits) - 1) << 1
+ old_index := (i - old_probe_count) & (m.even_index >> 1)
+ mut index := (old_index | (old_meta << m.shift)) & m.even_index
+ mut meta := (old_meta & hash_mask) | probe_inc
+ index, meta = m.meta_less(index, meta)
+ kv_index := unsafe { old_metas[i + 1] }
+ m.meta_greater(index, meta, kv_index)
+ }
+ unsafe { free(old_metas) }
+}
+
+// This method is used for assignment operators. If the argument-key
+// does not exist in the map, it's added to the map along with the zero/default value.
+// If the key exists, its respective value is returned.
+fn (mut m map) get_and_set(key voidptr, zero voidptr) voidptr {
+ for {
+ mut index, mut meta := m.key_to_index(key)
+ for {
+ if meta == unsafe { m.metas[index] } {
+ kv_index := int(unsafe { m.metas[index + 1] })
+ pkey := unsafe { m.key_values.key(kv_index) }
+ if m.key_eq_fn(key, pkey) {
+ pval := unsafe { m.key_values.value(kv_index) }
+ return unsafe { &byte(pval) }
+ }
+ }
+ index += 2
+ meta += probe_inc
+ if meta > unsafe { m.metas[index] } {
+ break
+ }
+ }
+ // Key not found, insert key with zero-value
+ m.set(key, zero)
+ }
+ assert false
+ return voidptr(0)
+}
+
+// If `key` matches the key of an element in the container,
+// the method returns a reference to its mapped value.
+// If not, a zero/default value is returned.
+fn (m &map) get(key voidptr, zero voidptr) voidptr {
+ mut index, mut meta := m.key_to_index(key)
+ for {
+ if meta == unsafe { m.metas[index] } {
+ kv_index := int(unsafe { m.metas[index + 1] })
+ pkey := unsafe { m.key_values.key(kv_index) }
+ if m.key_eq_fn(key, pkey) {
+ pval := unsafe { m.key_values.value(kv_index) }
+ return unsafe { &byte(pval) }
+ }
+ }
+ index += 2
+ meta += probe_inc
+ if meta > unsafe { m.metas[index] } {
+ break
+ }
+ }
+ return zero
+}
+
+// If `key` matches the key of an element in the container,
+// the method returns a reference to its mapped value.
+// If not, a zero pointer is returned.
+// This is used in `x := m['key'] or { ... }`
+fn (m &map) get_check(key voidptr) voidptr {
+ mut index, mut meta := m.key_to_index(key)
+ for {
+ if meta == unsafe { m.metas[index] } {
+ kv_index := int(unsafe { m.metas[index + 1] })
+ pkey := unsafe { m.key_values.key(kv_index) }
+ if m.key_eq_fn(key, pkey) {
+ pval := unsafe { m.key_values.value(kv_index) }
+ return unsafe { &byte(pval) }
+ }
+ }
+ index += 2
+ meta += probe_inc
+ if meta > unsafe { m.metas[index] } {
+ break
+ }
+ }
+ return 0
+}
+
+// Checks whether a particular key exists in the map.
+fn (m &map) exists(key voidptr) bool {
+ mut index, mut meta := m.key_to_index(key)
+ for {
+ if meta == unsafe { m.metas[index] } {
+ kv_index := int(unsafe { m.metas[index + 1] })
+ pkey := unsafe { m.key_values.key(kv_index) }
+ if m.key_eq_fn(key, pkey) {
+ return true
+ }
+ }
+ index += 2
+ meta += probe_inc
+ if meta > unsafe { m.metas[index] } {
+ break
+ }
+ }
+ return false
+}
+
+[inline]
+fn (mut d DenseArray) delete(i int) {
+ if d.deletes == 0 {
+ d.all_deleted = vcalloc(d.cap) // sets to 0
+ }
+ d.deletes++
+ unsafe {
+ d.all_deleted[i] = 1
+ }
+}
+
+// Removes the mapping of a particular key from the map.
+[unsafe]
+pub fn (mut m map) delete(key voidptr) {
+ mut index, mut meta := m.key_to_index(key)
+ index, meta = m.meta_less(index, meta)
+ // Perform backwards shifting
+ for meta == unsafe { m.metas[index] } {
+ kv_index := int(unsafe { m.metas[index + 1] })
+ pkey := unsafe { m.key_values.key(kv_index) }
+ if m.key_eq_fn(key, pkey) {
+ for (unsafe { m.metas[index + 2] } >> hashbits) > 1 {
+ unsafe {
+ m.metas[index] = m.metas[index + 2] - probe_inc
+ m.metas[index + 1] = m.metas[index + 3]
+ }
+ index += 2
+ }
+ m.len--
+ m.key_values.delete(kv_index)
+ unsafe {
+ m.metas[index] = 0
+ m.free_fn(pkey)
+ // Mark key as deleted
+ vmemset(pkey, 0, m.key_bytes)
+ }
+ if m.key_values.len <= 32 {
+ return
+ }
+ // Clean up key_values if too many have been deleted
+ if m.key_values.deletes >= (m.key_values.len >> 1) {
+ m.key_values.zeros_to_end()
+ m.rehash()
+ }
+ return
+ }
+ index += 2
+ meta += probe_inc
+ }
+}
+
+// Returns all keys in the map.
+fn (m &map) keys() array {
+ mut keys := __new_array(m.len, 0, m.key_bytes)
+ mut item := unsafe { &byte(keys.data) }
+ if m.key_values.deletes == 0 {
+ for i := 0; i < m.key_values.len; i++ {
+ unsafe {
+ pkey := m.key_values.key(i)
+ m.clone_fn(item, pkey)
+ item = item + m.key_bytes
+ }
+ }
+ return keys
+ }
+ for i := 0; i < m.key_values.len; i++ {
+ if !m.key_values.has_index(i) {
+ continue
+ }
+ unsafe {
+ pkey := m.key_values.key(i)
+ m.clone_fn(item, pkey)
+ item = item + m.key_bytes
+ }
+ }
+ return keys
+}
+
+// warning: only copies keys, does not clone
+[unsafe]
+fn (d &DenseArray) clone() DenseArray {
+ res := DenseArray{
+ key_bytes: d.key_bytes
+ value_bytes: d.value_bytes
+ cap: d.cap
+ len: d.len
+ deletes: d.deletes
+ all_deleted: 0
+ values: 0
+ keys: 0
+ }
+ unsafe {
+ if d.deletes != 0 {
+ res.all_deleted = memdup(d.all_deleted, d.cap)
+ }
+ res.keys = memdup(d.keys, d.cap * d.key_bytes)
+ res.values = memdup(d.values, d.cap * d.value_bytes)
+ }
+ return res
+}
+
+// clone returns a clone of the `map`.
+[unsafe]
+pub fn (m &map) clone() map {
+ metasize := int(sizeof(u32) * (m.even_index + 2 + m.extra_metas))
+ res := map{
+ key_bytes: m.key_bytes
+ value_bytes: m.value_bytes
+ even_index: m.even_index
+ cached_hashbits: m.cached_hashbits
+ shift: m.shift
+ key_values: unsafe { m.key_values.clone() }
+ metas: unsafe { &u32(malloc(metasize)) }
+ extra_metas: m.extra_metas
+ len: m.len
+ has_string_keys: m.has_string_keys
+ hash_fn: m.hash_fn
+ key_eq_fn: m.key_eq_fn
+ clone_fn: m.clone_fn
+ free_fn: m.free_fn
+ }
+ unsafe { vmemcpy(res.metas, m.metas, metasize) }
+ if !m.has_string_keys {
+ return res
+ }
+ // clone keys
+ for i in 0 .. m.key_values.len {
+ if !m.key_values.has_index(i) {
+ continue
+ }
+ m.clone_fn(res.key_values.key(i), m.key_values.key(i))
+ }
+ return res
+}
+
+// free releases all memory resources occupied by the `map`.
+[unsafe]
+pub fn (m &map) free() {
+ unsafe { free(m.metas) }
+ if m.key_values.deletes == 0 {
+ for i := 0; i < m.key_values.len; i++ {
+ unsafe {
+ pkey := m.key_values.key(i)
+ m.free_fn(pkey)
+ }
+ }
+ } else {
+ for i := 0; i < m.key_values.len; i++ {
+ if !m.key_values.has_index(i) {
+ continue
+ }
+ unsafe {
+ pkey := m.key_values.key(i)
+ m.free_fn(pkey)
+ }
+ }
+ unsafe { free(m.key_values.all_deleted) }
+ }
+ unsafe {
+ free(m.key_values.keys)
+ free(m.key_values.values)
+ }
+}