Skip to main content
summaryrefslogtreecommitdiffstats
blob: 969a893c097c41dde51ffb5c7cc0841b31c60f80 (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
/*******************************************************************************
 * Copyright (c) 2015, 2016 Google, Inc and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * 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;

/**
 * {@link NdRawLinkedList} stores a list of fixed-sized records. Along with the records themselves, there is also
 * a bit field associated with each record which can hold a small number of bits of metadata per record.
 * The underlying format is as follows:
 *
 * <pre>
 * Bytes       Content
 * ----------------
 * 4           Pointer to the next block. If this is 0, this is the last block and it is not yet full. The number of
 *             elements will be stored at the position where the last element would normally start. If this points back
 *             to the start of the block, this is the last block and it is full. If this holds any other value, the
 *             block is full and this points to the next block.
 * headerSize  Bit field for this block (the bits for each element are tightly packed)
 * recordSize  The content of the first element in the block
 * recordSize  The content of the second element in the block
 * ...         repeated recordsPerBlock times
 * recordSize  If the block is full, this holds the last
 * </pre>
 *
 * stored in linked blocks where each block is an array of record pointers. Each block contains a pointer to the
 * subsequent block, so they can be chained.
 * <p>
 * The size of the blocks are generally hardcoded. All blocks are the same size except for the first block whose size
 * may be configured independently. The size of the first block may be zero, in which case the first "block" is
 * simply a pointer to the following block or null.
 */
public class NdRawLinkedList {
	private static final int NEXT_MEMBER_BLOCK = 0;
	private static final int ELEMENT_START_POSITION = NEXT_MEMBER_BLOCK + Database.PTR_SIZE;

	private final long address;
	private final Nd nd;
	private final int firstBlockRecordCount;
	private final int recordCount;
	private final int elementRecordSize;
	private final int metadataBitsPerRecord;

	// Derived data. Holds the address for the last block we know about
	private long lastKnownBlock;

	public static interface ILinkedListVisitor {
		public void visit(long address, short metadataBits, int index) throws IndexException;
	}

	/**
	 * @param nd the Nd object
	 * @param address pointer to the start of the linked list
	 * @param recordsPerBlock number of records per block. This is normally a hardcoded value.
	 */
	public NdRawLinkedList(Nd nd, long address, int elementRecordSize, int firstBlockRecordCount, int recordsPerBlock,
			int metadataBitsPerRecord) {
		assert(recordsPerBlock > 0);
		assert(firstBlockRecordCount >= 0);
		this.nd = nd;
		this.address = address;
		this.firstBlockRecordCount = firstBlockRecordCount;
		this.recordCount = recordsPerBlock;
		this.elementRecordSize = elementRecordSize;
		this.lastKnownBlock = address;
		this.metadataBitsPerRecord = metadataBitsPerRecord;
	}

	/**
	 * Returns the record size for a linked list with the given element record size and number of
	 * records per block
	 */
	public static int recordSize(int elementRecordSize, int recordsPerBlock, int metadataBitsPerRecord) {
		int metadataSize = 0;

		if (metadataBitsPerRecord > 0) {
			int metadataRecordsPerShort = 16 / metadataBitsPerRecord;
			int numberOfShorts = (recordsPerBlock + metadataRecordsPerShort - 1) / metadataRecordsPerShort;

			metadataSize = 2 * numberOfShorts;
		}

		return Database.PTR_SIZE + elementRecordSize * recordsPerBlock + metadataSize;
	}

	public Nd getNd() {
		return this.nd;
	}

	private int getElementsInBlock(long currentRecord, long ptr, int currentRecordCount) throws IndexException {
		if (ptr == 0 && currentRecordCount > 0) {
			return getDB().getInt(getAddressOfElement(currentRecord, currentRecordCount - 1));
		}
		return currentRecordCount;
	}

	private Database getDB() {
		return this.nd.getDB();
	}

	public long getAddress() {
		return this.address;
	}

	/**
	 * Adds a new element to the list and returns the record pointer to the start of the newly-allocated object
	 *
	 * @param metadataBits the metadata bits to attach to the new member. Use 0 if this list does not use metadata.
	 */
	public long addMember(short metadataBits) throws IndexException {
		Database db = getDB();
		long current = this.lastKnownBlock;
		int thisBlockRecordCount = this.firstBlockRecordCount;
		while (true) {
			long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
			int elementsInBlock = getElementsInBlock(current, ptr, thisBlockRecordCount);

			// If there's room in this block
			if (elementsInBlock < thisBlockRecordCount) {
				long positionOfElementCount = getAddressOfElement(current, thisBlockRecordCount - 1);
				// If there's only one space left
				if (elementsInBlock == thisBlockRecordCount - 1) {
					// We use the fact that the next pointer points to itself as a sentinel to indicate that the
					// block is full and there are no further blocks
					db.putRecPtr(current + NEXT_MEMBER_BLOCK, current);
					// Zero out the int we've been using to hold the count of elements
					db.putInt(positionOfElementCount, 0);
				} else {
					// Increment the element count
					db.putInt(positionOfElementCount, elementsInBlock + 1);
				}

				if (this.metadataBitsPerRecord > 0) {
					int metadataMask = (1 << this.metadataBitsPerRecord) - 1;
					int metadataRecordsPerShort = this.metadataBitsPerRecord == 0 ? 0
							: (16 / this.metadataBitsPerRecord);
					metadataBits &= metadataMask;

					int metadataBitOffset = elementsInBlock % metadataRecordsPerShort;
					long metadataStart = getAddressOfMetadata(current, thisBlockRecordCount);
					int whichShort = elementsInBlock / metadataRecordsPerShort;
					long metadataOffset = metadataStart + 2 * whichShort;
					short metadataValue = db.getShort(metadataOffset);

					// Resetting the previous visibility bits of the target member.
					metadataValue &= ~(metadataMask << metadataBitOffset * this.metadataBitsPerRecord);
					// Setting the new visibility bits of the target member.
					metadataValue |= metadataBits << metadataBitOffset * this.metadataBitsPerRecord;

					getDB().putShort(metadataOffset, metadataValue);
				}

				this.lastKnownBlock = current;
				return getAddressOfElement(current, elementsInBlock);
			} else {
				// When ptr == current, this is a sentinel indicating that the block is full and there are no
				// further blocks. If this is the case, create a new block
				if (isLastBlock(current, ptr)) {
					current = db.malloc(
							recordSize(this.elementRecordSize, this.recordCount, this.metadataBitsPerRecord), Database.POOL_LINKED_LIST);
					db.putRecPtr(current + NEXT_MEMBER_BLOCK, current);
				} else {
					thisBlockRecordCount = this.recordCount;
					// Else, there are more blocks following this one so advance
					current = ptr;
				}
			}
		}
	}

	private long getAddressOfElement(long blockRecordStart, int elementNumber) {
		return blockRecordStart + ELEMENT_START_POSITION + elementNumber * this.elementRecordSize;
	}

	private long getAddressOfMetadata(long blockRecordStart, int blockRecordCount) {
		return getAddressOfElement(blockRecordStart, blockRecordCount);
	}

	public void accept(ILinkedListVisitor visitor) throws IndexException {
		int count = 0;
		Database db = getDB();

		int blockRecordCount = this.firstBlockRecordCount;
		int metadataMask = (1 << this.metadataBitsPerRecord) - 1;
		int metadataRecordsPerShort = this.metadataBitsPerRecord == 0 ? 0 : (16 / this.metadataBitsPerRecord);
		long current = this.address;
		while (true) {
			long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
			int elementsInBlock = getElementsInBlock(current, ptr, blockRecordCount);

			long metadataStart = getAddressOfMetadata(current, blockRecordCount);
			for (int idx = 0; idx < elementsInBlock; idx++) {
				long elementRecord = getAddressOfElement(current, idx);

				short metadataBits = 0;

				if (metadataRecordsPerShort > 0) {
					int metadataBitOffset = idx % metadataRecordsPerShort;
					int whichShort = idx / metadataRecordsPerShort;
					long metadataOffset = metadataStart + 2 * whichShort;
					metadataBits = getDB().getShort(metadataOffset);

					metadataBits >>>= metadataBits * metadataBitOffset;
					metadataBits &= metadataMask;
				}

				visitor.visit(elementRecord, metadataBits, count++);
			}

			blockRecordCount = this.recordCount;

			if (isLastBlock(current, ptr)) {
				return;
			}

			current = ptr;
		}
	}

	public void destruct() throws IndexException {
		Database db = getDB();
		long current = this.address;
		while (true) {
			long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
			db.free(current, Database.POOL_LINKED_LIST);

			if (isLastBlock(current, ptr)) {
				return;
			}

			current = ptr;
		}
	}

	private boolean isLastBlock(long blockAddress, long pointerToNextBlock) {
		return pointerToNextBlock == 0 || pointerToNextBlock == blockAddress;
	}

	/**
	 * Returns the number of elements in this list. This is an O(n) operation.
	 * @throws IndexException
	 */
	public int size() throws IndexException {
		int count = 0;
		Database db = getDB();
		int currentRecordCount = this.firstBlockRecordCount;
		long current = this.address;
		while (true) {
			long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
			count += getElementsInBlock(current, ptr, currentRecordCount);

			if (isLastBlock(current, ptr)) {
				break;
			}

			currentRecordCount = this.recordCount;
			current = ptr;
		}

		return count;
	}
}

Back to the top