hashmap.mx raw
1 package runtime
2
3 // This is a hashmap implementation for the map[T]T type.
4 // It is very roughly based on the implementation of the Go hashmap:
5 //
6 // https://golang.org/src/runtime/map.go
7
8 import (
9 "moxie"
10 "unsafe"
11 )
12
13 // The underlying hashmap structure for Go.
14 type hashmap struct {
15 buckets unsafe.Pointer // pointer to array of buckets
16 seed uintptr
17 count uintptr
18 keySize uintptr // maybe this can store the key type as well? E.g. keysize == 5 means string?
19 valueSize uintptr
20 bucketBits uint8
21 keyEqual func(x, y unsafe.Pointer, n uintptr) bool
22 keyHash func(key unsafe.Pointer, size, seed uintptr) uint32
23 }
24
25 // A hashmap bucket. A bucket is a container of 8 key/value pairs: first the
26 // following two entries, then the 8 keys, then the 8 values. This somewhat odd
27 // ordering is to make sure the keys and values are well aligned when one of
28 // them is smaller than the system word size.
29 type hashmapBucket struct {
30 tophash [8]uint8
31 next *hashmapBucket // next bucket (if there are more than 8 in a chain)
32 // Followed by the actual keys, and then the actual values. These are
33 // allocated but as they're of variable size they can't be shown here.
34 }
35
36 type hashmapIterator struct {
37 buckets unsafe.Pointer // pointer to array of hashapBuckets
38 numBuckets uintptr // length of buckets array
39 bucketNumber uintptr // current index into buckets array
40 startBucket uintptr // starting location for iterator
41 bucket *hashmapBucket // current bucket in chain
42 bucketIndex uint8 // current index into bucket
43 startIndex uint8 // starting bucket index for iterator
44 wrapped bool // true if the iterator has wrapped
45 }
46
47 func hashmapNewIterator() unsafe.Pointer {
48 return unsafe.Pointer(new(hashmapIterator))
49 }
50
51 // Get the topmost 8 bits of the hash, without using a special value (like 0).
52 func hashmapTopHash(hash uint32) uint8 {
53 tophash := uint8(hash >> 24)
54 if tophash < 1 {
55 // 0 means empty slot, so make it bigger.
56 tophash += 1
57 }
58 return tophash
59 }
60
61 // Create a new hashmap with the given keySize and valueSize.
62 func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashmap {
63 bucketBits := uint8(0)
64 for hashmapHasSpaceToGrow(bucketBits) && hashmapOverLoadFactor(sizeHint, bucketBits) {
65 bucketBits++
66 }
67
68 bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + keySize*8 + valueSize*8
69 buckets := alloc(bucketBufSize*(1<<bucketBits), nil)
70
71 keyHash := hashmapKeyHashAlg(moxie.HashmapAlgorithm(alg))
72 keyEqual := hashmapKeyEqualAlg(moxie.HashmapAlgorithm(alg))
73
74 return &hashmap{
75 buckets: buckets,
76 seed: uintptr(fastrand()),
77 keySize: keySize,
78 valueSize: valueSize,
79 bucketBits: bucketBits,
80 keyEqual: keyEqual,
81 keyHash: keyHash,
82 }
83 }
84
85 // Remove all entries from the map, without actually deallocating the space for
86 // it. This is used for the clear builtin, and can be used to reuse a map (to
87 // avoid extra heap allocations).
88 func hashmapClear(m *hashmap) {
89 if m == nil {
90 // Nothing to do. According to the spec:
91 // > If the map or slice is nil, clear is a no-op.
92 return
93 }
94
95 m.count = 0
96 numBuckets := uintptr(1) << m.bucketBits
97 bucketSize := hashmapBucketSize(m)
98 for i := uintptr(0); i < numBuckets; i++ {
99 bucket := hashmapBucketAddr(m, m.buckets, i)
100 for bucket != nil {
101 // Clear the tophash, to mark these keys/values as removed.
102 bucket.tophash = [8]uint8{}
103
104 // Clear the keys and values in the bucket so that the GC won't pin
105 // these allocations.
106 memzero(unsafe.Add(unsafe.Pointer(bucket), unsafe.Sizeof(hashmapBucket{})), bucketSize-unsafe.Sizeof(hashmapBucket{}))
107
108 // Move on to the next bucket in the chain.
109 bucket = bucket.next
110 }
111 }
112 }
113
114 func hashmapKeyEqualAlg(alg moxie.HashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
115 switch alg {
116 case moxie.HashmapAlgorithmBinary:
117 return memequal
118 case moxie.HashmapAlgorithmContent:
119 return hashmapContentEqual
120 case moxie.HashmapAlgorithmInterface:
121 return hashmapInterfaceEqual
122 default:
123 // compiler bug :(
124 return nil
125 }
126 }
127
128 func hashmapKeyHashAlg(alg moxie.HashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 {
129 switch alg {
130 case moxie.HashmapAlgorithmBinary:
131 return hash32
132 case moxie.HashmapAlgorithmContent:
133 return hashmapContentPtrHash
134 case moxie.HashmapAlgorithmInterface:
135 return hashmapInterfacePtrHash
136 default:
137 // compiler bug :(
138 return nil
139 }
140 }
141
142 func hashmapHasSpaceToGrow(bucketBits uint8) bool {
143 // Over this limit, we're likely to overflow uintptrs during calculations
144 // or numbers of hash elements. Don't allow any more growth.
145 // With 29 bits, this is 2^32 elements anyway.
146 return bucketBits <= uint8((unsafe.Sizeof(uintptr(0))*8)-3)
147 }
148
149 func hashmapOverLoadFactor(n uintptr, bucketBits uint8) bool {
150 // "maximum" number of elements is 0.75 * buckets * elements per bucket
151 // to avoid overflow, this is calculated as
152 // max = 3 * (1/4 * buckets * elements per bucket)
153 // = 3 * (buckets * (elements per bucket)/4)
154 // = 3 * (buckets * (8/4)
155 // = 3 * (buckets * 2)
156 // = 6 * buckets
157 max := (uintptr(6) << bucketBits)
158 return n > max
159 }
160
161 // Return the number of entries in this hashmap, called from the len builtin.
162 // A nil hashmap is defined as having length 0.
163 //
164 //go:inline
165 func hashmapLen(m *hashmap) int {
166 if m == nil {
167 return 0
168 }
169 return int(m.count)
170 }
171
172 //go:inline
173 func hashmapBucketSize(m *hashmap) uintptr {
174 return unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
175 }
176
177 //go:inline
178 func hashmapBucketAddr(m *hashmap, buckets unsafe.Pointer, n uintptr) *hashmapBucket {
179 bucketSize := hashmapBucketSize(m)
180 bucket := (*hashmapBucket)(unsafe.Add(buckets, bucketSize*n))
181 return bucket
182 }
183
184 //go:inline
185 func hashmapBucketAddrForHash(m *hashmap, hash uint32) *hashmapBucket {
186 numBuckets := uintptr(1) << m.bucketBits
187 bucketNumber := (uintptr(hash) & (numBuckets - 1))
188 return hashmapBucketAddr(m, m.buckets, bucketNumber)
189 }
190
191 //go:inline
192 func hashmapSlotKey(m *hashmap, bucket *hashmapBucket, slot uint8) unsafe.Pointer {
193 slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(slot)
194 slotKey := unsafe.Add(unsafe.Pointer(bucket), slotKeyOffset)
195 return slotKey
196 }
197
198 //go:inline
199 func hashmapSlotValue(m *hashmap, bucket *hashmapBucket, slot uint8) unsafe.Pointer {
200 slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(slot)
201 slotValue := unsafe.Add(unsafe.Pointer(bucket), slotValueOffset)
202 return slotValue
203 }
204
205 // Set a specified key to a given value. Grow the map if necessary.
206 //
207 //go:nobounds
208 func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32) {
209 if hashmapHasSpaceToGrow(m.bucketBits) && hashmapOverLoadFactor(m.count, m.bucketBits) {
210 hashmapGrow(m)
211 // seed changed when we grew; rehash key with new seed
212 hash = m.keyHash(key, m.keySize, m.seed)
213 }
214
215 tophash := hashmapTopHash(hash)
216 bucket := hashmapBucketAddrForHash(m, hash)
217 var lastBucket *hashmapBucket
218
219 // See whether the key already exists somewhere.
220 var emptySlotKey unsafe.Pointer
221 var emptySlotValue unsafe.Pointer
222 var emptySlotTophash *byte
223 for bucket != nil {
224 for i := uint8(0); i < 8; i++ {
225 slotKey := hashmapSlotKey(m, bucket, i)
226 slotValue := hashmapSlotValue(m, bucket, i)
227 if bucket.tophash[i] == 0 && emptySlotKey == nil {
228 // Found an empty slot, store it for if we couldn't find an
229 // existing slot.
230 emptySlotKey = slotKey
231 emptySlotValue = slotValue
232 emptySlotTophash = &bucket.tophash[i]
233 }
234 if bucket.tophash[i] == tophash {
235 // Could be an existing key that's the same.
236 if m.keyEqual(key, slotKey, m.keySize) {
237 // found same key, replace it
238 memcpy(slotValue, value, m.valueSize)
239 return
240 }
241 }
242 }
243 lastBucket = bucket
244 bucket = bucket.next
245 }
246 if emptySlotKey == nil {
247 // Add a new bucket to the bucket chain.
248 // TODO: rebalance if necessary to avoid O(n) insert and lookup time.
249 lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash))
250 return
251 }
252 m.count++
253 memcpy(emptySlotKey, key, m.keySize)
254 memcpy(emptySlotValue, value, m.valueSize)
255 *emptySlotTophash = tophash
256 }
257
258 // hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and
259 // value into the bucket, and returns a pointer to this bucket.
260 func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket {
261 bucketBufSize := hashmapBucketSize(m)
262 bucketBuf := alloc(bucketBufSize, nil)
263 bucket := (*hashmapBucket)(bucketBuf)
264
265 // Insert into the first slot, which is empty as it has just been allocated.
266 slotKey := hashmapSlotKey(m, bucket, 0)
267 slotValue := hashmapSlotValue(m, bucket, 0)
268 m.count++
269 memcpy(slotKey, key, m.keySize)
270 memcpy(slotValue, value, m.valueSize)
271 bucket.tophash[0] = tophash
272 return bucket
273 }
274
275 func hashmapGrow(m *hashmap) {
276 // allocate our new buckets twice as big
277 n := hashmapCopy(m, m.bucketBits+1)
278 *m = n
279 }
280
281 //go:linkname hashmapClone maps.clone
282 func hashmapClone(intf _interface) _interface {
283 typ, val := decomposeInterface(intf)
284 m := (*hashmap)(val)
285 n := hashmapCopy(m, m.bucketBits)
286 return composeInterface(typ, unsafe.Pointer(&n))
287 }
288
289 func hashmapCopy(m *hashmap, sizeBits uint8) hashmap {
290 // clone map as empty
291 n := *m
292 n.count = 0
293 n.seed = uintptr(fastrand())
294
295 n.bucketBits = sizeBits
296 numBuckets := uintptr(1) << n.bucketBits
297 bucketBufSize := hashmapBucketSize(m)
298 n.buckets = alloc(bucketBufSize*numBuckets, nil)
299
300 // use a hashmap iterator to go through the old map
301 var it hashmapIterator
302
303 var key = alloc(m.keySize, nil)
304 var value = alloc(m.valueSize, nil)
305
306 for hashmapNext(m, &it, key, value) {
307 h := n.keyHash(key, uintptr(n.keySize), n.seed)
308 hashmapSet(&n, key, value, h)
309 }
310
311 return n
312 }
313
314 // Get the value of a specified key, or zero the value if not found.
315 //
316 //go:nobounds
317 func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32) bool {
318 if m == nil {
319 // Getting a value out of a nil map is valid. From the spec:
320 // > if the map is nil or does not contain such an entry, a[x] is the
321 // > zero value for the element type of M
322 memzero(value, uintptr(valueSize))
323 return false
324 }
325
326 tophash := hashmapTopHash(hash)
327 bucket := hashmapBucketAddrForHash(m, hash)
328
329 // Try to find the key.
330 for bucket != nil {
331 for i := uint8(0); i < 8; i++ {
332 slotKey := hashmapSlotKey(m, bucket, i)
333 slotValue := hashmapSlotValue(m, bucket, i)
334 if bucket.tophash[i] == tophash {
335 // This could be the key we're looking for.
336 if m.keyEqual(key, slotKey, m.keySize) {
337 // Found the key, copy it.
338 memcpy(value, slotValue, m.valueSize)
339 return true
340 }
341 }
342 }
343 bucket = bucket.next
344 }
345
346 // Did not find the key.
347 memzero(value, m.valueSize)
348 return false
349 }
350
351 // Delete a given key from the map. No-op when the key does not exist in the
352 // map.
353 //
354 //go:nobounds
355 func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32) {
356 if m == nil {
357 // The delete builtin is defined even when the map is nil. From the spec:
358 // > If the map m is nil or the element m[k] does not exist, delete is a
359 // > no-op.
360 return
361 }
362
363 tophash := hashmapTopHash(hash)
364 bucket := hashmapBucketAddrForHash(m, hash)
365
366 // Try to find the key.
367 for bucket != nil {
368 for i := uint8(0); i < 8; i++ {
369 slotKey := hashmapSlotKey(m, bucket, i)
370 if bucket.tophash[i] == tophash {
371 // This could be the key we're looking for.
372 if m.keyEqual(key, slotKey, m.keySize) {
373 // Found the key, delete it.
374 bucket.tophash[i] = 0
375 // Zero out the key and value so garbage collector doesn't pin the allocations.
376 memzero(slotKey, m.keySize)
377 slotValue := hashmapSlotValue(m, bucket, i)
378 memzero(slotValue, m.valueSize)
379 m.count--
380 return
381 }
382 }
383 }
384 bucket = bucket.next
385 }
386 }
387
388 // Iterate over a hashmap.
389 //
390 //go:nobounds
391 func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool {
392 if m == nil {
393 // From the spec: If the map is nil, the number of iterations is 0.
394 return false
395 }
396
397 if it.buckets == nil {
398 // initialize iterator
399 it.buckets = m.buckets
400 it.numBuckets = uintptr(1) << m.bucketBits
401 it.startBucket = uintptr(fastrand()) & (it.numBuckets - 1)
402 it.startIndex = uint8(fastrand() & 7)
403
404 it.bucketNumber = it.startBucket
405 it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber)
406 it.bucketIndex = it.startIndex
407 }
408
409 for {
410 // If we've wrapped and we're back at our starting location, terminate the iteration.
411 if it.wrapped && it.bucketNumber == it.startBucket && it.bucketIndex == it.startIndex {
412 return false
413 }
414
415 if it.bucketIndex >= 8 {
416 // end of bucket, move to the next in the chain
417 it.bucketIndex = 0
418 it.bucket = it.bucket.next
419 }
420
421 if it.bucket == nil {
422 it.bucketNumber++ // next bucket
423 if it.bucketNumber >= it.numBuckets {
424 // went through all buckets -- wrap around
425 it.bucketNumber = 0
426 it.wrapped = true
427 }
428 it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber)
429 continue
430 }
431
432 if it.bucket.tophash[it.bucketIndex] == 0 {
433 // slot is empty - move on
434 it.bucketIndex++
435 continue
436 }
437
438 // Found a key.
439 slotKey := hashmapSlotKey(m, it.bucket, it.bucketIndex)
440 memcpy(key, slotKey, m.keySize)
441
442 if it.buckets == m.buckets {
443 // Our view of the buckets is the same as the parent map.
444 // Just copy the value we have
445 slotValue := hashmapSlotValue(m, it.bucket, it.bucketIndex)
446 memcpy(value, slotValue, m.valueSize)
447 it.bucketIndex++
448 } else {
449 it.bucketIndex++
450
451 // Our view of the buckets doesn't match the parent map.
452 // Look up the key in the new buckets and return that value if it exists
453 hash := m.keyHash(key, m.keySize, m.seed)
454 ok := hashmapGet(m, key, value, m.valueSize, hash)
455 if !ok {
456 // doesn't exist in parent map; try next key
457 continue
458 }
459
460 // All good.
461 }
462
463 return true
464 }
465 }
466
467 // Hashmap with plain binary data keys (not containing strings etc.).
468 func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
469 if m == nil {
470 nilMapPanic()
471 }
472 hash := hash32(key, m.keySize, m.seed)
473 hashmapSet(m, key, value, hash)
474 }
475
476 func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool {
477 if m == nil {
478 memzero(value, uintptr(valueSize))
479 return false
480 }
481 hash := hash32(key, m.keySize, m.seed)
482 return hashmapGet(m, key, value, valueSize, hash)
483 }
484
485 func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
486 if m == nil {
487 return
488 }
489 hash := hash32(key, m.keySize, m.seed)
490 hashmapDelete(m, key, hash)
491 }
492
493 // Hashmap with string keys (a common case).
494
495 func hashmapContentEqual(x, y unsafe.Pointer, n uintptr) bool {
496 return *(*string)(x) == *(*string)(y)
497 }
498
499 func hashmapContentHash(s string, seed uintptr) uint32 {
500 _s := (*_string)(unsafe.Pointer(&s))
501 return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length), seed)
502 }
503
504 func hashmapContentPtrHash(sptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
505 _s := *(*_string)(sptr)
506 return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length), seed)
507 }
508
509 func hashmapContentSet(m *hashmap, key string, value unsafe.Pointer) {
510 if m == nil {
511 nilMapPanic()
512 }
513 hash := hashmapContentHash(key, m.seed)
514 hashmapSet(m, unsafe.Pointer(&key), value, hash)
515 }
516
517 func hashmapContentGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool {
518 if m == nil {
519 memzero(value, uintptr(valueSize))
520 return false
521 }
522 hash := hashmapContentHash(key, m.seed)
523 return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
524 }
525
526 func hashmapContentDelete(m *hashmap, key string) {
527 if m == nil {
528 return
529 }
530 hash := hashmapContentHash(key, m.seed)
531 hashmapDelete(m, unsafe.Pointer(&key), hash)
532 }
533
534 // Hashmap with interface keys (for everything else).
535
536 // This is a method that is intentionally unexported in the reflect package. It
537 // is identical to the Interface() method call, except it doesn't check whether
538 // a field is exported and thus allows circumventing the type system.
539 // The hash function needs it as it also needs to hash unexported struct fields.
540 //
541 func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
542 f := *(*uint32)(ptr)
543 if f == 0x80000000 {
544 // convert -0 to 0 for hashing
545 f = 0
546 }
547 return hash32(unsafe.Pointer(&f), 4, seed)
548 }
549
550 func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
551 f := *(*uint64)(ptr)
552 if f == 0x8000000000000000 {
553 // convert -0 to 0 for hashing
554 f = 0
555 }
556 return hash32(unsafe.Pointer(&f), 8, seed)
557 }
558
559 func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
560 iface := (*_interface)(unsafe.Pointer(&itf))
561 if iface.typecode == nil {
562 return 0 // nil interface
563 }
564
565 t := (*rawType)(iface.typecode)
566 value := iface.value
567
568 // Convert interface value to direct data pointer.
569 ptr := value
570 if t.size() <= unsafe.Sizeof(uintptr(0)) {
571 ptr = unsafe.Pointer(&value)
572 }
573
574 return hashmapPtrHash(t, ptr, seed)
575 }
576
577 // hashmapPtrHash hashes the value at ptr. ptr always points directly to the
578 // value data — no interface packing. Used for recursive struct/array hashing.
579 func hashmapPtrHash(t *rawType, ptr unsafe.Pointer, seed uintptr) uint32 {
580 k := t.kind()
581
582 switch k {
583 case Int, Int8, Int16, Int32, Int64:
584 return hash32(ptr, t.size(), seed)
585 case Bool, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
586 return hash32(ptr, t.size(), seed)
587 case Float32:
588 return hashmapFloat32Hash(ptr, seed)
589 case Float64:
590 return hashmapFloat64Hash(ptr, seed)
591 case Complex64:
592 rptr, iptr := ptr, unsafe.Add(ptr, 4)
593 return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed)
594 case Complex128:
595 rptr, iptr := ptr, unsafe.Add(ptr, 8)
596 return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed)
597 case kindBytes:
598 s := *(*_string)(ptr)
599 return hashmapContentHash(*(*string)(unsafe.Pointer(&s)), seed)
600 case kindChan, kindPointer, kindUnsafePointer:
601 return hash32(ptr, t.size(), seed)
602 case kindArray:
603 elemType := t.elem()
604 elemSize := elemType.size()
605 length := t.arrayLen()
606 var hash uint32
607 for i := 0; i < length; i++ {
608 ep := unsafe.Add(ptr, uintptr(i)*elemSize)
609 hash ^= hashmapPtrHash(elemType, ep, seed)
610 }
611 return hash
612 case kindStruct:
613 nf := t.numField()
614 var hash uint32
615 for i := 0; i < nf; i++ {
616 ft := t.structFieldType(i)
617 off := t.structFieldOffset(i)
618 fp := unsafe.Add(ptr, off)
619 hash ^= hashmapPtrHash(ft, fp, seed)
620 }
621 return hash
622 case kindInterface:
623 return hashmapInterfaceHash(*(*interface{})(ptr), seed)
624 default:
625 runtimePanic("comparing un-comparable type")
626 return 0
627 }
628 }
629
630 func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
631 _i := *(*interface{})(iptr)
632 return hashmapInterfaceHash(_i, seed)
633 }
634
635 func hashmapInterfaceEqual(x, y unsafe.Pointer, n uintptr) bool {
636 return *(*interface{})(x) == *(*interface{})(y)
637 }
638
639 func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) {
640 if m == nil {
641 nilMapPanic()
642 }
643 hash := hashmapInterfaceHash(key, m.seed)
644 hashmapSet(m, unsafe.Pointer(&key), value, hash)
645 }
646
647 func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valueSize uintptr) bool {
648 if m == nil {
649 memzero(value, uintptr(valueSize))
650 return false
651 }
652 hash := hashmapInterfaceHash(key, m.seed)
653 return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
654 }
655
656 func hashmapInterfaceDelete(m *hashmap, key interface{}) {
657 if m == nil {
658 return
659 }
660 hash := hashmapInterfaceHash(key, m.seed)
661 hashmapDelete(m, unsafe.Pointer(&key), hash)
662 }
663