xref: /aosp_15_r20/external/leakcanary2/shark-graph/src/main/java/shark/internal/hppc/LongObjectScatterMap.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1 /*
2  *  Copyright 2010-2013, Carrot Search s.c., Boznicza 11/56, Poznan, Poland
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */
16 
17 package shark.internal.hppc
18 
19 import java.util.Locale
20 
21 /**
22  * Code from com.carrotsearch.hppc.LongLongScatterMap copy pasted, inlined and converted to Kotlin.
23  *
24  * See https://github.com/carrotsearch/hppc .
25  */
26 internal class LongObjectScatterMap<T> {
27   /**
28    * The array holding keys.
29    */
30   private var keys: LongArray = longArrayOf()
31 
32   /**
33    * The array holding values.
34    */
35   @Suppress("UNCHECKED_CAST")
36   private var values: Array<T?> = emptyArray<Any?>() as Array<T?>
37 
38   /**
39    * The number of stored keys (assigned key slots), excluding the special
40    * "empty" key, if any (use [.size] instead).
41    *
42    * @see .size
43    */
44   private var assigned: Int = 0
45 
46   /**
47    * Mask for slot scans in [.keys].
48    */
49   private var mask: Int = 0
50 
51   /**
52    * Expand (rehash) [.keys] when [.assigned] hits this value.
53    */
54   private var resizeAt: Int = 0
55 
56   /**
57    * Special treatment for the "empty slot" key marker.
58    */
59   private var hasEmptyKey: Boolean = false
60 
61   /**
62    * The load factor for [.keys].
63    */
64   private var loadFactor: Double = 0.75
65 
66   val isEmpty: Boolean
67     get() = size == 0
68 
69   init {
70     ensureCapacity(4)
71   }
72 
setnull73   operator fun set(
74     key: Long,
75     value: T
76   ): T? {
77     val mask = this.mask
78     if (key == 0L) {
79       hasEmptyKey = true
80       val previousValue = values[mask + 1]
81       values[mask + 1] = value
82       return previousValue
83     } else {
84       val keys = this.keys
85       var slot = hashKey(key) and mask
86 
87       var existing = keys[slot]
88       while (existing != 0L) {
89         if (existing == key) {
90           val previousValue = values[slot]
91           values[slot] = value
92           return previousValue
93         }
94         slot = slot + 1 and mask
95         existing = keys[slot]
96       }
97 
98       if (assigned == resizeAt) {
99         allocateThenInsertThenRehash(slot, key, value)
100       } else {
101         keys[slot] = key
102         values[slot] = value
103       }
104 
105       assigned++
106       return null
107     }
108   }
109 
removenull110   fun remove(key: Long): T? {
111     val mask = this.mask
112     if (key == 0L) {
113       hasEmptyKey = false
114       val previousValue = values[mask + 1]
115       values[mask + 1] = null
116       return previousValue
117     } else {
118       val keys = this.keys
119       var slot = hashKey(key) and mask
120 
121       var existing = keys[slot]
122       while (existing != 0L) {
123         if (existing == key) {
124           val previousValue = values[slot]
125           shiftConflictingKeys(slot)
126           return previousValue
127         }
128         slot = slot + 1 and mask
129         existing = keys[slot]
130       }
131 
132       return null
133     }
134   }
135 
getnull136   operator fun get(key: Long): T? {
137     if (key == 0L) {
138       return if (hasEmptyKey) values[mask + 1] else null
139     } else {
140       val keys = this.keys
141       val mask = this.mask
142       var slot = hashKey(key) and mask
143 
144       var existing = keys[slot]
145       while (existing != 0L) {
146         if (existing == key) {
147           return values[slot]
148         }
149         slot = slot + 1 and mask
150         existing = keys[slot]
151       }
152 
153       return null
154     }
155   }
156 
entrySequencenull157   fun entrySequence(): Sequence<LongObjectPair<T>> {
158     val max = mask + 1
159     var slot = -1
160     return generateSequence {
161       if (slot < max) {
162         var existing: Long
163         slot++
164         while (slot < max) {
165           existing = keys[slot]
166           if (existing != 0L) {
167             return@generateSequence existing to values[slot]!!
168           }
169           slot++
170         }
171       }
172       if (slot == max && hasEmptyKey) {
173         slot++
174         return@generateSequence 0L to values[max]!!
175       }
176       return@generateSequence null
177     }
178   }
179 
containsKeynull180   fun containsKey(key: Long): Boolean {
181     if (key == 0L) {
182       return hasEmptyKey
183     } else {
184       val keys = this.keys
185       val mask = this.mask
186       var slot = hashKey(key) and mask
187 
188       var existing = keys[slot]
189       while (existing != 0L) {
190         if (existing == key) {
191           return true
192         }
193         slot = slot + 1 and mask
194         existing = keys[slot]
195       }
196 
197       return false
198     }
199   }
200 
releasenull201   fun release() {
202     assigned = 0
203     hasEmptyKey = false
204 
205     allocateBuffers(HPPC.minBufferSize(4, loadFactor))
206   }
207 
208   val size: Int
209     get() {
210       return assigned + if (hasEmptyKey) 1 else 0
211     }
212 
ensureCapacitynull213   fun ensureCapacity(expectedElements: Int) {
214     if (expectedElements > resizeAt) {
215       val prevKeys = this.keys
216       val prevValues = this.values
217       allocateBuffers(HPPC.minBufferSize(expectedElements, loadFactor))
218       if (!isEmpty) {
219         rehash(prevKeys, prevValues)
220       }
221     }
222   }
223 
hashKeynull224   private fun hashKey(key: Long): Int {
225     return HPPC.mixPhi(key)
226   }
227 
228   /**
229    * Rehash from old buffers to new buffers.
230    */
rehashnull231   private fun rehash(
232     fromKeys: LongArray,
233     fromValues: Array<T?>
234   ) {
235     // Rehash all stored key/value pairs into the new buffers.
236     val keys = this.keys
237     val values = this.values
238     val mask = this.mask
239     var existing: Long
240 
241     // Copy the zero element's slot, then rehash everything else.
242     var from = fromKeys.size - 1
243     keys[keys.size - 1] = fromKeys[from]
244     values[values.size - 1] = fromValues[from]
245     while (--from >= 0) {
246       existing = fromKeys[from]
247       if (existing != 0L) {
248         var slot = hashKey(existing) and mask
249         while (keys[slot] != 0L) {
250           slot = slot + 1 and mask
251         }
252         keys[slot] = existing
253         values[slot] = fromValues[from]
254       }
255     }
256   }
257 
258   /**
259    * Allocate new internal buffers. This method attempts to allocate
260    * and assign internal buffers atomically (either allocations succeed or not).
261    */
allocateBuffersnull262   private fun allocateBuffers(arraySize: Int) {
263 
264     // Ensure no change is done if we hit an OOM.
265     val prevKeys = this.keys
266     val prevValues = this.values
267     try {
268       val emptyElementSlot = 1
269       this.keys = LongArray(arraySize + emptyElementSlot)
270       @Suppress("UNCHECKED_CAST")
271       this.values = arrayOfNulls<Any?>(arraySize + emptyElementSlot) as Array<T?>
272     } catch (e: OutOfMemoryError) {
273       this.keys = prevKeys
274       this.values = prevValues
275       throw RuntimeException(
276         String.format(
277           Locale.ROOT,
278           "Not enough memory to allocate buffers for rehashing: %d -> %d",
279           this.mask + 1,
280           arraySize
281         ), e
282       )
283     }
284 
285     this.resizeAt = HPPC.expandAtCount(arraySize, loadFactor)
286     this.mask = arraySize - 1
287   }
288 
289   /**
290    * This method is invoked when there is a new key/ value pair to be inserted into
291    * the buffers but there is not enough empty slots to do so.
292    *
293    * New buffers are allocated. If this succeeds, we know we can proceed
294    * with rehashing so we assign the pending element to the previous buffer
295    * (possibly violating the invariant of having at least one empty slot)
296    * and rehash all keys, substituting new buffers at the end.
297    */
allocateThenInsertThenRehashnull298   private fun allocateThenInsertThenRehash(
299     slot: Int,
300     pendingKey: Long,
301     pendingValue: T
302   ) {
303 
304     // Try to allocate new buffers first. If we OOM, we leave in a consistent state.
305     val prevKeys = this.keys
306     val prevValues = this.values
307     allocateBuffers(HPPC.nextBufferSize(mask + 1, size, loadFactor))
308 
309     // We have succeeded at allocating new data so insert the pending key/value at
310     // the free slot in the old arrays before rehashing.
311     prevKeys[slot] = pendingKey
312     prevValues[slot] = pendingValue
313 
314     // Rehash old keys, including the pending key.
315     rehash(prevKeys, prevValues)
316   }
317 
318   /**
319    * Shift all the slot-conflicting keys and values allocated to
320    * (and including) `slot`.
321    */
shiftConflictingKeysnull322   private fun shiftConflictingKeys(gapSlotArg: Int) {
323     var gapSlot = gapSlotArg
324     val keys = this.keys
325     val values = this.values
326     val mask = this.mask
327 
328     // Perform shifts of conflicting keys to fill in the gap.
329     var distance = 0
330     while (true) {
331       val slot = gapSlot + ++distance and mask
332       val existing = keys[slot]
333       if (existing == 0L) {
334         break
335       }
336 
337       val idealSlot = hashKey(existing)
338       val shift = slot - idealSlot and mask
339       if (shift >= distance) {
340         // Entry at this position was originally at or before the gap slot.
341         // Move the conflict-shifted entry to the gap's position and repeat the procedure
342         // for any entries to the right of the current position, treating it
343         // as the new gap.
344         keys[gapSlot] = existing
345         values[gapSlot] = values[slot]
346         gapSlot = slot
347         distance = 0
348       }
349     }
350 
351     // Mark the last found gap slot without a conflict as empty.
352     keys[gapSlot] = 0L
353     values[gapSlot] = null
354     assigned--
355   }
356 }
357