| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package maps | |
| import ( | |
| "internal/abi" | |
| "internal/goarch" | |
| "internal/runtime/sys" | |
| "unsafe" | |
| ) | |
| const ( | |
| // Maximum load factor prior to growing. | |
| // | |
| // 7/8 is the same load factor used by Abseil, but Abseil defaults to | |
| // 16 slots per group, so they get two empty slots vs our one empty | |
| // slot. We may want to reevaluate if this is best for us. | |
| maxAvgGroupLoad = 7 | |
| ctrlEmpty ctrl = 0b10000000 | |
| ctrlDeleted ctrl = 0b11111110 | |
| bitsetLSB = 0x0101010101010101 | |
| bitsetMSB = 0x8080808080808080 | |
| bitsetEmpty = bitsetLSB * uint64(ctrlEmpty) | |
| ) | |
| // bitset represents a set of slots within a group. | |
| // | |
| // The underlying representation depends on GOARCH. | |
| // | |
| // On AMD64, bitset uses one bit per slot, where the bit is set if the slot is | |
| // part of the set. All of the ctrlGroup.match* methods are replaced with | |
| // intrinsics that return this packed representation. | |
| // | |
| // On other architectures, bitset uses one byte per slot, where each byte is | |
| // either 0x80 if the slot is part of the set or 0x00 otherwise. This makes it | |
| // convenient to calculate for an entire group at once using standard | |
| // arithmetic instructions. | |
| type bitset uint64 | |
| // first returns the relative index of the first control byte in the group that | |
| // is in the set. | |
| // | |
| // Preconditions: b is not 0 (empty). | |
| func (b bitset) first() uintptr { | |
| return bitsetFirst(b) | |
| } | |
| // Portable implementation of first. | |
| // | |
| // On AMD64, this is replaced with an intrisic that simply does | |
| // TrailingZeros64. There is no need to shift as the bitset is packed. | |
| func bitsetFirst(b bitset) uintptr { | |
| return uintptr(sys.TrailingZeros64(uint64(b))) >> 3 | |
| } | |
| // removeFirst clears the first set bit (that is, resets the least significant | |
| // set bit to 0). | |
| func (b bitset) removeFirst() bitset { | |
| return b & (b - 1) | |
| } | |
| // removeBelow clears all set bits below slot i (non-inclusive). | |
| func (b bitset) removeBelow(i uintptr) bitset { | |
| return bitsetRemoveBelow(b, i) | |
| } | |
| // Portable implementation of removeBelow. | |
| // | |
| // On AMD64, this is replaced with an intrisic that clears the lower i bits. | |
| func bitsetRemoveBelow(b bitset, i uintptr) bitset { | |
| // Clear all bits below slot i's byte. | |
| mask := (uint64(1) << (8 * uint64(i))) - 1 | |
| return b &^ bitset(mask) | |
| } | |
| // lowestSet returns true if the bit is set for the lowest index in the bitset. | |
| // | |
| // This is intended for use with shiftOutLowest to loop over all entries in the | |
| // bitset regardless of whether they are set. | |
| func (b bitset) lowestSet() bool { | |
| return bitsetLowestSet(b) | |
| } | |
| // Portable implementation of lowestSet. | |
| // | |
| // On AMD64, this is replaced with an intrisic that checks the lowest bit. | |
| func bitsetLowestSet(b bitset) bool { | |
| return b&(1<<7) != 0 | |
| } | |
| // shiftOutLowest shifts the lowest entry out of the bitset. Afterwards, the | |
| // lowest entry in the bitset corresponds to the next slot. | |
| func (b bitset) shiftOutLowest() bitset { | |
| return bitsetShiftOutLowest(b) | |
| } | |
| // Portable implementation of shiftOutLowest. | |
| // | |
| // On AMD64, this is replaced with an intrisic that shifts a single bit. | |
| func bitsetShiftOutLowest(b bitset) bitset { | |
| return b >> 8 | |
| } | |
| // count returns the number of bits set in b. | |
| func (b bitset) count() int { | |
| // Note: works for both bitset representations (AMD64 and generic) | |
| return sys.OnesCount64(uint64(b)) | |
| } | |
| // Each slot in the hash table has a control byte which can have one of three | |
| // states: empty, deleted, and full. They have the following bit patterns: | |
| // | |
| // empty: 1 0 0 0 0 0 0 0 | |
| // deleted: 1 1 1 1 1 1 1 0 | |
| // full: 0 h h h h h h h // h represents the H2 hash bits | |
| // | |
| // TODO(prattmic): Consider inverting the top bit so that the zero value is empty. | |
| type ctrl uint8 | |
| // ctrlGroup is a fixed size array of abi.MapGroupSlots control bytes | |
| // stored in a uint64. | |
| type ctrlGroup uint64 | |
| // get returns the i-th control byte. | |
| func (g *ctrlGroup) get(i uintptr) ctrl { | |
| if goarch.BigEndian { | |
| return *(*ctrl)(unsafe.Add(unsafe.Pointer(g), 7-i)) | |
| } | |
| return *(*ctrl)(unsafe.Add(unsafe.Pointer(g), i)) | |
| } | |
| // set sets the i-th control byte. | |
| func (g *ctrlGroup) set(i uintptr, c ctrl) { | |
| if goarch.BigEndian { | |
| *(*ctrl)(unsafe.Add(unsafe.Pointer(g), 7-i)) = c | |
| return | |
| } | |
| *(*ctrl)(unsafe.Add(unsafe.Pointer(g), i)) = c | |
| } | |
| // setEmpty sets all the control bytes to empty. | |
| func (g *ctrlGroup) setEmpty() { | |
| *g = ctrlGroup(bitsetEmpty) | |
| } | |
| // matchH2 returns the set of slots which are full and for which the 7-bit hash | |
| // matches the given value. May return false positives. | |
| func (g ctrlGroup) matchH2(h uintptr) bitset { | |
| return ctrlGroupMatchH2(g, h) | |
| } | |
| // Portable implementation of matchH2. | |
| // | |
| // Note: On AMD64, this is an intrinsic implemented with SIMD instructions. See | |
| // note on bitset about the packed intrinsified return value. | |
| func ctrlGroupMatchH2(g ctrlGroup, h uintptr) bitset { | |
| // NB: This generic matching routine produces false positive matches when | |
| // h is 2^N and the control bytes have a seq of 2^N followed by 2^N+1. For | |
| // example: if ctrls==0x0302 and h=02, we'll compute v as 0x0100. When we | |
| // subtract off 0x0101 the first 2 bytes we'll become 0xffff and both be | |
| // considered matches of h. The false positive matches are not a problem, | |
| // just a rare inefficiency. Note that they only occur if there is a real | |
| // match and never occur on ctrlEmpty, or ctrlDeleted. The subsequent key | |
| // comparisons ensure that there is no correctness issue. | |
| v := uint64(g) ^ (bitsetLSB * uint64(h)) | |
| return bitset(((v - bitsetLSB) &^ v) & bitsetMSB) | |
| } | |
| // matchEmpty returns the set of slots in the group that are empty. | |
| func (g ctrlGroup) matchEmpty() bitset { | |
| return ctrlGroupMatchEmpty(g) | |
| } | |
| // Portable implementation of matchEmpty. | |
| // | |
| // Note: On AMD64, this is an intrinsic implemented with SIMD instructions. See | |
| // note on bitset about the packed intrinsified return value. | |
| func ctrlGroupMatchEmpty(g ctrlGroup) bitset { | |
| // An empty slot is 1000 0000 | |
| // A deleted slot is 1111 1110 | |
| // A full slot is 0??? ???? | |
| // | |
| // A slot is empty iff bit 7 is set and bit 1 is not. We could select any | |
| // of the other bits here (e.g. v << 1 would also work). | |
| v := uint64(g) | |
| return bitset((v &^ (v << 6)) & bitsetMSB) | |
| } | |
| // matchEmptyOrDeleted returns the set of slots in the group that are empty or | |
| // deleted. | |
| func (g ctrlGroup) matchEmptyOrDeleted() bitset { | |
| return ctrlGroupMatchEmptyOrDeleted(g) | |
| } | |
| // Portable implementation of matchEmptyOrDeleted. | |
| // | |
| // Note: On AMD64, this is an intrinsic implemented with SIMD instructions. See | |
| // note on bitset about the packed intrinsified return value. | |
| func ctrlGroupMatchEmptyOrDeleted(g ctrlGroup) bitset { | |
| // An empty slot is 1000 0000 | |
| // A deleted slot is 1111 1110 | |
| // A full slot is 0??? ???? | |
| // | |
| // A slot is empty or deleted iff bit 7 is set. | |
| v := uint64(g) | |
| return bitset(v & bitsetMSB) | |
| } | |
| // matchFull returns the set of slots in the group that are full. | |
| func (g ctrlGroup) matchFull() bitset { | |
| return ctrlGroupMatchFull(g) | |
| } | |
| // anyFull reports whether any slots in the group are full. | |
| func (g ctrlGroup) anyFull() bool { | |
| // A slot is full iff bit 7 is unset. Test whether any slot has bit 7 unset. | |
| return (^g)&bitsetMSB != 0 | |
| } | |
| // Portable implementation of matchFull. | |
| // | |
| // Note: On AMD64, this is an intrinsic implemented with SIMD instructions. See | |
| // note on bitset about the packed intrinsified return value. | |
| func ctrlGroupMatchFull(g ctrlGroup) bitset { | |
| // An empty slot is 1000 0000 | |
| // A deleted slot is 1111 1110 | |
| // A full slot is 0??? ???? | |
| // | |
| // A slot is full iff bit 7 is unset. | |
| v := uint64(g) | |
| return bitset(^v & bitsetMSB) | |
| } | |
| // groupReference is a wrapper type representing a single slot group stored at | |
| // data. | |
| // | |
| // A group holds abi.MapGroupSlots slots (key/elem pairs) plus their | |
| // control word. | |
| type groupReference struct { | |
| // data points to the group, which is described by typ.Group and has | |
| // layout: | |
| // | |
| // type group struct { | |
| // ctrls ctrlGroup | |
| // slots [abi.MapGroupSlots]slot | |
| // } | |
| // | |
| // type slot struct { | |
| // key typ.Key | |
| // elem typ.Elem | |
| // } | |
| data unsafe.Pointer // data *typ.Group | |
| } | |
| const ( | |
| ctrlGroupsSize = unsafe.Sizeof(ctrlGroup(0)) | |
| groupSlotsOffset = ctrlGroupsSize | |
| ) | |
| // alignUp rounds n up to a multiple of a. a must be a power of 2. | |
| func alignUp(n, a uintptr) uintptr { | |
| return (n + a - 1) &^ (a - 1) | |
| } | |
| // alignUpPow2 rounds n up to the next power of 2. | |
| // | |
| // Returns true if round up causes overflow. | |
| func alignUpPow2(n uint64) (uint64, bool) { | |
| if n == 0 { | |
| return 0, false | |
| } | |
| v := (uint64(1) << sys.Len64(n-1)) | |
| if v == 0 { | |
| return 0, true | |
| } | |
| return v, false | |
| } | |
| // ctrls returns the group control word. | |
| func (g *groupReference) ctrls() *ctrlGroup { | |
| return (*ctrlGroup)(g.data) | |
| } | |
| // key returns a pointer to the key at index i. | |
| func (g *groupReference) key(typ *abi.MapType, i uintptr) unsafe.Pointer { | |
| offset := groupSlotsOffset + i*typ.SlotSize | |
| return unsafe.Pointer(uintptr(g.data) + offset) | |
| } | |
| // elem returns a pointer to the element at index i. | |
| func (g *groupReference) elem(typ *abi.MapType, i uintptr) unsafe.Pointer { | |
| offset := groupSlotsOffset + i*typ.SlotSize + typ.ElemOff | |
| return unsafe.Pointer(uintptr(g.data) + offset) | |
| } | |
| // groupsReference is a wrapper type describing an array of groups stored at | |
| // data. | |
| type groupsReference struct { | |
| // data points to an array of groups. See groupReference above for the | |
| // definition of group. | |
| data unsafe.Pointer // data *[length]typ.Group | |
| // lengthMask is the number of groups in data minus one (note that | |
| // length must be a power of two). This allows computing i%length | |
| // quickly using bitwise AND. | |
| lengthMask uint64 | |
| } | |
| // newGroups allocates a new array of length groups. | |
| // | |
| // Length must be a power of two. | |
| func newGroups(typ *abi.MapType, length uint64) groupsReference { | |
| return groupsReference{ | |
| // TODO: make the length type the same throughout. | |
| data: newarray(typ.Group, int(length)), | |
| lengthMask: length - 1, | |
| } | |
| } | |
| // group returns the group at index i. | |
| func (g *groupsReference) group(typ *abi.MapType, i uint64) groupReference { | |
| // TODO(prattmic): Do something here about truncation on cast to | |
| // uintptr on 32-bit systems? | |
| offset := uintptr(i) * typ.GroupSize | |
| return groupReference{ | |
| data: unsafe.Pointer(uintptr(g.data) + offset), | |
| } | |
| } | |
| func cloneGroup(typ *abi.MapType, newGroup, oldGroup groupReference) { | |
| typedmemmove(typ.Group, newGroup.data, oldGroup.data) | |
| if typ.IndirectKey() { | |
| // Deep copy keys if indirect. | |
| for i := uintptr(0); i < abi.MapGroupSlots; i++ { | |
| oldKey := *(*unsafe.Pointer)(oldGroup.key(typ, i)) | |
| if oldKey == nil { | |
| continue | |
| } | |
| newKey := newobject(typ.Key) | |
| typedmemmove(typ.Key, newKey, oldKey) | |
| *(*unsafe.Pointer)(newGroup.key(typ, i)) = newKey | |
| } | |
| } | |
| if typ.IndirectElem() { | |
| // Deep copy elems if indirect. | |
| for i := uintptr(0); i < abi.MapGroupSlots; i++ { | |
| oldElem := *(*unsafe.Pointer)(oldGroup.elem(typ, i)) | |
| if oldElem == nil { | |
| continue | |
| } | |
| newElem := newobject(typ.Elem) | |
| typedmemmove(typ.Elem, newElem, oldElem) | |
| *(*unsafe.Pointer)(newGroup.elem(typ, i)) = newElem | |
| } | |
| } | |
| } | |