package runtime

// This is a hashmap implementation for the map[T]T type.
// It is very roughly based on the implementation of the Go hashmap:
//
//     https://golang.org/src/runtime/map.go

import (
	"internal/reflectlite"
	"tinygo"
	"unsafe"
)

// The underlying hashmap structure for Go.
type hashmap struct {
	buckets       unsafe.Pointer // pointer to array of buckets
	seed          uintptr
	count         uintptr
	keySize       uintptr
	valueSize     uintptr
	keySlotSize   uintptr // == keySize, or sizeof(ptr) if indirect
	valueSlotSize uintptr // == valueSize, or sizeof(ptr) if indirect
	bucketBits    uint8
	flags         uint8
	keyEqual      func(x, y unsafe.Pointer, n uintptr) bool
	keyHash       func(key unsafe.Pointer, size, seed uintptr) uint32
}

const (
	hashmapMaxKeySize   = 128
	hashmapMaxValueSize = 128

	hashmapFlagIndirectKey   = 1 << 0
	hashmapFlagIndirectValue = 1 << 1
)

// A hashmap bucket. A bucket is a container of 8 key/value pairs: first the
// following two entries, then the 8 keys, then the 8 values. This somewhat odd
// ordering is to make sure the keys and values are well aligned when one of
// them is smaller than the system word size.
type hashmapBucket struct {
	tophash [8]uint8
	next    *hashmapBucket // next bucket (if there are more than 8 in a chain)
	// Followed by the actual keys, and then the actual values. These are
	// allocated but as they're of variable size they can't be shown here.
}

// hashmapBucketHeaderSize is the offset in bytes from the start of a bucket to
// the first key, aligned to 8 bytes. This ensures that keys requiring 8-byte
// alignment (float64, complex128, uint64 on strict-alignment architectures
// like MIPS) are properly aligned in the bucket.
const hashmapBucketHeaderSize = (unsafe.Sizeof(hashmapBucket{}) + 7) &^ 7

// hashmapKeySlotSize returns the size of a key slot in the bucket. For indirect
// keys, this is the pointer size; otherwise the actual key size.
//
//go:inline
func hashmapKeySlotSize(m *hashmap) uintptr {
	return m.keySlotSize
}

// hashmapValueSlotSize returns the size of a value slot in the bucket.
//
//go:inline
func hashmapValueSlotSize(m *hashmap) uintptr {
	return m.valueSlotSize
}

// hashmapSlotKeyData returns a pointer to the actual key data for a given slot.
// For indirect keys, the slot contains a pointer that must be dereferenced.
//
//go:inline
func hashmapSlotKeyData(m *hashmap, slotKey unsafe.Pointer) unsafe.Pointer {
	if m.flags&hashmapFlagIndirectKey != 0 {
		return *(*unsafe.Pointer)(slotKey)
	}
	return slotKey
}

// hashmapSlotValueData returns a pointer to the actual value data for a given slot.
//
//go:inline
func hashmapSlotValueData(m *hashmap, slotValue unsafe.Pointer) unsafe.Pointer {
	if m.flags&hashmapFlagIndirectValue != 0 {
		return *(*unsafe.Pointer)(slotValue)
	}
	return slotValue
}

type hashmapIterator struct {
	buckets      unsafe.Pointer // pointer to array of hashapBuckets
	numBuckets   uintptr        // length of buckets array
	bucketNumber uintptr        // current index into buckets array
	startBucket  uintptr        // starting location for iterator
	bucket       *hashmapBucket // current bucket in chain
	bucketIndex  uint8          // current index into bucket
	startIndex   uint8          // starting bucket index for iterator
	wrapped      bool           // true if the iterator has wrapped
}

func hashmapNewIterator() unsafe.Pointer {
	return unsafe.Pointer(new(hashmapIterator))
}

// Get the topmost 8 bits of the hash, without using a special value (like 0).
func hashmapTopHash(hash uint32) uint8 {
	tophash := uint8(hash >> 24)
	if tophash < 1 {
		// 0 means empty slot, so make it bigger.
		tophash += 1
	}
	return tophash
}

// Create a new hashmap with the given keySize and valueSize.
func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashmap {
	bucketBits := uint8(0)
	for hashmapHasSpaceToGrow(bucketBits) && hashmapOverLoadFactor(sizeHint, bucketBits) {
		bucketBits++
	}

	var flags uint8
	keySlotSize := keySize
	if keySize > hashmapMaxKeySize {
		flags |= hashmapFlagIndirectKey
		keySlotSize = unsafe.Sizeof(unsafe.Pointer(nil))
	}
	valueSlotSize := valueSize
	if valueSize > hashmapMaxValueSize {
		flags |= hashmapFlagIndirectValue
		valueSlotSize = unsafe.Sizeof(unsafe.Pointer(nil))
	}

	bucketBufSize := hashmapBucketHeaderSize + keySlotSize*8 + valueSlotSize*8
	buckets := alloc(bucketBufSize*(1<<bucketBits), nil)

	keyHash := hashmapKeyHashAlg(tinygo.HashmapAlgorithm(alg))
	keyEqual := hashmapKeyEqualAlg(tinygo.HashmapAlgorithm(alg))

	return &hashmap{
		buckets:       buckets,
		seed:          uintptr(fastrand()),
		keySize:       keySize,
		valueSize:     valueSize,
		keySlotSize:   keySlotSize,
		valueSlotSize: valueSlotSize,
		bucketBits:    bucketBits,
		flags:         flags,
		keyEqual:      keyEqual,
		keyHash:       keyHash,
	}
}

// Remove all entries from the map, without actually deallocating the space for
// it. This is used for the clear builtin, and can be used to reuse a map (to
// avoid extra heap allocations).
func hashmapClear(m *hashmap) {
	if m == nil {
		// Nothing to do. According to the spec:
		// > If the map or slice is nil, clear is a no-op.
		return
	}

	m.count = 0
	numBuckets := uintptr(1) << m.bucketBits
	bucketSize := hashmapBucketSize(m)
	for i := uintptr(0); i < numBuckets; i++ {
		bucket := hashmapBucketAddr(m, m.buckets, i)
		for bucket != nil {
			// Clear the tophash, to mark these keys/values as removed.
			bucket.tophash = [8]uint8{}

			// Clear the keys and values in the bucket so that the GC won't pin
			// these allocations.
			memzero(unsafe.Add(unsafe.Pointer(bucket), unsafe.Sizeof(hashmapBucket{})), bucketSize-unsafe.Sizeof(hashmapBucket{}))

			// Move on to the next bucket in the chain.
			bucket = bucket.next
		}
	}
}

func hashmapKeyEqualAlg(alg tinygo.HashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
	switch alg {
	case tinygo.HashmapAlgorithmBinary:
		return memequal
	case tinygo.HashmapAlgorithmString:
		return hashmapStringEqual
	default:
		// compiler bug :(
		return nil
	}
}

func hashmapKeyHashAlg(alg tinygo.HashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 {
	switch alg {
	case tinygo.HashmapAlgorithmBinary:
		return hash32
	case tinygo.HashmapAlgorithmString:
		return hashmapStringPtrHash
	default:
		// compiler bug :(
		return nil
	}
}

func hashmapHasSpaceToGrow(bucketBits uint8) bool {
	// Over this limit, we're likely to overflow uintptrs during calculations
	// or numbers of hash elements.   Don't allow any more growth.
	// With 29 bits, this is 2^32 elements anyway.
	return bucketBits <= uint8((unsafe.Sizeof(uintptr(0))*8)-3)
}

func hashmapOverLoadFactor(n uintptr, bucketBits uint8) bool {
	// "maximum" number of elements is 0.75 * buckets * elements per bucket
	// to avoid overflow, this is calculated as
	// max = 3 * (1/4 * buckets * elements per bucket)
	//     = 3 * (buckets * (elements per bucket)/4)
	//     = 3 * (buckets * (8/4)
	//     = 3 * (buckets * 2)
	//     = 6 * buckets
	max := (uintptr(6) << bucketBits)
	return n > max
}

// Return the number of entries in this hashmap, called from the len builtin.
// A nil hashmap is defined as having length 0.
//
//go:inline
func hashmapLen(m *hashmap) int {
	if m == nil {
		return 0
	}
	return int(m.count)
}

//go:inline
func hashmapBucketSize(m *hashmap) uintptr {
	return hashmapBucketHeaderSize + hashmapKeySlotSize(m)*8 + hashmapValueSlotSize(m)*8
}

//go:inline
func hashmapBucketAddr(m *hashmap, buckets unsafe.Pointer, n uintptr) *hashmapBucket {
	bucketSize := hashmapBucketSize(m)
	bucket := (*hashmapBucket)(unsafe.Add(buckets, bucketSize*n))
	return bucket
}

//go:inline
func hashmapBucketAddrForHash(m *hashmap, hash uint32) *hashmapBucket {
	numBuckets := uintptr(1) << m.bucketBits
	bucketNumber := (uintptr(hash) & (numBuckets - 1))
	return hashmapBucketAddr(m, m.buckets, bucketNumber)
}

//go:inline
func hashmapSlotKey(m *hashmap, bucket *hashmapBucket, slot uint8) unsafe.Pointer {
	slotKeyOffset := hashmapBucketHeaderSize + hashmapKeySlotSize(m)*uintptr(slot)
	return unsafe.Add(unsafe.Pointer(bucket), slotKeyOffset)
}

//go:inline
func hashmapSlotValue(m *hashmap, bucket *hashmapBucket, slot uint8) unsafe.Pointer {
	slotValueOffset := hashmapBucketHeaderSize + hashmapKeySlotSize(m)*8 + hashmapValueSlotSize(m)*uintptr(slot)
	return unsafe.Add(unsafe.Pointer(bucket), slotValueOffset)
}

// Set a specified key to a given value. Grow the map if necessary.
//
//go:nobounds
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32) {
	if hashmapHasSpaceToGrow(m.bucketBits) && hashmapOverLoadFactor(m.count, m.bucketBits) {
		hashmapGrow(m)
		// seed changed when we grew; rehash key with new seed
		hash = m.keyHash(key, m.keySize, m.seed)
	}

	tophash := hashmapTopHash(hash)
	bucket := hashmapBucketAddrForHash(m, hash)
	var lastBucket *hashmapBucket

	// See whether the key already exists somewhere.
	var emptySlotKey unsafe.Pointer
	var emptySlotValue unsafe.Pointer
	var emptySlotTophash *byte
	for bucket != nil {
		for i := uint8(0); i < 8; i++ {
			slotKey := hashmapSlotKey(m, bucket, i)
			slotValue := hashmapSlotValue(m, bucket, i)
			if bucket.tophash[i] == 0 && emptySlotKey == nil {
				// Found an empty slot, store it for if we couldn't find an
				// existing slot.
				emptySlotKey = slotKey
				emptySlotValue = slotValue
				emptySlotTophash = &bucket.tophash[i]
			}
			if bucket.tophash[i] == tophash {
				// Could be an existing key that's the same.
				if m.keyEqual(key, hashmapSlotKeyData(m, slotKey), m.keySize) {
					// found same key, replace the value
					hashmapStoreValue(m, slotValue, value)
					return
				}
			}
		}
		lastBucket = bucket
		bucket = bucket.next
	}
	if emptySlotKey == nil {
		// Add a new bucket to the bucket chain.
		// TODO: rebalance if necessary to avoid O(n) insert and lookup time.
		lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash))
		return
	}
	m.count++
	hashmapStoreKey(m, emptySlotKey, key)
	hashmapStoreValue(m, emptySlotValue, value)
	*emptySlotTophash = tophash
}

// hashmapStoreKey stores a key into a bucket slot, allocating backing storage
// if the key is indirect (first insert) or copying into it (shouldn't happen
// for keys, but handles it correctly).
//
//go:inline
func hashmapStoreKey(m *hashmap, slotKey, key unsafe.Pointer) {
	if m.flags&hashmapFlagIndirectKey != 0 {
		p := alloc(m.keySize, nil)
		memcpy(p, key, m.keySize)
		*(*unsafe.Pointer)(slotKey) = p
	} else {
		memcpy(slotKey, key, m.keySize)
	}
}

// hashmapStoreValue stores a value into a bucket slot. For indirect values,
// it allocates backing storage on first insert or copies into the existing
// backing on overwrite.
//
//go:inline
func hashmapStoreValue(m *hashmap, slotValue, value unsafe.Pointer) {
	if m.flags&hashmapFlagIndirectValue != 0 {
		p := *(*unsafe.Pointer)(slotValue)
		if p == nil {
			// First insert: allocate backing storage.
			p = alloc(m.valueSize, nil)
			*(*unsafe.Pointer)(slotValue) = p
		}
		memcpy(p, value, m.valueSize)
	} else {
		memcpy(slotValue, value, m.valueSize)
	}
}

// hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and
// value into the bucket, and returns a pointer to this bucket.
func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket {
	bucketBufSize := hashmapBucketSize(m)
	bucketBuf := alloc(bucketBufSize, nil)
	bucket := (*hashmapBucket)(bucketBuf)

	// Insert into the first slot, which is empty as it has just been allocated.
	slotKey := hashmapSlotKey(m, bucket, 0)
	slotValue := hashmapSlotValue(m, bucket, 0)
	m.count++
	hashmapStoreKey(m, slotKey, key)
	hashmapStoreValue(m, slotValue, value)
	bucket.tophash[0] = tophash
	return bucket
}

func hashmapGrow(m *hashmap) {
	// allocate our new buckets twice as big
	n := hashmapCopy(m, m.bucketBits+1)
	*m = n
}

//go:linkname hashmapClone maps.clone
func hashmapClone(intf _interface) _interface {
	typ, val := decomposeInterface(intf)
	m := (*hashmap)(val)
	n := hashmapCopy(m, m.bucketBits)
	return composeInterface(typ, unsafe.Pointer(&n))
}

func hashmapCopy(m *hashmap, sizeBits uint8) hashmap {
	// clone map as empty
	n := *m
	n.count = 0
	n.seed = uintptr(fastrand())

	n.bucketBits = sizeBits
	numBuckets := uintptr(1) << n.bucketBits
	bucketBufSize := hashmapBucketSize(m)
	n.buckets = alloc(bucketBufSize*numBuckets, nil)

	// use a hashmap iterator to go through the old map
	var it hashmapIterator

	var key = alloc(m.keySize, nil)
	var value = alloc(m.valueSize, nil)

	for hashmapNext(m, &it, key, value) {
		h := n.keyHash(key, uintptr(n.keySize), n.seed)
		hashmapSet(&n, key, value, h)
	}

	return n
}

// Get the value of a specified key, or zero the value if not found.
//
//go:nobounds
func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32) bool {
	if m == nil {
		// Getting a value out of a nil map is valid. From the spec:
		// > if the map is nil or does not contain such an entry, a[x] is the
		// > zero value for the element type of M
		memzero(value, uintptr(valueSize))
		return false
	}

	tophash := hashmapTopHash(hash)
	bucket := hashmapBucketAddrForHash(m, hash)

	// Try to find the key.
	for bucket != nil {
		for i := uint8(0); i < 8; i++ {
			slotKey := hashmapSlotKey(m, bucket, i)
			if bucket.tophash[i] == tophash {
				// This could be the key we're looking for.
				if m.keyEqual(key, hashmapSlotKeyData(m, slotKey), m.keySize) {
					// Found the key, copy it.
					slotValue := hashmapSlotValue(m, bucket, i)
					memcpy(value, hashmapSlotValueData(m, slotValue), m.valueSize)
					return true
				}
			}
		}
		bucket = bucket.next
	}

	// Did not find the key.
	memzero(value, m.valueSize)
	return false
}

// Delete a given key from the map. No-op when the key does not exist in the
// map.
//
//go:nobounds
func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32) {
	if m == nil {
		// The delete builtin is defined even when the map is nil. From the spec:
		// > If the map m is nil or the element m[k] does not exist, delete is a
		// > no-op.
		return
	}

	tophash := hashmapTopHash(hash)
	bucket := hashmapBucketAddrForHash(m, hash)

	// Try to find the key.
	for bucket != nil {
		for i := uint8(0); i < 8; i++ {
			slotKey := hashmapSlotKey(m, bucket, i)
			if bucket.tophash[i] == tophash {
				// This could be the key we're looking for.
				if m.keyEqual(key, hashmapSlotKeyData(m, slotKey), m.keySize) {
					// Found the key, delete it.
					bucket.tophash[i] = 0
					// Zero out the slot so the GC won't pin the allocations.
					keySlotSize := hashmapKeySlotSize(m)
					memzero(slotKey, keySlotSize)
					slotValue := hashmapSlotValue(m, bucket, i)
					valueSlotSize := hashmapValueSlotSize(m)
					memzero(slotValue, valueSlotSize)
					m.count--
					return
				}
			}
		}
		bucket = bucket.next
	}
}

// Iterate over a hashmap.
//
//go:nobounds
func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool {
	if m == nil {
		// From the spec: If the map is nil, the number of iterations is 0.
		return false
	}

	if it.buckets == nil {
		// initialize iterator
		it.buckets = m.buckets
		it.numBuckets = uintptr(1) << m.bucketBits
		it.startBucket = uintptr(fastrand()) & (it.numBuckets - 1)
		it.startIndex = uint8(fastrand() & 7)

		it.bucketNumber = it.startBucket
		it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber)
		it.bucketIndex = it.startIndex
	}

	for {
		// If we've wrapped and we're back at our starting location, terminate the iteration.
		if it.wrapped && it.bucketNumber == it.startBucket && it.bucketIndex == it.startIndex {
			return false
		}

		if it.bucketIndex >= 8 {
			// end of bucket, move to the next in the chain
			it.bucketIndex = 0
			it.bucket = it.bucket.next
		}

		if it.bucket == nil {
			it.bucketNumber++ // next bucket
			if it.bucketNumber >= it.numBuckets {
				// went through all buckets -- wrap around
				it.bucketNumber = 0
				it.wrapped = true
			}
			it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber)
			continue
		}

		if it.bucket.tophash[it.bucketIndex] == 0 {
			// slot is empty - move on
			it.bucketIndex++
			continue
		}

		// Found a key.
		slotKey := hashmapSlotKey(m, it.bucket, it.bucketIndex)
		memcpy(key, hashmapSlotKeyData(m, slotKey), m.keySize)

		if it.buckets == m.buckets {
			// Our view of the buckets is the same as the parent map.
			// Just copy the value we have
			slotValue := hashmapSlotValue(m, it.bucket, it.bucketIndex)
			memcpy(value, hashmapSlotValueData(m, slotValue), m.valueSize)
			it.bucketIndex++
		} else {
			it.bucketIndex++

			// Our view of the buckets doesn't match the parent map.
			// Look up the key in the new buckets and return that value if it exists
			hash := m.keyHash(key, m.keySize, m.seed)
			ok := hashmapGet(m, key, value, m.valueSize, hash)
			if !ok {
				// doesn't exist in parent map; try next key
				continue
			}

			// All good.
		}

		return true
	}
}

// Hashmap with plain binary data keys (not containing strings etc.).
func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
	if m == nil {
		nilMapPanic()
	}
	hash := hash32(key, m.keySize, m.seed)
	hashmapSet(m, key, value, hash)
}

func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool {
	if m == nil {
		memzero(value, uintptr(valueSize))
		return false
	}
	hash := hash32(key, m.keySize, m.seed)
	return hashmapGet(m, key, value, valueSize, hash)
}

func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
	if m == nil {
		return
	}
	hash := hash32(key, m.keySize, m.seed)
	hashmapDelete(m, key, hash)
}

// Hashmap with compiler-generated key hash/equal functions.
// Unlike the binary path (which uses hash32/memequal), these use the
// type-specific keyHash and keyEqual function pointers stored in the hashmap
// struct. This is used for composite key types (e.g. structs containing
// strings) where the compiler generates specialized hash/equal functions.

func hashmapGenericSet(m *hashmap, key, value unsafe.Pointer) {
	if m == nil {
		nilMapPanic()
	}
	hash := m.keyHash(key, m.keySize, m.seed)
	hashmapSet(m, key, value, hash)
}

func hashmapGenericGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool {
	if m == nil {
		memzero(value, uintptr(valueSize))
		return false
	}
	hash := m.keyHash(key, m.keySize, m.seed)
	return hashmapGet(m, key, value, valueSize, hash)
}

func hashmapGenericDelete(m *hashmap, key unsafe.Pointer) {
	if m == nil {
		return
	}
	hash := m.keyHash(key, m.keySize, m.seed)
	hashmapDelete(m, key, hash)
}

// hashmapMakeGeneric creates a new hashmap with compiler-provided hash and
// equal functions. This avoids the interface/reflection path for composite
// key types like structs containing strings.
func hashmapMakeGeneric(keySize, valueSize uintptr, sizeHint uintptr,
	keyHash func(key unsafe.Pointer, size, seed uintptr) uint32,
	keyEqual func(x, y unsafe.Pointer, n uintptr) bool) *hashmap {
	bucketBits := uint8(0)
	for hashmapHasSpaceToGrow(bucketBits) && hashmapOverLoadFactor(sizeHint, bucketBits) {
		bucketBits++
	}

	var flags uint8
	keySlotSize := keySize
	if keySize > hashmapMaxKeySize {
		flags |= hashmapFlagIndirectKey
		keySlotSize = unsafe.Sizeof(unsafe.Pointer(nil))
	}
	valueSlotSize := valueSize
	if valueSize > hashmapMaxValueSize {
		flags |= hashmapFlagIndirectValue
		valueSlotSize = unsafe.Sizeof(unsafe.Pointer(nil))
	}

	bucketBufSize := hashmapBucketHeaderSize + keySlotSize*8 + valueSlotSize*8
	buckets := alloc(bucketBufSize*(1<<bucketBits), nil)

	return &hashmap{
		buckets:       buckets,
		seed:          uintptr(fastrand()),
		keySize:       keySize,
		valueSize:     valueSize,
		keySlotSize:   keySlotSize,
		valueSlotSize: valueSlotSize,
		bucketBits:    bucketBits,
		flags:         flags,
		keyEqual:      keyEqual,
		keyHash:       keyHash,
	}
}

// hashmapMakeReflect creates a hashmap for reflect.MakeMapWithSize using
// closures that reconstruct interface{} values from raw key bytes,
// delegating to hashmapInterfaceHash for hashing and == for equality.
func hashmapMakeReflect(keySize, valueSize, sizeHint uintptr, keyType unsafe.Pointer) *hashmap {
	t := (*reflectlite.RawType)(keyType)
	if t.Kind() == reflectlite.Interface {
		// Interface keys are already stored as interface values in the
		// bucket; use the existing interface hash/equal directly.
		return hashmapMakeGeneric(keySize, valueSize, sizeHint,
			hashmapInterfacePtrHash, hashmapInterfaceEqual)
	}
	keyHash := func(key unsafe.Pointer, size, seed uintptr) uint32 {
		return hashmapInterfaceHash(rawToInterface(t, key), seed)
	}
	keyEqual := func(x, y unsafe.Pointer, n uintptr) bool {
		return rawToInterface(t, x) == rawToInterface(t, y)
	}
	return hashmapMakeGeneric(keySize, valueSize, sizeHint, keyHash, keyEqual)
}

// rawToInterface reconstructs an interface{} from raw bytes at ptr.
func rawToInterface(t *reflectlite.RawType, ptr unsafe.Pointer) interface{} {
	var val unsafe.Pointer
	if t.Size() <= unsafe.Sizeof(uintptr(0)) {
		val = reflectliteLoadSmallValue(ptr, t.Size())
	} else {
		val = ptr
	}
	i := composeInterface(unsafe.Pointer(t), val)
	return *(*interface{})(unsafe.Pointer(&i))
}

//go:linkname reflectliteLoadSmallValue internal/reflectlite.loadSmallValue
func reflectliteLoadSmallValue(ptr unsafe.Pointer, size uintptr) unsafe.Pointer

// Hashmap with string keys (a common case).

func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool {
	return *(*string)(x) == *(*string)(y)
}

func hashmapStringHash(s string, seed uintptr) uint32 {
	_s := (*_string)(unsafe.Pointer(&s))
	return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length), seed)
}

func hashmapStringPtrHash(sptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
	_s := *(*_string)(sptr)
	return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length), seed)
}

func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) {
	if m == nil {
		nilMapPanic()
	}
	hash := hashmapStringHash(key, m.seed)
	hashmapSet(m, unsafe.Pointer(&key), value, hash)
}

func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool {
	if m == nil {
		memzero(value, uintptr(valueSize))
		return false
	}
	hash := hashmapStringHash(key, m.seed)
	return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
}

func hashmapStringDelete(m *hashmap, key string) {
	if m == nil {
		return
	}
	hash := hashmapStringHash(key, m.seed)
	hashmapDelete(m, unsafe.Pointer(&key), hash)
}

// Hashmap with interface keys (for everything else).

// This is a method that is intentionally unexported in the reflect package. It
// is identical to the Interface() method call, except it doesn't check whether
// a field is exported and thus allows circumventing the type system.
// The hash function needs it as it also needs to hash unexported struct fields.
//
//go:linkname valueInterfaceUnsafe internal/reflectlite.valueInterfaceUnsafe
func valueInterfaceUnsafe(v reflectlite.Value) interface{}

func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
	f := *(*uint32)(ptr)
	if f == 0x80000000 {
		// convert -0 to 0 for hashing
		f = 0
	}
	return hash32(unsafe.Pointer(&f), 4, seed)
}

func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 {
	f := *(*uint64)(ptr)
	if f == 0x8000000000000000 {
		// convert -0 to 0 for hashing
		f = 0
	}
	return hash32(unsafe.Pointer(&f), 8, seed)
}

func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
	x := reflectlite.ValueOf(itf)
	if x.RawType() == nil {
		return 0 // nil interface
	}

	value := (*_interface)(unsafe.Pointer(&itf)).value
	ptr := value
	if x.RawType().Size() <= unsafe.Sizeof(uintptr(0)) {
		// Value fits in pointer, so it's directly stored in the pointer.
		ptr = unsafe.Pointer(&value)
	}

	switch x.RawType().Kind() {
	case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64:
		return hash32(ptr, x.RawType().Size(), seed)
	case reflectlite.Bool, reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr:
		return hash32(ptr, x.RawType().Size(), seed)
	case reflectlite.Float32:
		// It should be possible to just has the contents. However, NaN != NaN
		// so if you're using lots of NaNs as map keys (you shouldn't) then hash
		// time may become exponential. To fix that, it would be better to
		// return a random number instead:
		// https://research.swtch.com/randhash
		return hashmapFloat32Hash(ptr, seed)
	case reflectlite.Float64:
		return hashmapFloat64Hash(ptr, seed)
	case reflectlite.Complex64:
		rptr, iptr := ptr, unsafe.Add(ptr, 4)
		return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed)
	case reflectlite.Complex128:
		rptr, iptr := ptr, unsafe.Add(ptr, 8)
		return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed)
	case reflectlite.String:
		return hashmapStringHash(x.String(), seed)
	case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer:
		// It might seem better to just return the pointer, but that won't
		// result in an evenly distributed hashmap. Instead, hash the pointer
		// like most other types.
		return hash32(ptr, x.RawType().Size(), seed)
	case reflectlite.Array:
		var hash uint32
		for i := 0; i < x.Len(); i++ {
			hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed)
		}
		return hash
	case reflectlite.Struct:
		var hash uint32
		for i := 0; i < x.NumField(); i++ {
			hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed)
		}
		return hash
	default:
		runtimePanic("comparing un-comparable type")
		return 0 // unreachable
	}
}

func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr, seed uintptr) uint32 {
	_i := *(*interface{})(iptr)
	return hashmapInterfaceHash(_i, seed)
}

func hashmapInterfaceEqual(x, y unsafe.Pointer, n uintptr) bool {
	return *(*interface{})(x) == *(*interface{})(y)
}
