Expand description
A software TLB: the shape compiled code needs to reach guest memory without calling back into Rust.
The Mmu answers an access with a hash lookup, a
per-page bounds walk and a per-byte permission scan. That is the right shape
for an interpreter, which pays a dispatch per operation anyway, and the wrong
one for compiled code, where it is the only thing standing between a guest
load and a machine load. This cache is the part a compiled block can inline:
an index, a tag compare, and an add.
§What an entry promises, and what it does not
An entry says only where the page lives: the host address of a resident
PageData for one guest page. It says nothing about
permissions. Compiled code checks those itself, per byte, out of the same
allocation — which is why PageData keeps the
permission bytes next to the data bytes at a fixed offset rather than in a
second allocation.
Splitting reads from writes would let the table itself carry the coarse permission, as icicle’s does. It would buy nothing here: this MMU’s permissions are per byte, so the byte scan happens either way, and one table means one entry to fill and invalidate.
§Invalidation
An entry holds a raw pointer into a page allocation, so it outlives its page only if nobody drops one. Every operation that can add, drop or replace a page flushes the whole table; permission edits do not, because permissions are read live from the page rather than cached here.
Structs§
- TlbEntry
- One cached translation.
- Translation
Cache - The table compiled code indexes.
Constants§
- TLB_
ENTRIES - Number of entries in the table. A power of two, so indexing is a mask.
- TLB_
INDEX_ BITS - log2 of the number of entries. 64 entries covers the working set of the code this runs — a stack page, a couple of data pages, the code page — with room for a few more before it thrashes, and the whole table stays inside a few cache lines.