xref: /aosp_15_r20/external/leakcanary2/shark-graph/src/main/java/shark/internal/hppc/LongLongScatterMap.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 LongLongScatterMap constructor(expectedElements: Int = 4) {
27 
interfacenull28   fun interface ForEachCallback {
29     fun onEntry(key: Long, value: Long)
30   }
31 
32   /**
33    * The array holding keys.
34    */
35   private var keys: LongArray = longArrayOf()
36 
37   /**
38    * The array holding values.
39    */
40   private var values: LongArray = longArrayOf()
41 
42   /**
43    * The number of stored keys (assigned key slots), excluding the special
44    * "empty" key, if any (use [.size] instead).
45    *
46    * @see .size
47    */
48   private var assigned: Int = 0
49 
50   /**
51    * Mask for slot scans in [.keys].
52    */
53   private var mask: Int = 0
54 
55   /**
56    * Expand (rehash) [.keys] when [.assigned] hits this value.
57    */
58   private var resizeAt: Int = 0
59 
60   /**
61    * Special treatment for the "empty slot" key marker.
62    */
63   private var hasEmptyKey: Boolean = false
64 
65   /**
66    * The load factor for [.keys].
67    */
68   private var loadFactor: Double = 0.75
69 
70   val isEmpty: Boolean
71     get() = size == 0
72 
73   init {
74     ensureCapacity(expectedElements)
75   }
76 
setnull77   operator fun set(
78     key: Long,
79     value: Long
80   ): Long {
81     val mask = this.mask
82     if (key == 0L) {
83       hasEmptyKey = true
84       val previousValue = values[mask + 1]
85       values[mask + 1] = value
86       return previousValue
87     } else {
88       val keys = this.keys
89       var slot = hashKey(key) and mask
90 
91       var existing = keys[slot]
92       while (existing != 0L) {
93         if (existing == key) {
94           val previousValue = values[slot]
95           values[slot] = value
96           return previousValue
97         }
98         slot = slot + 1 and mask
99         existing = keys[slot]
100       }
101 
102       if (assigned == resizeAt) {
103         allocateThenInsertThenRehash(slot, key, value)
104       } else {
105         keys[slot] = key
106         values[slot] = value
107       }
108 
109       assigned++
110       return 0L
111     }
112   }
113 
removenull114   fun remove(key: Long): Long {
115     val mask = this.mask
116     if (key == 0L) {
117       hasEmptyKey = false
118       val previousValue = values[mask + 1]
119       values[mask + 1] = 0L
120       return previousValue
121     } else {
122       val keys = this.keys
123       var slot = hashKey(key) and mask
124 
125       var existing = keys[slot]
126       while (existing != 0L) {
127         if (existing == key) {
128           val previousValue = values[slot]
129           shiftConflictingKeys(slot)
130           return previousValue
131         }
132         slot = slot + 1 and mask
133         existing = keys[slot]
134       }
135 
136       return 0L
137     }
138   }
139 
140   /**
141    * Being given a key looks it up in the map and returns the slot where element sits, so it later
142    * can be retrieved with [getSlotValue]; return '-1' if element not found.
143    * Why so complicated and not just make [get] return null if value not found? The reason is performance:
144    * this approach prevents unnecessary boxing of the primitive long that would happen with nullable Long?
145    */
getSlotnull146   fun getSlot(key: Long): Int {
147     if (key == 0L) {
148       return if (hasEmptyKey) mask + 1 else -1
149     } else {
150       val keys = this.keys
151       val mask = this.mask
152       var slot = hashKey(key) and mask
153 
154       var existing = keys[slot]
155       while (existing != 0L) {
156         if (existing == key) {
157           return slot
158         }
159         slot = slot + 1 and mask
160         existing = keys[slot]
161       }
162 
163       return -1
164     }
165   }
166 
167   /**
168    * Being given a slot of element retrieves it from the collection
169    */
getSlotValuenull170   fun getSlotValue(slot: Int): Long = values[slot]
171 
172   /**
173    * Returns an element matching a provided [key]; throws [IllegalArgumentException] if element not found
174    */
175   operator fun get(key: Long): Long {
176     val slot = getSlot(key)
177     require(slot != -1) { "Unknown key $key" }
178 
179     return getSlotValue(slot)
180   }
181 
forEachnull182   fun forEach(forEachCallback: ForEachCallback) {
183     val max = mask + 1
184     var slot = -1
185 
186     exitWhile@ while (true) {
187       if (slot < max) {
188         var existing: Long
189         slot++
190         while (slot < max) {
191           existing = keys[slot]
192           if (existing != 0L) {
193             forEachCallback.onEntry(existing, values[slot])
194             continue@exitWhile
195           }
196           slot++
197         }
198       }
199 
200       if (slot == max && hasEmptyKey) {
201         slot++
202         forEachCallback.onEntry(0L, values[max])
203         continue@exitWhile
204       }
205       break@exitWhile
206     }
207   }
208 
entrySequencenull209   fun entrySequence(): Sequence<LongLongPair> {
210     val max = mask + 1
211     var slot = -1
212     return generateSequence {
213       if (slot < max) {
214         var existing: Long
215         slot++
216         while (slot < max) {
217           existing = keys[slot]
218           if (existing != 0L) {
219             return@generateSequence existing to values[slot]
220           }
221           slot++
222         }
223       }
224       if (slot == max && hasEmptyKey) {
225         slot++
226         return@generateSequence 0L to values[max]
227       }
228       return@generateSequence null
229     }
230   }
231 
containsKeynull232   fun containsKey(key: Long): Boolean {
233     if (key == 0L) {
234       return hasEmptyKey
235     } else {
236       val keys = this.keys
237       val mask = this.mask
238       var slot = hashKey(key) and mask
239 
240       var existing = keys[slot]
241       while (existing != 0L) {
242         if (existing == key) {
243           return true
244         }
245         slot = slot + 1 and mask
246         existing = keys[slot]
247       }
248 
249       return false
250     }
251   }
252 
releasenull253   fun release() {
254     assigned = 0
255     hasEmptyKey = false
256 
257     allocateBuffers(HPPC.minBufferSize(4, loadFactor))
258   }
259 
260   val size: Int
261     get() {
262       return assigned + if (hasEmptyKey) 1 else 0
263     }
264 
ensureCapacitynull265   fun ensureCapacity(expectedElements: Int) {
266     if (expectedElements > resizeAt) {
267       val prevKeys = this.keys
268       val prevValues = this.values
269       allocateBuffers(HPPC.minBufferSize(expectedElements, loadFactor))
270       if (!isEmpty) {
271         rehash(prevKeys, prevValues)
272       }
273     }
274   }
275 
hashKeynull276   private fun hashKey(key: Long): Int {
277     return HPPC.mixPhi(key)
278   }
279 
280   /**
281    * Rehash from old buffers to new buffers.
282    */
rehashnull283   private fun rehash(
284     fromKeys: LongArray,
285     fromValues: LongArray
286   ) {
287     // Rehash all stored key/value pairs into the new buffers.
288     val keys = this.keys
289     val values = this.values
290     val mask = this.mask
291     var existing: Long
292 
293     // Copy the zero element's slot, then rehash everything else.
294     var from = fromKeys.size - 1
295     keys[keys.size - 1] = fromKeys[from]
296     values[values.size - 1] = fromValues[from]
297     while (--from >= 0) {
298       existing = fromKeys[from]
299       if (existing != 0L) {
300         var slot = hashKey(existing) and mask
301         while (keys[slot] != 0L) {
302           slot = slot + 1 and mask
303         }
304         keys[slot] = existing
305         values[slot] = fromValues[from]
306       }
307     }
308   }
309 
310   /**
311    * Allocate new internal buffers. This method attempts to allocate
312    * and assign internal buffers atomically (either allocations succeed or not).
313    */
allocateBuffersnull314   private fun allocateBuffers(arraySize: Int) {
315 
316     // Ensure no change is done if we hit an OOM.
317     val prevKeys = this.keys
318     val prevValues = this.values
319     try {
320       val emptyElementSlot = 1
321       this.keys = LongArray(arraySize + emptyElementSlot)
322       this.values = LongArray(arraySize + emptyElementSlot)
323     } catch (e: OutOfMemoryError) {
324       this.keys = prevKeys
325       this.values = prevValues
326       throw RuntimeException(
327         String.format(
328           Locale.ROOT,
329           "Not enough memory to allocate buffers for rehashing: %d -> %d",
330           this.mask + 1,
331           arraySize
332         ), e
333       )
334     }
335 
336     this.resizeAt = HPPC.expandAtCount(arraySize, loadFactor)
337     this.mask = arraySize - 1
338   }
339 
340   /**
341    * This method is invoked when there is a new key/ value pair to be inserted into
342    * the buffers but there is not enough empty slots to do so.
343    *
344    * New buffers are allocated. If this succeeds, we know we can proceed
345    * with rehashing so we assign the pending element to the previous buffer
346    * (possibly violating the invariant of having at least one empty slot)
347    * and rehash all keys, substituting new buffers at the end.
348    */
allocateThenInsertThenRehashnull349   private fun allocateThenInsertThenRehash(
350     slot: Int,
351     pendingKey: Long,
352     pendingValue: Long
353   ) {
354 
355     // Try to allocate new buffers first. If we OOM, we leave in a consistent state.
356     val prevKeys = this.keys
357     val prevValues = this.values
358     allocateBuffers(HPPC.nextBufferSize(mask + 1, size, loadFactor))
359 
360     // We have succeeded at allocating new data so insert the pending key/value at
361     // the free slot in the old arrays before rehashing.
362     prevKeys[slot] = pendingKey
363     prevValues[slot] = pendingValue
364 
365     // Rehash old keys, including the pending key.
366     rehash(prevKeys, prevValues)
367   }
368 
369   /**
370    * Shift all the slot-conflicting keys and values allocated to
371    * (and including) `slot`.
372    */
shiftConflictingKeysnull373   private fun shiftConflictingKeys(gapSlotArg: Int) {
374     var gapSlot = gapSlotArg
375     val keys = this.keys
376     val values = this.values
377     val mask = this.mask
378 
379     // Perform shifts of conflicting keys to fill in the gap.
380     var distance = 0
381     while (true) {
382       val slot = gapSlot + ++distance and mask
383       val existing = keys[slot]
384       if (existing == 0L) {
385         break
386       }
387 
388       val idealSlot = hashKey(existing)
389       val shift = slot - idealSlot and mask
390       if (shift >= distance) {
391         // Entry at this position was originally at or before the gap slot.
392         // Move the conflict-shifted entry to the gap's position and repeat the procedure
393         // for any entries to the right of the current position, treating it
394         // as the new gap.
395         keys[gapSlot] = existing
396         values[gapSlot] = values[slot]
397         gapSlot = slot
398         distance = 0
399       }
400     }
401 
402     // Mark the last found gap slot without a conflict as empty.
403     keys[gapSlot] = 0L
404     values[gapSlot] = 0L
405     assigned--
406   }
407 }
408