Main Routine and Local Variables (Z-Machine Specification Remark)

I’m working with a class where we’re implementing a Z-Machine from scratch.

The project is now here: https://github.com/jeffnyman/quendor

As we go through this, we’re trying to document the code via what we read in the specification. One thing that was interesting was a remark in the specification that says:

“Inform’s “main” routine is required not to have local variables and has to be the first defined routine.”

One of our sample files, however, is this:

[ Main result value;
  value = 2;
  result = 3 + value -1;
]

Unless I’m very much mistaken, that’s a Main routine with two local variables.

So it’s not clear what the spec is referring to. But it is interesting because if you look at txd output for that simple example (), you see:

Main routine 496, 0 locals ()

  497:  call            49e -> gef
  49c:  quit            

Routine 49e, 2 locals (0000, 0000)

Clearly the 49e (with two locals) is the Main from the Inform source text. So the “Main routine 496” is some other main routine.

I’m just curious if I’m reading this correctly.

You are indeed reading it correctly!

According to the spec, the main routine can’t have local variables and has to be the first one defined, which is a bit annoying and limiting.

So Inform actually adds in a different main routine, which looks something like this:

[ Main__ ;
    Main();
    @quit;
]

Now Main is just a normal routine which can have as many locals as you want.

2 Likes

Mmmm, that’s sneaky. I like it!

Where are you getting the Z-Machine spec from? I’m pretty sure I removed that remark about Inform’s main routine when I updated the spec back in 2014.

1 Like

I’m pretty sure I removed that remark about Inform’s main routine when I updated the spec back in 2014.

You are correct. The old line is preserved in http://inform-fiction.org/zmachine/standards/z1point0-1997/sect05.html .

The Inform 6 compiler still does the Main__() thing under the hood, but nobody needs to worry about it except compiler maintainers.

Ah, oops, I got the name wrong; I was only half-remembering the veneer layer. Interesting that it’s not needed any more!

Do any interpreters still enforce the “main routine can’t have locals” thing? Or was that never really a thing to begin with?

Except for Version 6, the Z-Machine doesn’t have an initial routine. Execution just starts at a point specified in the header, with no local variables available.

Inform’s convention of creating a Main__() routine is mostly a matter of compiler tidiness. It doesn’t need special-case (compiler) code to generate executable (Z-machine) code outside a routine wrapper.

The fact that Main__() doesn’t have local variables is meant to reflect the reality that… what David just said. :)

Besides the fact that there’s no way to specify the number of locals or their defaults, since the header points to an instruction instead of a routine… the Quetzal save file format also specifies that the first call frame is written with zeroes for everything except the stack depth, so if you return enough times after restoring, you’ll end up in an initial frame with no locals.

True, but it would make some sense to allow writing to locals in those first instructions; reading from them before writing could be UB or could always return zero. (It also makes sense to disallow it and crash if it’s used; I’m just curious what the existing interpreters have done.)

Each routine defines the number of local variables it has. Since the execution starts without a routine call, there are no locals defined. An interpreter that allowed reading and writing to locals that don’t exist would encourage creation of games that completely fail on other terps. I can’t absolutely guarantee that no interpreters allow the use of undefined variables, but it would definitely be non-standard behaviour, and a bad idea.

1 Like