Source file src/internal/runtime/maps/group.go
1 // Copyright 2024 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package maps 6 7 import ( 8 "internal/abi" 9 "internal/goarch" 10 "internal/runtime/sys" 11 "unsafe" 12 ) 13 14 const ( 15 // Maximum load factor prior to growing. 16 // 17 // 7/8 is the same load factor used by Abseil, but Abseil defaults to 18 // 16 slots per group, so they get two empty slots vs our one empty 19 // slot. We may want to reevaluate if this is best for us. 20 maxAvgGroupLoad = 7 21 22 ctrlEmpty ctrl = 0b10000000 23 ctrlDeleted ctrl = 0b11111110 24 25 bitsetLSB = 0x0101010101010101 26 bitsetMSB = 0x8080808080808080 27 bitsetEmpty = bitsetLSB * uint64(ctrlEmpty) 28 bitsetDeleted = bitsetLSB * uint64(ctrlDeleted) 29 ) 30 31 // bitset represents a set of slots within a group. 32 // 33 // The underlying representation uses one byte per slot, where each byte is 34 // either 0x80 if the slot is part of the set or 0x00 otherwise. This makes it 35 // convenient to calculate for an entire group at once (e.g. see matchEmpty). 36 type bitset uint64 37 38 // first assumes that only the MSB of each control byte can be set (e.g. bitset 39 // is the result of matchEmpty or similar) and returns the relative index of the 40 // first control byte in the group that has the MSB set. 41 // 42 // Returns abi.SwissMapGroupSlots if the bitset is empty. 43 func (b bitset) first() uintptr { 44 return uintptr(sys.TrailingZeros64(uint64(b))) >> 3 45 } 46 47 // removeFirst removes the first set bit (that is, resets the least significant 48 // set bit to 0). 49 func (b bitset) removeFirst() bitset { 50 return b & (b - 1) 51 } 52 53 // removeBelow removes all set bits below slot i (non-inclusive). 54 func (b bitset) removeBelow(i uintptr) bitset { 55 // Clear all bits below slot i's byte. 56 mask := (uint64(1) << (8*uint64(i))) - 1 57 return b &^ bitset(mask) 58 } 59 60 // Each slot in the hash table has a control byte which can have one of three 61 // states: empty, deleted, and full. They have the following bit patterns: 62 // 63 // empty: 1 0 0 0 0 0 0 0 64 // deleted: 1 1 1 1 1 1 1 0 65 // full: 0 h h h h h h h // h represents the H1 hash bits 66 // 67 // TODO(prattmic): Consider inverting the top bit so that the zero value is empty. 68 type ctrl uint8 69 70 // ctrlGroup is a fixed size array of abi.SwissMapGroupSlots control bytes 71 // stored in a uint64. 72 type ctrlGroup uint64 73 74 // get returns the i-th control byte. 75 func (g *ctrlGroup) get(i uintptr) ctrl { 76 if goarch.BigEndian { 77 return *(*ctrl)(unsafe.Add(unsafe.Pointer(g), 7-i)) 78 } 79 return *(*ctrl)(unsafe.Add(unsafe.Pointer(g), i)) 80 } 81 82 // set sets the i-th control byte. 83 func (g *ctrlGroup) set(i uintptr, c ctrl) { 84 if goarch.BigEndian { 85 *(*ctrl)(unsafe.Add(unsafe.Pointer(g), 7-i)) = c 86 return 87 } 88 *(*ctrl)(unsafe.Add(unsafe.Pointer(g), i)) = c 89 } 90 91 // setEmpty sets all the control bytes to empty. 92 func (g *ctrlGroup) setEmpty() { 93 *g = ctrlGroup(bitsetEmpty) 94 } 95 96 // matchH2 returns the set of slots which are full and for which the 7-bit hash 97 // matches the given value. May return false positives. 98 func (g ctrlGroup) matchH2(h uintptr) bitset { 99 // NB: This generic matching routine produces false positive matches when 100 // h is 2^N and the control bytes have a seq of 2^N followed by 2^N+1. For 101 // example: if ctrls==0x0302 and h=02, we'll compute v as 0x0100. When we 102 // subtract off 0x0101 the first 2 bytes we'll become 0xffff and both be 103 // considered matches of h. The false positive matches are not a problem, 104 // just a rare inefficiency. Note that they only occur if there is a real 105 // match and never occur on ctrlEmpty, or ctrlDeleted. The subsequent key 106 // comparisons ensure that there is no correctness issue. 107 v := uint64(g) ^ (bitsetLSB * uint64(h)) 108 return bitset(((v - bitsetLSB) &^ v) & bitsetMSB) 109 } 110 111 // matchEmpty returns the set of slots in the group that are empty. 112 func (g ctrlGroup) matchEmpty() bitset { 113 // An empty slot is 1000 0000 114 // A deleted slot is 1111 1110 115 // A full slot is 0??? ???? 116 // 117 // A slot is empty iff bit 7 is set and bit 1 is not. We could select any 118 // of the other bits here (e.g. v << 1 would also work). 119 v := uint64(g) 120 return bitset((v &^ (v << 6)) & bitsetMSB) 121 } 122 123 // matchEmptyOrDeleted returns the set of slots in the group that are empty or 124 // deleted. 125 func (g ctrlGroup) matchEmptyOrDeleted() bitset { 126 // An empty slot is 1000 0000 127 // A deleted slot is 1111 1110 128 // A full slot is 0??? ???? 129 // 130 // A slot is empty or deleted iff bit 7 is set. 131 v := uint64(g) 132 return bitset(v & bitsetMSB) 133 } 134 135 // matchFull returns the set of slots in the group that are full. 136 func (g ctrlGroup) matchFull() bitset { 137 // An empty slot is 1000 0000 138 // A deleted slot is 1111 1110 139 // A full slot is 0??? ???? 140 // 141 // A slot is full iff bit 7 is unset. 142 v := uint64(g) 143 return bitset(^v & bitsetMSB) 144 } 145 146 // groupReference is a wrapper type representing a single slot group stored at 147 // data. 148 // 149 // A group holds abi.SwissMapGroupSlots slots (key/elem pairs) plus their 150 // control word. 151 type groupReference struct { 152 // data points to the group, which is described by typ.Group and has 153 // layout: 154 // 155 // type group struct { 156 // ctrls ctrlGroup 157 // slots [abi.SwissMapGroupSlots]slot 158 // } 159 // 160 // type slot struct { 161 // key typ.Key 162 // elem typ.Elem 163 // } 164 data unsafe.Pointer // data *typ.Group 165 } 166 167 const ( 168 ctrlGroupsSize = unsafe.Sizeof(ctrlGroup(0)) 169 groupSlotsOffset = ctrlGroupsSize 170 ) 171 172 // alignUp rounds n up to a multiple of a. a must be a power of 2. 173 func alignUp(n, a uintptr) uintptr { 174 return (n + a - 1) &^ (a - 1) 175 } 176 177 // alignUpPow2 rounds n up to the next power of 2. 178 // 179 // Returns true if round up causes overflow. 180 func alignUpPow2(n uint64) (uint64, bool) { 181 if n == 0 { 182 return 0, false 183 } 184 v := (uint64(1) << sys.Len64(n-1)) 185 if v == 0 { 186 return 0, true 187 } 188 return v, false 189 } 190 191 // ctrls returns the group control word. 192 func (g *groupReference) ctrls() *ctrlGroup { 193 return (*ctrlGroup)(g.data) 194 } 195 196 // key returns a pointer to the key at index i. 197 func (g *groupReference) key(typ *abi.SwissMapType, i uintptr) unsafe.Pointer { 198 offset := groupSlotsOffset + i*typ.SlotSize 199 200 return unsafe.Pointer(uintptr(g.data) + offset) 201 } 202 203 // elem returns a pointer to the element at index i. 204 func (g *groupReference) elem(typ *abi.SwissMapType, i uintptr) unsafe.Pointer { 205 offset := groupSlotsOffset + i*typ.SlotSize + typ.ElemOff 206 207 return unsafe.Pointer(uintptr(g.data) + offset) 208 } 209 210 // groupsReference is a wrapper type describing an array of groups stored at 211 // data. 212 type groupsReference struct { 213 // data points to an array of groups. See groupReference above for the 214 // definition of group. 215 data unsafe.Pointer // data *[length]typ.Group 216 217 // lengthMask is the number of groups in data minus one (note that 218 // length must be a power of two). This allows computing i%length 219 // quickly using bitwise AND. 220 lengthMask uint64 221 222 // entryMask is the total number of slots in the groups minus one. 223 entryMask uint64 224 } 225 226 // newGroups allocates a new array of length groups. 227 // 228 // Length must be a power of two. 229 func newGroups(typ *abi.SwissMapType, length uint64) groupsReference { 230 return groupsReference{ 231 // TODO: make the length type the same throughout. 232 data: newarray(typ.Group, int(length)), 233 lengthMask: length - 1, 234 entryMask: (length * abi.SwissMapGroupSlots) - 1, 235 } 236 } 237 238 // group returns the group at index i. 239 func (g *groupsReference) group(typ *abi.SwissMapType, i uint64) groupReference { 240 // TODO(prattmic): Do something here about truncation on cast to 241 // uintptr on 32-bit systems? 242 offset := uintptr(i) * typ.Group.Size_ 243 244 return groupReference{ 245 data: unsafe.Pointer(uintptr(g.data) + offset), 246 } 247 } 248