Winding passage

This is a crooked corridor from the north, with forks to the southwest and south.

> go north

If you think the winding passage is deliberately designed to be easily confused with the twisting passage, you’re right!

Code for this episode can be found in

We’ve managed to make it through two instructions without crashing, which is great. The next one to implement would be “add”, but I’d like to implement returns first, because we’ve still got calls fresh in our minds. What needs to happen on a return?

All routines return a value – or, at least, the ones that return at all do! Note that this is true even if we’re using a version of the Z-machine where the particular call instruction discards the value. Our helper will take the value to be returned.

We need to pop off the topmost frame, reset the program counter to point to the instruction after the call that got us here and store the result in the variable indicated by the call. All in all much less work than doing the call, but still, we’ve got stuff to do here.

Suppose we already have computed the value. This helper function does the rest:

let interpret_return interpreter value =
 let frame = current_frame interpreter in
 let next_pc = Frame.resume_at frame in
 let store = frame in
 let pop_frame_interpreter = remove_frame interpreter in
 let result_interpreter = set_program_counter pop_frame_interpreter next_pc in
 interpret_store result_interpreter store value

Just to be clear: this thing takes an interpreter and a value, and produces the interpreter which is the result of returning that value.

We can now implement the “ret” instruction in our interpreter. The ret instruction is very straightforward: it has a single operand which is evaluated to produce the returned value.

I’m going to follow a pattern whereby almost every instruction has an associated “handle” function, but I’m going to have other code talk directly to interpret_return later in this episode, so we’ll keep these logically separated even though doing so is not strictly speaking necessary:

let handle_ret result interpreter =
    interpret_return interpreter result

And in step_instruction we add:

match (opcode, arguments) with
  | (OP1_139, [result]) -> handle_ret result interpreter 

And that is actually it for returns; pretty straightforward.

Now that we have returns, we can interpret the “branch” portion of any instruction that has one. Recall that instructions which have a conditional branch may choose from “return true” “return false”, and “branch to a relative address” if the branch condition is met.

Of course we will not use this helper method for calls and returns, as they have special logic for determining the next instruction. But any “normal” instruction either has a branch or does not; if it has no branch then control moves to the following instruction. If it has a branch then the result is evaluated as a Boolean — zero is false, everything else is true — and checked to see if it matches the “sense” of the branch. That is, we saved away whether this branch was “branch on false” or “branch on true”. If the condition is met then we take the branch, otherwise we move on to the following instruction:

let interpret_branch interpreter instruction result =
  let result = not (result = 0) in 
  let following = Instruction.following instruction in
  match Instruction.branch instruction with
  | None -> set_program_counter interpreter following 
  | Some (sense, Return_false) ->
    if result = sense then interpret_return interpreter 0
    else set_program_counter interpreter following 
  | Some (sense, Return_true) ->
    if result = sense then interpret_return interpreter 1
    else set_program_counter interpreter following 
  | Some (sense, Branch_address branch_target) ->
    if result = sense then set_program_counter interpreter branch_target
    else set_program_counter interpreter following 

Next time on FAIC: We’ll make some helper methods, and then implement the “add” instruction.

2 thoughts on “Winding passage

  1. Hey, just (re)discovered your blog!

    You long ago linked to my (now defunct) blog regarding the z-machine interpreter I wrote in C#. Ironically, I used a lot of functional style stuff like persistent data structures that I learned about on your blog.

    I’ve since switched entirely to Linux, these days only doing C# stuff for work. I have been thinking for a couple years about redoing my interpreter in another language and your blog has inspired me to get started. I will enjoy following along (after I get caught up with past entries). I think I am going to use Haskell.

    Thanks for the inspiration (again)!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s