Skip to content

Commit 3907513

Browse files
committed
Misc. work on allocators.
Added new implementation with less reliance on memory correctness of program (still not entirely working). Misc. work on important data structures for alternative allocators with no reliance on guest correctness.
1 parent b23320b commit 3907513

File tree

7 files changed

+535
-17
lines changed

7 files changed

+535
-17
lines changed

src/main/java/myworld/hummingbird/util/Allocator.java

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import myworld.hummingbird.HummingbirdVM;
44

5+
import java.util.Arrays;
6+
57
import static myworld.hummingbird.HummingbirdVM.NULL;
68

79
/**
@@ -137,7 +139,6 @@ public Allocator(HummingbirdVM vm, int baseAddress, int initialSize){
137139

138140
hvm = vm;
139141
head = baseAddress;
140-
tail = head;
141142
Header = new HeaderStruct(vm);
142143

143144
this.initialSize = initialSize;
@@ -207,13 +208,18 @@ public synchronized int malloc(int nbytes){
207208

208209
for(var block = Header.nextBlock(head); block != NULL; previousBlock = block, block = Header.nextBlock(block)){
209210

210-
if(Header.size(block) == nbytes && !(previousBlock == head && Header.nextBlock(block) == NULL)){
211+
if(Header.size(block) == nbytes && !(previousBlock == head && block == tail)){
211212
// Exact match - don't resize, just join the preceding and trailing blocks.
212213
// Can't do this on the head block because there is no prior block to work with.
213214

214215
Header.nextBlock(previousBlock, Header.nextBlock(block));
215216
Header.nextBlock(block, NULL);
216217

218+
System.out.println("Exact malloc(%d): %s (tail is %s (%d))".formatted(nbytes, Pointer.toString(block), Pointer.toString(tail), Header.size(tail)));
219+
if(block == tail){
220+
System.out.println("Did exact allocation on tail. Prior block is %s, prior's new next is %s".formatted(Pointer.toString(previousBlock), Pointer.toString(Header.nextBlock(previousBlock))));
221+
}
222+
217223
return block + Header.sizeOf();
218224
}else if(Header.size(block) >= nbytes + Header.sizeOf()){
219225

@@ -232,9 +238,14 @@ public synchronized int malloc(int nbytes){
232238
if(block == tail){
233239
// We just snagged the first bit of the tail block, so update
234240
// the tail to the new block
241+
if(newBlock == 452984836){
242+
System.out.println("Found it");
243+
new Exception().printStackTrace();
244+
}
235245
tail = newBlock;
236246
Header.nextBlock(newBlock, NULL);
237247
}
248+
System.out.println("Split malloc(%d): %s".formatted(nbytes, Pointer.toString(block)));
238249
return block + Header.sizeOf();
239250
}
240251

@@ -244,6 +255,7 @@ public synchronized int malloc(int nbytes){
244255
// to be large enough.
245256
if(block == tail){
246257
block = morecore(nbytes);
258+
//System.out.println("New core ptr: " + Pointer.toString(block));
247259
if(block == NULL){
248260
return NULL;
249261
}
@@ -261,6 +273,10 @@ protected int freeInternal(int ptr){
261273
}
262274

263275
int hPtr = ptr - Header.sizeOf();
276+
System.out.println("free(%s)".formatted(Pointer.toString(hPtr)));
277+
System.out.println("Free blocks: " + Arrays.toString(Arrays.stream(freeBlocks()).mapToObj(Pointer::toString).toArray()));
278+
System.out.println("Free block sizes: " + Arrays.toString(blockSizes()));
279+
//new Exception().printStackTrace(System.out);
264280

265281
int block = head;
266282
if(hPtr > tail){
@@ -278,23 +294,38 @@ protected int freeInternal(int ptr){
278294
block = next;
279295
}
280296
}
297+
System.out.println("block: " + Pointer.toString(block));
281298

282299
// Insert hPtr into the list
283300
// Note that when block == tail, nextBlock is NULL.
301+
if(block == tail){
302+
System.out.println("Freeing from tail (%s): next is (%s)".formatted(Pointer.toString(tail), Pointer.toString(Header.nextBlock(tail))));
303+
}
284304
int nextBlock = Header.nextBlock(block);
285305

286306
Header.nextBlock(block, hPtr);
287307
Header.nextBlock(hPtr, nextBlock);
288308

289309
// Now merge (if able) block, hPtr, and the trailing block
290310
var mergedOrNext = mergeIfAble(block, hPtr);
311+
//System.out.println("Merge 1: %s (%d), next %s (%d)".formatted(Pointer.toString(mergedOrNext), Header.size(mergedOrNext),
312+
// Pointer.toString(nextBlock), Header.size(nextBlock)));
291313
if(nextBlock != NULL){
292314
mergedOrNext = mergeIfAble(mergedOrNext, nextBlock);
315+
// System.out.println("Merge 2: %s (%d), next %s (%d)".formatted(Pointer.toString(mergedOrNext), Header.size(mergedOrNext),
316+
// Pointer.toString(Header.nextBlock(mergedOrNext)), Header.size(Header.nextBlock(nextBlock))));
293317
}
318+
//System.out.println("Finished applicable merges");
294319

295320
if(block == tail){
296321
// Sets the tail to the merged block (if merge happened),
297322
// or leaves it pointing to the old tail (if merge didn't happen).
323+
//if(mergedOrNext == 452984832){
324+
//System.out.println("Setting new tail: Block: %s (%d), hPtr: %s (%d), tail: %s (%d)".formatted(Pointer.toString(block), Header.size(block), Pointer.toString(hPtr), Header.size(hPtr), Pointer.toString(tail), Header.size(tail)));
325+
//System.out.println("Merged ptr: " + Pointer.toString(mergedOrNext));
326+
//System.out.println("Tail next block: " + Pointer.toString(Header.nextBlock(tail)));
327+
//System.out.println("Head size: " + Header.size(head));
328+
//}
298329
tail = mergedOrNext;
299330
Header.nextBlock(tail, NULL);
300331
}
@@ -330,16 +361,46 @@ public synchronized int[] blockSizes(){
330361
return sizes;
331362
}
332363

364+
public synchronized int[] freeBlocks(){
365+
var blocks = countFreeBlocks();
366+
var pointers = new int[blocks];
367+
for(int i = 0, block = head; block != NULL; block = Header.nextBlock(block), i++){
368+
pointers[i] = block;
369+
}
370+
return pointers;
371+
}
372+
333373
private int mergeIfAble(int preceding, int trailing){
374+
//if(preceding == 0x7E4 || trailing == 0x7E4 || preceding == 0x7EE || trailing == 0x7EE){
375+
//System.out.println("Merge: Preceding %s (%d), trailing %s (%d)".formatted(
376+
// Pointer.toString(preceding), Header.size(preceding),
377+
// Pointer.toString(trailing), Header.size(trailing)
378+
//));
379+
//}
334380
if(preceding == head){
381+
System.out.println("Preceding is head, not merging");
335382
return trailing; // Can't merge head
336383
}
337384
if(preceding + Header.sizeOf() + Header.size(preceding) == trailing){
338385
// Include the size of the header in the block being merged
339386
Header.size(preceding, Header.size(preceding) + Header.size(trailing) + Header.sizeOf());
340387
Header.nextBlock(preceding, Header.nextBlock(trailing));
388+
//if(preceding == 0x7E4 || trailing == 0x7E4){
389+
// System.out.println("Merged block: %s (%d), next %s (%d)".formatted(
390+
// Pointer.toString(preceding), Header.size(preceding),
391+
// Pointer.toString(Header.nextBlock(preceding)), Header.size(Header.nextBlock(preceding))
392+
// ));
393+
//}
394+
System.out.println("Merged %s (%d)".formatted(Pointer.toString(preceding), Header.size(preceding)));
341395
return preceding;
342396
}
397+
//if(preceding == 0x7E4 || trailing == 0x7E4){
398+
//System.out.println("Tail: " + Pointer.toString(tail));
399+
//System.out.println("Didn't merge, trailing block: %s (%d), next %s".formatted(
400+
// Pointer.toString(trailing), Header.size(trailing),
401+
// Pointer.toString(Header.nextBlock(trailing)))
402+
//);
403+
//}
343404
return trailing;
344405
}
345406

@@ -364,13 +425,18 @@ public HeaderStruct(HummingbirdVM vm){
364425
}
365426

366427
public int nextBlock(int hPtr){
367-
return acc.readInt(hPtr, nextBlock);
428+
var ptr = acc.readInt(hPtr, nextBlock);
429+
if(ptr == 0x1B000000){
430+
System.out.println("Found bad next read at nextBlock(%s)".formatted(Pointer.toString(hPtr)));
431+
new Exception().printStackTrace(System.out);
432+
}
433+
return ptr;
368434
}
369435

370436
public void nextBlock(int hPtr, int ptr){
371-
if(hPtr == 8 && ptr == 0){
372-
new Exception().printStackTrace();
373-
System.exit(0);
437+
if(/*hPtr == 0x7EE &&*/ ptr == 0x1B000000){
438+
System.out.println("Found bad next write: " + ptr);
439+
new Exception().printStackTrace(System.out);
374440
}
375441
acc.writeInt(hPtr, Allocator.HeaderStruct.nextBlock, ptr);
376442
}
@@ -380,6 +446,13 @@ public int size(int hPtr){
380446
}
381447

382448
public void size(int hPtr, int size){
449+
if(hPtr == 0x08 + 4){
450+
System.err.println("Found bad write to head size: " + size);
451+
}
452+
/*if(hPtr == 0x7E4){
453+
System.err.println("Found write to tail: " + size);
454+
new Exception().printStackTrace();
455+
}*/
383456
acc.writeInt(hPtr, Allocator.HeaderStruct.size, size);
384457
}
385458

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package myworld.hummingbird.util;
2+
3+
/**
4+
* Fast 32-bit integer hashing based on https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/
5+
*/
6+
public class FibHash {
7+
8+
public static int hash(int key){
9+
var fib = 1140071481932319848L;
10+
var hash = fib * key;
11+
hash ^= hash >> 32; // Mix in the high bits
12+
return (int) hash;
13+
}
14+
15+
/**
16+
* @param hash hash code to map to the slot count range
17+
* @param slotCount must be a power of two
18+
*/
19+
public static int hashToRange(int hash, int slotCount){
20+
return hash & (slotCount - 1);
21+
}
22+
23+
public static int nextPowerOf2(int n){
24+
// Round up to nearest power of two
25+
var b = Integer.highestOneBit(n);
26+
return n > b ? b << 1 : b;
27+
}
28+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package myworld.hummingbird.util;
2+
3+
import java.util.Arrays;
4+
5+
import static myworld.hummingbird.util.FibHash.*;
6+
7+
/**
8+
* Only supports nonzero keys & values
9+
*/
10+
public class IntHashTable {
11+
12+
private final float loadFactor;
13+
private int count;
14+
private int slots;
15+
private int nominalSize;
16+
private int[] entries;
17+
18+
public IntHashTable(int initialSize, float loadFactor){
19+
if(loadFactor <= 0.0 || loadFactor >= 1.0f){
20+
throw new IllegalArgumentException("Load factor must be in the range (0.0, 1.0)");
21+
}
22+
this.loadFactor = loadFactor;
23+
nominalSize = initialSize;
24+
slots = nextPowerOf2((int) (initialSize / loadFactor));
25+
entries = new int[slots * 2];
26+
}
27+
28+
public void insert(int k, int v){
29+
if(k == 0 || v == 0){
30+
throw new IllegalArgumentException("Illegal k/v pair: (%d, %d)".formatted(k, v));
31+
}
32+
if(count + 1 >= nominalSize){
33+
growTable();
34+
}
35+
var nominalIndex = hashToRange(hash(k), slots);
36+
for(int i = nominalIndex; i < slots; i += 2){
37+
var eKey = entries[i];
38+
if(eKey == 0 || eKey == k){
39+
entries[i] = k;
40+
entries[i + 1] = v;
41+
count++;
42+
return;
43+
}
44+
}
45+
// We didn't find a slot, so grow the table and try again. There are guaranteed to be free slots now.
46+
// This is incredibly unlikely to occur since we already check the load factor and grow the table
47+
// as needed before insert, but this fallback provides robustness against strange load factors.
48+
growTable();
49+
insert(k, v);
50+
}
51+
52+
public int get(int k){
53+
if(k == 0){
54+
throw new IllegalArgumentException("Illegal key: %d".formatted(k));
55+
}
56+
var nominalIndex = hashToRange(hash(k), slots);
57+
for(int i = nominalIndex; i < slots; i += 2){
58+
var eKey = entries[i];
59+
if(eKey == k){
60+
return entries[i + 1];
61+
}
62+
}
63+
return 0;
64+
}
65+
66+
public boolean contains(int k){
67+
if(k == 0){
68+
throw new IllegalArgumentException("Illegal key: %d".formatted(k));
69+
}
70+
var nominalIndex = hashToRange(hash(k), slots);
71+
for(int i = nominalIndex; i < slots; i += 2){
72+
var eKey = entries[i];
73+
if(eKey == k){
74+
return true;
75+
}
76+
}
77+
return false;
78+
}
79+
80+
public int remove(int k){
81+
if(k == 0){
82+
throw new IllegalArgumentException("Illegal key: %d".formatted(k));
83+
}
84+
var nominalIndex = hashToRange(hash(k), slots);
85+
for(int i = nominalIndex; i < slots; i += 2){
86+
var eKey = entries[i];
87+
if(eKey == k){
88+
entries[i] = 0;
89+
var v = entries[i + 1];
90+
entries[i + 1] = 0;
91+
count--;
92+
return v;
93+
}
94+
}
95+
return 0;
96+
}
97+
98+
public void clear(){
99+
Arrays.fill(entries, 0);
100+
count = 0;
101+
}
102+
103+
public int size(){
104+
return count;
105+
}
106+
107+
public int slots(){
108+
return slots;
109+
}
110+
111+
public void copyTo(IntHashTable other){
112+
for(int i = 0; i < slots; i += 2){
113+
var k = entries[i];
114+
if(k != 0){
115+
other.insert(k, entries[i + 1]);
116+
}
117+
}
118+
}
119+
120+
private void growTable(){
121+
var oldTable = entries;
122+
slots = nextPowerOf2(slots + 1);
123+
nominalSize = (int) (slots * loadFactor);
124+
entries = new int[slots * 2];
125+
126+
if(oldTable != null){
127+
count = 0;
128+
// This works because we've already snagged a copy of the entries and set the count to 0
129+
for(int i = 0; i < slots; i += 2){
130+
var k = oldTable[i];
131+
if(k != 0){
132+
insert(k, oldTable[i + 1]);
133+
}
134+
}
135+
}
136+
}
137+
138+
}

0 commit comments

Comments
 (0)