RamStore Module
How a read/write memory's contents are held while a simulation runs. See [docs/dev/ramRepresentation.md] for why this exists and what it is worth. In short: the contents used to be a `Map` and a fresh snapshot of it was stored for every clock step, so a write cost an AVL path copy plus two boxed `BigInt`s, a read cost a descent with boxed comparisons, and a long waveform simulation retained every version it had ever made. Here - a read during simulation is an array index, - a write appends two numbers to shared flat arrays, - the value an address held at any past step is a binary search of that address's own writes, whose cost does not depend on how far the cursor moved, and - nothing at all is allocated per step. **Storage is CSR.** Every address that is ever written is given a *slot* number, and a slot's writes are one contiguous run of the shared `IStep`/`IVal` arrays, delimited by `Start`. The alternative - a growable list per address - costs a record and two list objects per address, which is around 230 bytes for what may be a single 8-byte write. That is affordable for one big RAM and is not for a hundred small ones, which between them have just as many addresses. Recent writes go to a small tail and are folded in by `compact`. **Where this file sits.** F# compile order is the dependency layering, and `SimGraphTypes.SimulationComponentState` names this type, so it has to be compiled before `SimGraphTypes.fs` and cannot live in the `FastSim` block with the rest of the simulator. It depends on nothing but `CommonTypes`, which is what makes that possible. **Mutability.** This is the same layer as the step arrays, which are already mutable, and [docs/mutableState.md] allows it for a measured performance reason - the whole point here is to stop allocating. Nothing mutable escapes: `Memory1` remains the immutable type everywhere outside the running simulation, and `toMemory` builds one on demand.
Types and nested modules
Functions and values
| Function or value | Description |
|
|
|
|
Full Usage:
liveCountExceeds ram step limit
Parameters:
Ram
step : int
limit : int
Returns: bool
|
Whether more than `limit` words were non-zero at the end of `step`. This is all the RAM table needs: it chooses between listing every non-zero location and a windowed display, and the exact figure never appears. Asking the cheaper question means the walk stops as soon as the answer is settled, and usually it never starts - a memory that has never had more than `limit` addresses written cannot have more than `limit` non-zero.
|
|
|
|
|
|
|
|
|
|
|
|
Current contents at `addr`, on the bigint-value path. A store keeps values in one of two forms, chosen once by word width, and the caller has to ask for the one it holds - the evaluators do, via `BigIntState`. Asking for the other would otherwise read an array that was never filled, so it is redirected rather than trusted.
|
|
|
|
|
Full Usage:
sparseUpTo ram step limit
Parameters:
Ram
step : int
limit : int
Returns: (bigint * bigint) list option
|
The non-zero locations at the end of `step`, in address order - or None when there are more than `limit` of them. This is what a RAM table's sparse display is: every location worth a row, when there are few enough to list. It replaces asking `liveCountExceeds` and then building the answer with `toMemory`, which is the right question answered the expensive way - the count is bounded by the limit but the whole-memory read is not, so a memory with 65,536 addresses written and fifty non-zero now took the sparse branch and paid 80 ms for it, on every render. Bounded by TWO things, and it needs both. The slot table is the set of addresses that could be non-zero - a slot exists once an address has been written, and `reset` seeds the initial contents as writes at step -1 - so the walk stops at the first `limit + 1` non-zero words. But a memory with 65,536 addresses written and fifty non-zero now would run out of SLOTS before it ran out of limit, and walking all of them is the 80 ms read this exists to avoid. So a memory with more than `Constants.maxSlotsForWholeRead` addresses ever written answers None without walking at all: the caller shows a window instead. The count that decides it is therefore addresses EVER WRITTEN, not words currently non-zero. It has to be: the second is what the walk is for, so it cannot also be its precondition.
|
|
|
|
The whole memory as of `step`, when reading it is affordable - None when it is not. What "affordable" means, and why it is a count of addresses ever written rather than of words held, is on `Constants.maxSlotsForWholeRead`. A caller told None must show something bounded instead, and must not fall back to `toMemory`.
|
|
|
Full Usage:
writeAddrBigIntDataBigInt ram step addr value
Parameters:
Ram
step : int
addr : bigint
value : bigint
|
|
Full Usage:
writeAddrBigIntDataUInt32 ram step addr value
Parameters:
Ram
step : int
addr : bigint
value : uint32
|
|
Full Usage:
writeAddrUInt32DataBigInt ram step addr value
Parameters:
Ram
step : int
addr : uint32
value : bigint
|
|
Full Usage:
writeAddrUInt32DataUInt32 ram step addr value
Parameters:
Ram
step : int
addr : uint32
value : uint32
|
|
Full Usage:
writeBigVal ram step addr value
Parameters:
Ram
step : int
addr : uint32
value : bigint
|
|
Full Usage:
writeU ram step addr value
Parameters:
Ram
step : int
addr : uint32
value : uint32
|
Record a write, unless it changes nothing. A write of the value already at that address is not a write: skipping it costs one comparison on a value the reducer has already read (it is the old contents, which a RAM returns as its output) and saves an entry, which every later search and every later compaction then does not have to walk. Nothing observable changes - the contents are identical by definition, and the RAM table's read/write highlighting comes from the input step arrays rather than from here.
|