Memory in Grue

In this post, I’ll dig into one of the core areas you must set up first for your Z-Machine implementation: memory. Without having the virtual machine’s memory set up, you can’t effectively do anything.

The Architecture

The Z-Machine specification does provide an architecture diagram. I’ve modified and updated that slightly.

I’ll likely have reason to come back to this diagram in various posts, but for now, the key thing to focus on is the “Main Memory,” which Grue will have to establish and then maintain. What this does is take us into the notion of state. In this post, I’ll talk about memory, and in a follow-on post, I’ll talk about state.

Memory Maps

The bytes in the zcode file that a Z-Machine executes represent the program’s memory map. The Z-Machine specification says the following:

The memory map of the Z-machine is an array of bytes with “byte addresses” running from 0 upwards.

This is consistent with everything I said in the previous post in terms of being byte-oriented. The memory map is a region of memory that contains the program’s instructions and data. The Z-Machine specification also brings up an interesting point:

Note that the routine call state, the stack and the PC must be stored outside the Z-machine memory map, in the interpreter’s private memory.

That idea of “interpreter’s private memory” caused a little cognitive friction for me when I first read it. In a program like Grue, it’s worth asking how the “Z-Machine memory map” and the “interpreter’s private memory” are distinguished.

The answer is that in the context of a Python program that emulates a Z-Machine, you can think of the “Z-Machine memory map” as a bytes object that represents the memory map of the Z-Machine program. This bytes object is created by reading the contents of a zcode file into memory. The “interpreter’s private memory” refers to the memory used by the Python program to execute the Z-Machine program. This includes the stack, the routine call state, and the program counter (PC), all mentioned in the specification and shown in the diagram.

It’s going to take me a bit to implement all that. Still, I found that specific note in the specification worth investigating since it seemed to be an important consideration for implementation. In reality, it’s really just stating something fairly obvious.

What I can already tell is that in order to implement the Z-Machine in Python (or any language, for that matter), I’ll need to create data structures in the “interpreter’s private memory” — i.e., my Python logic — to represent the stack, routine call state, and program counter. Crucially, however, these data structures are separate from the bytes object that represents the memory map of the Z-Machine program.

So, at the bare minimum, any Z-Machine implementation must read in a zcode binary file. Yes, this is really low-hanging fruit here, folks, but let’s just keep it simple. I suspect it will get plenty complex as I move along. Here is how I started my initial implementation in my app.py module:

This is using the Click library to read in an argument and apply some defensive programming without me having to write code to check certain things, such as if the program file passed in actually exists or is readable. With this in place, Grue now requires a program file to be passed in.


poetryp run grue <zcode program>

Here the zcode program can be one of those test oracles I mentioned in the Grue context post. With that code in place, here’s what I started with:

Not too complicated. I open the program that is passed in for reading as a binary file. I then read the contents of that file and get the bytes object that I mentioned earlier. The tests for this logic are pretty simple:

For those reading this series just to understand how to build a Z-Machine implementation, note that I will periodically be including details on how I’m testing as well. That fits in with the theme of this blog in general but also, as you’ll see, some basic tests are a good idea to catch any regressions you might introduce.

This is pretty much the bare minimum to even get started on a working Z-Machine implementation. You have to be able to read in a zcode binary file and get that binary data. That binary data effectively provides you with the memory map you need.

In programming just about anything, you must decide how many abstractions you want in your code. It’s possible to overcomplicate things, but it’s also very easy not to follow good practices, such as having one function or method do too many things. There’s likely to be a lot to the memory handling implementation, so I decided to wrap the binary contents in a memory abstraction.

Should the ability to load a program be its own abstraction? Perhaps. Right now it’s a very simple action so I didn’t feel it necessary to break that out. If you’re following along or writing your own implementation, obviously you should do what you feel makes sense.

To handle this abstraction, I created a memory.py module.

The operative code then becomes this:

This is a small change but it’s good to see that the tests all still pass. This is, of course, one of the benefits of having tests that act as regression detectors. If you want to follow along with the implementation of Grue as described in this post, you can do the following if you have cloned the repo:


git checkout v0.0.2

In the next post, I’m going to talk a bit about state because that does tie into the memory discussed here.

Share

This article was written by Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.