F4.05 · part 1 of 3
Bitmapped nodes
A HAMT node could be a 32-element array, but most slots in most nodes are empty, so that wastes space. Instead a node keeps one 32-bit bitmap — a bit set for each occupied slot — and one packed array holding only the occupants, in slot order. To find a slot’s position in that array you take a popcount: the number of set bits below it.
A node with entries at slots 1 and 6 and a child at slot 4 (8 of 32 slots shown). One array holds all three. Select what to read.
One bitmap, one array
The bitmap says which slots are used; the array holds the contents densely. Reading a slot is two steps: test the bit, then index into the array by the popcount of the lower bits. Select the bitmap, the array, or watch the index get computed.
What to read · select one
role: which slots are occupied
result: bitmap → 3 packed slots
A bitmap plus a popcount replaces a 32-wide array of mostly-empty slots with a compact one. The node stores one small array and one integer, and still finds any slot in constant time.
Advanced: leaves and children, mixed
That one array holds two kinds of thing: an inline entry (a key and its value) and a child node (a pointer to
the next level). They sit together, so the code that reads a slot needs a way to tell them apart — a tag
on each cell, or a side table. The bitmap alone does not say which is which; it only says the slot is used.
That mixing is the one compromise the layout makes, and it is the thing F4.06 removes by keeping
two bitmaps and two arrays, one per kind. The index trick is the same popcount either way, and
popcount is a single CPU instruction on modern hardware, so the lookup stays cheap.
This compact node is the unit the rest of the spine builds on: a HAMT stacks these into a tree (F4.05.2), an insert copies a path of them and shares the rest (F4.05.3), and a CHAMP refines the node into its canonical, two-bitmap form (F4.06). The portal’s page registry is stored in nodes like this, one per level of a key’s hash.
import Bitwise # is slot i occupied? — test the one bitmap def occupied?(bitmap, i), do: (bitmap &&& (1 <<< i)) != 0 # position in the packed array = popcount of the lower bits def index(bitmap, i), do: popcount(bitmap &&& ((1 <<< i) - 1)) # index(bitmap, 4) = popcount(bits 0..3) = 1 -> slots[1] # each slots[] cell is a leaf or a child — a tag says which
F4.05.1 · one bitmap, one mixed array
Leaves and children share a single packed array; a tag tells them apart.
F4.06 · two bitmaps, two arrays
A CHAMP separates entries from children, so no tag is needed and the shape is canonical.
Next: hash-prefix indexing — how the key’s hash chooses which slot to read at each level of the tree.
References
Primary sources for this lesson, and where it connects in the course.
Sources
- Bagwell, “Ideal Hash Trees” (2001) — the original HAMT paper.
- Hash array mapped trie — Wikipedia — the structure in brief.