Skip to main content
summaryrefslogtreecommitdiffstats
blob: 7279420b330138a3b8fcf6f94efa57cbcef876b2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
/*******************************************************************************
 * Copyright (c) 2015, 2016 Google, Inc and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Stefan Xenos (Google) - Initial implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.core.nd;

import org.eclipse.jdt.internal.core.nd.db.Database;
import org.eclipse.jdt.internal.core.nd.db.IndexException;
import org.eclipse.jdt.internal.core.nd.field.FieldInt;
import org.eclipse.jdt.internal.core.nd.field.FieldPointer;
import org.eclipse.jdt.internal.core.nd.field.FieldShort;
import org.eclipse.jdt.internal.core.nd.field.StructDef;
import org.eclipse.jdt.internal.core.nd.util.MathUtils;

/**
 * Implements a growable array of pointers that supports constant-time insertions and removals. Items are inserted at
 * the end of the array, and each insertion hands back a unique identifier that can be used to remove the item quickly
 * at a later time.
 * <p>
 * The memory format contains a header is as follows:
 * <p>
 * 
 * <pre>
 * Byte				Meaning
 * --------------------------------------------
 * 0..3				Pointer to the growable block. Null if the number of records <= inlineRecordCount
 * 4..7				Record [0]
 * 8..11			Record [1]
 * ...
 * k...k+4			Record [inlineRecordCount-1]
 * </pre>
 * 
 * As shown above, the first few records are stored inline with the array. inlineRecordCount is a tunable parameter
 * which may be 0. If there are fewer than inlineRecordCount records, there is no growable block and all records are
 * stored in the header. Storing the first few records in the header is intended as an optimization for very small
 * arrays in the case where small arrays are expected to be a common case. If there are fewer than inlineRecordCount
 * records stored in the array, the size of the array is not stored explicitly. It is computed on demand by searching
 * for the first null entry among the inline records.
 *
 * <p>
 * The memory format for a growable block is as follows:
 * <p>
 * 
 * <pre>
 * Byte				Meaning
 * --------------------------------------------
 * 0..3				Size of the array, including all inline records. This is also the index at which the next entry will
 *					be inserted
 * 4..7				Capacity of this growable block.
 * 8..11			Record [n]
 * 12..15			Record [n+1]
 * ...
 * k...k+4			Record [blockSize-1]
 * </pre>
 * 
 * <p>
 * The growable block itself begins with a 4-byte int holding the size of the array, followed by a 4-byte int holding
 * the capacity of the growable block. In the event that the array is larger than
 * {@link GrowableBlockHeader#MAX_GROWABLE_SIZE} enough to be using a metablock, there will be multiple growable blocks
 * in use. In this case, the size and capacity stored in the metablock is used for the array and the size and capacity
 * stored in each growable block will be filled in with 0s.
 * <p>
 * If capacity <= MAX_BLOCK_SIZE then this is a normal block containing a flat array of record pointers starting from
 * the element numbered inlineRecordCount. If capacity > MAX_BLOCK_SIZE then then it is a metablock which holds record
 * pointers to separate growable blocks, each of which holds exactly MAX_BLOCK_SIZE elements.
 * <p>
 * Every time an element is inserted in the array, the add method returns the element's index. Indices can be used to
 * remove elements in constant time, but will be reassigned by the RawGrowableArray during removes by swapping the
 * removed element with the last element in the array. If the owner of the array is keeping track of indices, it should
 * update the relevant indices on remove.
 * <p>
 * The array itself is tightly packed. When an element is removed, the last element in the array is swapped into its
 * location. Anyone keeping track of indices may rely on the fact that they are consecutive integers.
 * <p>
 * These arrays preserve insertion order until the first call to "remove". If element order matters, you should not
 * remove individual elements but should instead destroy and rebuild the entire array.
 * <p>
 * Element additions and removals run in constant amortized time.
 * <p>
 * There are a lot of ints and longs used in the implementation of this class. In order to help clarify their function,
 * they get the following suffixes:
 * <ul>
 * <li>index - holds an index into the array
 * <li>size - holds a count of the number of indices
 * <li>value - holds one of the pointer values inserted into the array
 * <li>address - holds a pointer into the database (refers to a full 8-byte long, not the compressed 4-byte version)
 * <li>bytes - holds the size (in bytes) of something in the database
 * <li>block - holds a block number (in the case where a metablock is in use, the growable blocks are identified by
 * block numbers).
 * <li>blockCount - holds a number of blocks
 * </ul>
 */
public final class RawGrowableArray {
	private static final FieldPointer GROWABLE_BLOCK_ADDRESS;
	private static final int ARRAY_HEADER_BYTES;

	private static final StructDef<RawGrowableArray> type; 

	static {
		type = StructDef.createAbstract(RawGrowableArray.class);
		GROWABLE_BLOCK_ADDRESS = type.addPointer();
		type.done();

		ARRAY_HEADER_BYTES = type.size();
	}

	private static class GrowableBlockHeader {
		public static final FieldInt ARRAY_SIZE;
		public static final FieldInt ALLOCATED_SIZE;
		public static final int GROWABLE_BLOCK_HEADER_BYTES;
		public static final int MAX_GROWABLE_SIZE;

		@SuppressWarnings("hiding")
		private static final StructDef<GrowableBlockHeader> type;

		static {
			type = StructDef.createAbstract(GrowableBlockHeader.class);

			ARRAY_SIZE = type.addInt();
			ALLOCATED_SIZE = type.addInt();
			type.done();

			GROWABLE_BLOCK_HEADER_BYTES = type.size();

			MAX_GROWABLE_SIZE = (Database.MAX_SINGLE_BLOCK_MALLOC_SIZE - GROWABLE_BLOCK_HEADER_BYTES)
					/ Database.PTR_SIZE;
		}
	}

	@SuppressWarnings("synthetic-access")
	private static final class MetaBlockHeader extends GrowableBlockHeader {
		/**
		 * Holds the number of pages used for the metablock. Note that the start of the metablock array needs to be
		 * 4-byte aligned. Since all malloc calls are always 2 bytes away from 4-byte alignment, we need to use at
		 * least one short in this struct. */ 
		public static final FieldShort METABLOCK_NUM_PAGES;
		public static final int META_BLOCK_HEADER_BYTES;

		@SuppressWarnings("hiding")
		private static final StructDef<MetaBlockHeader> type;

		static {
			type = StructDef.createAbstract(MetaBlockHeader.class, GrowableBlockHeader.type);

			METABLOCK_NUM_PAGES = type.addShort();
			type.done();

			META_BLOCK_HEADER_BYTES = type.size();
		}
	}
	
	private final int inlineSize;

	public RawGrowableArray(int inlineRecords) {
		this.inlineSize = inlineRecords;
	}

	public static int getMaxGrowableBlockSize() {
		return GrowableBlockHeader.MAX_GROWABLE_SIZE;
	}

	/**
	 * Returns the size of the array.
	 * 
	 * @param address address of the array
	 * @return the array size, in number elements
	 */
	public int size(Nd nd, long address) {
		Database db = nd.getDB();
		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);

		if (growableBlockAddress == 0) {
			// If there is no growable block or metablock, then the size is determined by the position of the first
			// null pointer among the inline records.
			long inlineRecordStartAddress = address + ARRAY_HEADER_BYTES;
			for (int index = 0; index < this.inlineSize; index++) {
				long nextAddress = inlineRecordStartAddress + index * Database.PTR_SIZE;

				long nextValue = db.getRecPtr(nextAddress);
				if (nextValue == 0) {
					return index;
				}
			}
			return this.inlineSize;
		}
		return GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress);
	}

	/**
	 * Adds the given value to the array. Returns an index which can be later passed into remove in order to
	 * remove the element at a later time.
	 */
	public int add(Nd nd, long address, long value) {
		if (value == 0) {
			throw new IllegalArgumentException("Null pointers cannot be inserted into " + getClass().getName()); //$NON-NLS-1$
		}
		Database db = nd.getDB();

		int insertionIndex = size(nd, address);
		int newSize = insertionIndex + 1;

		try {
			ensureCapacity(nd, address, newSize);
			long recordAddress = getAddressOfRecord(nd, address, insertionIndex);
			db.putRecPtr(recordAddress, value);
			setSize(nd, address, newSize);
		} catch (IndexException e) {
			IndexExceptionBuilder descriptor = nd.describeProblem();
			addSizeTo(nd, address, descriptor);
			descriptor.attachTo(e);
			throw e;
		}
		return insertionIndex;
	}

	/**
	 * Returns the element at the given index (nonzero). The given index must be < size().
	 */
	public long get(Nd nd, long address, int index) {
		long recordAddress = getAddressOfRecord(nd, address, index);
		return nd.getDB().getRecPtr(recordAddress);
	}

	/**
	 * Ensures that the array contains at least enough space allocated to fit the given number of new elements.
	 */
	public void ensureCapacity(Nd nd, long address, int desiredSize) {
		int growableBlockNeededSize = desiredSize - this.inlineSize;
		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
		int growableBlockCurrentSize = growableBlockAddress == 0 ? 0
				: GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);

		// The growable region is already large enough.
		if (growableBlockNeededSize <= growableBlockCurrentSize) {
			return;
		}

		Database db = nd.getDB();

		int neededBlockSize = getGrowableRegionSizeFor(desiredSize); 
		if (neededBlockSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
			// We need a metablock.
			long metablockAddress = growableBlockAddress;

			// neededBlockSize should always be a multiple of the max block size when metablocks are in use
			assert neededBlockSize % GrowableBlockHeader.MAX_GROWABLE_SIZE == 0;
			// Create extra growable blocks if necessary.
			int requiredBlockCount = divideRoundingUp(neededBlockSize, GrowableBlockHeader.MAX_GROWABLE_SIZE);

			int neededMetablockPages = computeMetablockPagesForBlocks(requiredBlockCount);

			if (neededMetablockPages > Short.MAX_VALUE) {
				throw new IndexException("A metablock overflowed. Unable to allocate " + neededMetablockPages //$NON-NLS-1$
						+ " pages."); //$NON-NLS-1$
			}
			if (!(growableBlockCurrentSize > GrowableBlockHeader.MAX_GROWABLE_SIZE)) {
				// We weren't using a metablock previously
				int currentSize = size(nd, address);
				// Need to convert to using metablocks.
				long firstGrowableBlockAddress = resizeBlock(nd, address, GrowableBlockHeader.MAX_GROWABLE_SIZE);

				metablockAddress = db.malloc(Database.getBytesThatFitInChunks(neededMetablockPages),
						Database.POOL_GROWABLE_ARRAY);
				GrowableBlockHeader.ARRAY_SIZE.put(nd, metablockAddress, currentSize);
				GrowableBlockHeader.ALLOCATED_SIZE.put(nd, metablockAddress,
						GrowableBlockHeader.MAX_GROWABLE_SIZE);
				MetaBlockHeader.METABLOCK_NUM_PAGES.put(nd, metablockAddress, (short)neededMetablockPages);

				// Link the first block into the metablock.
				db.putRecPtr(metablockAddress + MetaBlockHeader.META_BLOCK_HEADER_BYTES,
						firstGrowableBlockAddress);
				GROWABLE_BLOCK_ADDRESS.put(nd, address, metablockAddress);
			}

			short metablockCurrentPages = MetaBlockHeader.METABLOCK_NUM_PAGES.get(nd, metablockAddress);
			if (metablockCurrentPages < neededMetablockPages) {
				short newMetablockPages = (short)Math.min(Short.MAX_VALUE, neededMetablockPages * 1.5);
				long newMetablockAddress = db.malloc(Database.getBytesThatFitInChunks(newMetablockPages),
						Database.POOL_GROWABLE_ARRAY);
				int oldNumPages = MetaBlockHeader.METABLOCK_NUM_PAGES.get(nd, metablockAddress);
				db.memcpy(newMetablockAddress, metablockAddress, (int)Database.getBytesThatFitInChunks(oldNumPages));
				db.free(metablockAddress, Database.POOL_GROWABLE_ARRAY);
				metablockAddress = newMetablockAddress;
				MetaBlockHeader.METABLOCK_NUM_PAGES.put(nd, metablockAddress, newMetablockPages);
				GROWABLE_BLOCK_ADDRESS.put(nd, address, metablockAddress);
			}
			int currentAllocatedSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, metablockAddress);
			assert currentAllocatedSize % GrowableBlockHeader.MAX_GROWABLE_SIZE == 0;
			int currentBlockCount = currentAllocatedSize / GrowableBlockHeader.MAX_GROWABLE_SIZE;

			for (int nextBlock = currentBlockCount; nextBlock < requiredBlockCount; nextBlock++) {
				long nextBlockAddress = db.malloc(computeBlockBytes(GrowableBlockHeader.MAX_GROWABLE_SIZE),
						Database.POOL_GROWABLE_ARRAY);

				db.putRecPtr(metablockAddress + MetaBlockHeader.META_BLOCK_HEADER_BYTES
						+ nextBlock * Database.PTR_SIZE, nextBlockAddress);
			}

			GrowableBlockHeader.ALLOCATED_SIZE.put(nd, metablockAddress, neededBlockSize);
		} else {
			long newBlockAddress = resizeBlock(nd, address, neededBlockSize);

			GROWABLE_BLOCK_ADDRESS.put(nd, address, newBlockAddress);
		}
	}

	private static int divideRoundingUp(int neededBlockSize, int maxGrowableSize) {
		return (neededBlockSize + maxGrowableSize - 1) / maxGrowableSize;
	}

	private int computeMetablockPagesForBlocks(int requiredBlockCount) {
		return Database.getChunksNeededForBytes(
				requiredBlockCount * Database.PTR_SIZE + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES);
	}

	/**
	 * Allocates a new normal block, copies the contents of the old block to it, and deletes the old block. Should not
	 * be used if the array is using metablocks. Returns the address of the newly-allocated block.
	 */
	private long resizeBlock(Nd nd, long address, int newBlockSize) {
		Database db = nd.getDB();
		long oldGrowableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);

		// Check if the existing block is already exactly the right size
		if (oldGrowableBlockAddress != 0) {
			if (newBlockSize == 0) {
				db.free(oldGrowableBlockAddress, Database.POOL_GROWABLE_ARRAY);
				return 0;
			}

			int oldAllocatedSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, oldGrowableBlockAddress);
			if (oldAllocatedSize == newBlockSize) {
				return oldGrowableBlockAddress;
			}
		}

		int arraySize = size(nd, address);
		int numToCopySize = Math.min(Math.max(0, arraySize - this.inlineSize), newBlockSize);
		long newGrowableBlockAddress = db.malloc(computeBlockBytes(newBlockSize), Database.POOL_GROWABLE_ARRAY);

		if (oldGrowableBlockAddress != 0) {
			db.memcpy(newGrowableBlockAddress, oldGrowableBlockAddress, computeBlockBytes(numToCopySize));
			db.free(oldGrowableBlockAddress, Database.POOL_GROWABLE_ARRAY);
		}

		GrowableBlockHeader.ARRAY_SIZE.put(nd, newGrowableBlockAddress, arraySize);
		GrowableBlockHeader.ALLOCATED_SIZE.put(nd, newGrowableBlockAddress, newBlockSize);
		return newGrowableBlockAddress;
	}

	private int computeBlockBytes(int size) {
		return size * Database.PTR_SIZE + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
	}

	/**
	 * @param size
	 */
	private void setSize(Nd nd, long address, int size) {
		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);

		// If we're not using a growable block, we don't explicitly store the size
		if (growableBlockAddress == 0) {
			return;
		}

		GrowableBlockHeader.ARRAY_SIZE.put(nd, growableBlockAddress, size);
	}

	/**
	 * Returns a record address given a record number
	 */
	private long getAddressOfRecord(Nd nd, long address, int index) {
		int growableBlockRelativeIndex = index - this.inlineSize;

		if (growableBlockRelativeIndex >= 0) {
			Database db = nd.getDB();
			// This record is located within the growable region
			long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
			int size = size(nd, address);

			// We use reads of 1 past the end of the array to handle insertions.
			if (index > size) {
				IndexExceptionBuilder builder = nd.describeProblem();

				addSizeTo(nd, address, builder);

				builder.addProblemAddress(GROWABLE_BLOCK_ADDRESS, address);
				throw builder.build(
						"Record index " + index + " out of range. Array contains " + size + " elements"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}

			int growableBlockSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);

			if (growableBlockSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
				// If this array is so big that it's using a metablock, look up the correct sub-block and use the
				// correct address within the sub-block
				int blockRelativeIndex = growableBlockRelativeIndex % GrowableBlockHeader.MAX_GROWABLE_SIZE;
				int block = growableBlockRelativeIndex / GrowableBlockHeader.MAX_GROWABLE_SIZE;

				long dataBlockAddress = growableBlockAddress + MetaBlockHeader.META_BLOCK_HEADER_BYTES
						+ block * Database.PTR_SIZE;
				growableBlockAddress = db.getRecPtr(dataBlockAddress);
				if (growableBlockAddress == 0) {
					throw nd.describeProblem()
						.addProblemAddress("backpointer number " + block, dataBlockAddress, Database.PTR_SIZE) //$NON-NLS-1$
						.addProblemAddress(GROWABLE_BLOCK_ADDRESS, address)
						.build("Null data block found in metablock"); //$NON-NLS-1$
				}
				growableBlockRelativeIndex = blockRelativeIndex;
			}

			long dataStartAddress = growableBlockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
			return dataStartAddress + growableBlockRelativeIndex * Database.PTR_SIZE;
		} else {
			// This record is one of the ones inlined in the header
			return address + ARRAY_HEADER_BYTES + index * Database.PTR_SIZE;
		}
	}

	private void addSizeTo(Nd nd, long address, IndexExceptionBuilder builder) {
		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
		if (growableBlockAddress != 0) {
			builder.addProblemAddress(GrowableBlockHeader.ARRAY_SIZE, growableBlockAddress);
		}
	}

	/**
	 * Removes an entry from the array, given an element index. If the given index is not the last element
	 * in the list, the last element will have its index swapped with the removed element. If another element
	 * was swapped into the position of the removed element, this returns the value of that element. Otherwise,
	 * it returns 0.
	 */
	public long remove(Nd nd, long address, int index) {
		int currentSize = size(nd, address);
		int lastElementIndex = currentSize - 1;

		Database db = nd.getDB();
		if (index > lastElementIndex || index < 0) {
			IndexExceptionBuilder descriptor = nd.describeProblem().addProblemAddress(GROWABLE_BLOCK_ADDRESS, address);
			addSizeTo(nd, address, descriptor);
			throw descriptor.build("Attempt to remove nonexistent element " + index //$NON-NLS-1$
					+ " from an array of size " + (lastElementIndex + 1)); //$NON-NLS-1$
		}

		long toRemoveAddress = getAddressOfRecord(nd, address, index);
		long returnValue;
		// If we're removing the last element
		if (index == lastElementIndex) {
			returnValue = 0;
			// Clear out the removed element
			db.putRecPtr(toRemoveAddress, 0);
		} else {
			long lastElementAddress = getAddressOfRecord(nd, address, lastElementIndex);
			long lastElementValue = db.getRecPtr(lastElementAddress);

			// Move the last element into the position occupied by the element being removed (this is a noop if
			// removing the last element)
			db.putRecPtr(toRemoveAddress, lastElementValue);

			// Clear out the last element
			db.putRecPtr(lastElementAddress, 0);

			returnValue = lastElementValue;
		}

		// Update the array size
		setSize(nd, address, currentSize - 1);
		repackIfNecessary(nd, address, currentSize);

		return returnValue;
	}

	/**
	 * Checks if we should reduce the amount of allocated in the growable region, such that the array can hold the given
	 * number of elements.
	 * 
	 * @param desiredSize
	 *            the new current size of the array or 0 to free up all memory
	 */
	private void repackIfNecessary(Nd nd, long address, int desiredSize) {
		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);

		// If there is no growable block then the array is already as small as we can make it. Nothing to do.
		if (growableBlockAddress == 0) {
			return;
		}

		int desiredGrowableSize = desiredSize - this.inlineSize;

		int currentGrowableSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
		int newGrowableSize = getGrowableRegionSizeFor(desiredSize);

		// We only need to repack if the new size is smaller than the old one
		if (newGrowableSize >= currentGrowableSize) {
			return;
		}

		Database db = nd.getDB();
		if (currentGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
			// We are currently using a metablock
			int desiredBlockCount = (newGrowableSize + GrowableBlockHeader.MAX_GROWABLE_SIZE - 1)
					/ GrowableBlockHeader.MAX_GROWABLE_SIZE;
			int currentBlockCount = currentGrowableSize / GrowableBlockHeader.MAX_GROWABLE_SIZE;

			// Only deallocate memory if either there are either two full unused blocks
			// or the desired size is less than or equal to half of a block + 1. We add one to ensure
			// that the newly-shrunk array will still be about double the size of the used elements.
			boolean needsRepacking = (currentBlockCount - desiredBlockCount > 1)
					|| (newGrowableSize <= (GrowableBlockHeader.MAX_GROWABLE_SIZE / 2 + 1));
			if (!needsRepacking) {
				return;
			}

			long metablockRecordsAddress = growableBlockAddress + MetaBlockHeader.META_BLOCK_HEADER_BYTES;
			int currentBlock = currentBlockCount;
			while (--currentBlock >= desiredBlockCount) {
				long nextAddress = metablockRecordsAddress + currentBlock * Database.PTR_SIZE;
				long oldBlockAddress = db.getRecPtr(nextAddress);
				db.free(oldBlockAddress, Database.POOL_GROWABLE_ARRAY);
				db.putRecPtr(nextAddress, 0);
			}

			// If we still need to be using a metablock, we're done
			if (newGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
				// First record the new growable region size
				GrowableBlockHeader.ALLOCATED_SIZE.put(nd, growableBlockAddress, newGrowableSize);
				return;
			}

			// Else we need to stop using a metablock.
			// Dispose the metablock and replace it with the first growable block
			long firstBlockAddress = db.getRecPtr(metablockRecordsAddress);
			int oldSize = GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress);
			db.free(growableBlockAddress, Database.POOL_GROWABLE_ARRAY);

			GROWABLE_BLOCK_ADDRESS.put(nd, address, firstBlockAddress);

			if (firstBlockAddress != 0) {
				currentGrowableSize = GrowableBlockHeader.MAX_GROWABLE_SIZE;
				GrowableBlockHeader.ARRAY_SIZE.put(nd, firstBlockAddress, oldSize);
				GrowableBlockHeader.ALLOCATED_SIZE.put(nd, firstBlockAddress,
						GrowableBlockHeader.MAX_GROWABLE_SIZE);
			}

			// Then we'll fall through to the normal (non-metablock) case, which may shrink the size of the last
			// growable block further
		}

		// If we're not using metablocks, we only resize the growable region once the size of the array shrinks
		// such that we're only using 1/4 of it.
		if (desiredGrowableSize <= (currentGrowableSize / 4 + 1)) {
			long newBlockAddress = resizeBlock(nd, address, newGrowableSize);

			GROWABLE_BLOCK_ADDRESS.put(nd, address, newBlockAddress);
		}
	}

	/**
	 * Returns the number of elements that should actually be allocated in the growable region for an array of the given
	 * size
	 */
	private int getGrowableRegionSizeFor(int arraySize) {
		int growableRegionSize = arraySize - this.inlineSize;

		if (growableRegionSize <= 0) {
			return 0;
		}

		// Find the next power of two that is equal or greater than the required size. We use inlineSize
		// as the minimum growable block size since we tend to assign a large inlineSize to lists with a large
		// average number of elements, and these are also the lists that will benefit from a larger initial block size.
		int nextGrowableSize = getNextPowerOfTwo(Math.max(growableRegionSize, this.inlineSize));

		if (nextGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
			// If the next power of two is greater than the max block size but the requested size is smaller than it,
			// clamp it to the the max block size
			if (growableRegionSize <= GrowableBlockHeader.MAX_GROWABLE_SIZE) {
				return GrowableBlockHeader.MAX_GROWABLE_SIZE;
			}

			// For sizes larger than the max block size, we need to use a metablock. In this case, the allocated size
			// will be a multiple of the max block size.
			return MathUtils.roundUpToNearestMultiple(growableRegionSize, GrowableBlockHeader.MAX_GROWABLE_SIZE);
		}

		return nextGrowableSize;
	}

	/**
	 * Returns the largest power of two that is less than or equal to the given integer
	 */
	private static int getPrevPowerOfTwo(int n) {
		n |= (n >> 1);
		n |= (n >> 2);
		n |= (n >> 4);
		n |= (n >> 8);
		n |= (n >> 16);
		return n - (n >> 1);
	}

	/**
	 * Returns the next power of two that is equal to or greater than the given int.
	 */
	private static int getNextPowerOfTwo(int toTest) {
		int highBit = getPrevPowerOfTwo(toTest);
		int nextGrowableSize = highBit;

		if (highBit != toTest) {
			assert (nextGrowableSize << 1) != 0;
			nextGrowableSize <<= 1;
		}
		return nextGrowableSize;
	}

	/**
	 * Returns the record size for a RawGrowableSize with the given number of inline records
	 */
	public int getRecordSize() {
		return ARRAY_HEADER_BYTES + Database.PTR_SIZE * this.inlineSize;
	}

	public void destruct(Nd nd, long address) {
		repackIfNecessary(nd, address, 0);
	}

	
	/**
	 * Returns true iff the size of the array is 0
	 * 
	 * @param address address of the array
	 * @return the array size, in number elements
	 */
	public boolean isEmpty(Nd nd, long address) {
		Database db = nd.getDB();
		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);

		if (growableBlockAddress == 0) {
			if (this.inlineSize == 0) {
				return true;
			}
			// If there is no growable block or metablock, then the size is determined by the position of the first
			// null pointer among the inline records.
			long firstValue = db.getRecPtr(address + ARRAY_HEADER_BYTES);

			return firstValue == 0;
		}
		return GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress) == 0;
	}

	public int getCapacity(Nd nd, long address) {
		long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);

		if (growableBlockAddress == 0) {
			return this.inlineSize;
		}

		int growableBlockCurrentSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);

		return growableBlockCurrentSize + this.inlineSize;
	}
}

Back to the top