You have a couple of stateful properties. Those are the registers and memory. And we have a transition between states. This is each CPU cycle. We can therefore model this architecture in a straightforward fashion:
function cycle(state: State) -> State { ... }
Our cycle
function will treat the input state
as an immutable object, and create a new state to return. This involves copying most data over to the new state. This is not a problem until you start caring about performance.
The CPU cycles until some halting state is reached. This could be expressed as a loop:
while(! state.isHaltingState()) {
state = cycle(state);
}
which could of course also be expressed as infinite recursion.
Inside our cycle
function, we locate, decode, and perform the current instruction. All instructions will at least mutate the instruction pointer. In JavaScript (which is not a pure functional language), the approach would be:
- Make a copy of the old registers
- Execute the current instruction, which possible mutates the current registers
- Treat the registers as immutable and add them to the next state
- Return the next state.
Unlike registers, memory (= an array of memory cells, e.g. bytes) will not necessarily be mutated during each instruction. If memory was not touched, we don't have to perform a copy and can simply reference the old memory in the next state. If we do change any cell, we have to create an updated copy for use in the new state. The impact of this can be reduced by dividing memory into pages, and only replacing pages that were actually touched. Effectively, this approach turns the array into an n-ary tree of arrays of fixed depth.
I would model each instruction as a function that calculates a delta from the current state. This way, the code for applying the delta to the current state and returning a new state in an effective manner does not have to be repeated throughout the code. A delta could be a small object such as { registers: { eax: 0x42}, memory: {0x1234: 0x00} }
.