Squashed globals in Z-code

As part of the ongoing “make z3 games as small as possible” retro effort, @heasm66 proposed an Inform 6 compiler option to squash array space down into the global variables segment if the game doesn’t use all 240 global variables.

ZCODE_COMPACT_GLOBALS, only allocate space for actually used globals · Issue #306 · DavidKinder/Inform6 · GitHub

That is, if the game only uses 32 global variables (let’s say, at addresses $300 to $340), then the first array could start at $340, rather than wasting all the space from $340 to $4E0.

(This requires rearranging how I6 uses globals, but whatever, small matter of programming.)

Henrik says:

There’s precedence for this, for example, look at Hitchhiker’s Guide, r31, 871119 (Solid Gold) that only use 364, istead of 480, bytes for the globals.

So that’s fine as far as it goes. However, when I tested the change with a very small game, I ran into problems:

Global vv1;
Global vv2;
Global vv3;
Global vv4;

Array arr --> 4;

[ Main; ];

Runs in Frotz, but Bocfel (and Gargoyle) throws a consistency check error:

[Fatal error: corrupted story: global variables are not in dynamic memory]

The problem is that there’s only four globals and eight bytes of array space. So the static memory range (ROM) begins inside what the spec says is the global variables segment, even though the compiler knows that it will never touch globals past the fourth one.

The HHGG Solid Gold game file doesn’t have this problem, to be clear. It only uses 182 global variables but it then has plenty of arrays and other RAM data. So Bocfel’s check doesn’t fail. I’m sure the same would be true of any PunyInform game. The error only appears for very small test games.

I guess there’s a few ways we could think about this.

  • (1) “This compiler option is stupid, don’t implement it.”
  • (2) “Bocfel’s check is a bug; it should be removed.”
  • (3) “Very compact Z-code files are meant for retro platforms, so if the game file runs on Ozmoo, there’s no problem.”
  • (4) “The compiler should show a warning if you get into this situation. Then the author can decide whether to proceed.”
  • (5) “The compiler should nudge up the static memory limit to avoid this situation.”
2 Likes

I think Bocfel’s behaviour is correct, and I’d vote for option 5.

The Z-Machine has 240 globals, meaning 480 bytes of dynamic memory. It’s okay (if kinda risky) for some of the globals to be used for other things. But if there isn’t any other data to put in dynamic memory, then it would need to be padded.

If globals aren’t the first thing after the header then moving them there might help allow for more compaction.

2 Likes

For some precedent, the Dialog Z-machine compiler does option 5: if the end of RAM is too small for 240 globals, it raises the start of ROM to be above that line.

Thanks, that’s helpful. I also feel like 5 is the right plan.

I agree that 5 is the way to go. In the linked issue Dannii does note that the standard says (in §1.1) dynamic memory must be at least 64 bytes (implying it could be only 64 bytes and thus not large enough for any globals), but §6.2 is quite clear that the global table is 480 bytes and in dynamic memory. These are contradictory but I do think §6.2 feels “more normative”, if that makes any sense.

From a practical standpoint, too, as Zarf alluded to in the linked issue: I imagine there will be people using older Gargoyles for a while, so even if I removed the check, it wouldn’t help until everybody’s upgraded.

2 Likes

If allowing less than 240 globals, it would be useful to write the total somewhere to aid interpreters that want to expose globals in some way.

The total will always be

(#array__start-#globals_array)/WORDSIZE

…which is a hacky answer but it will work.

If you mean make it visible somewhere in the raw game file, there isn’t really anywhere to do that. You’d have to rely on the gameinfo.dbg file, which describes everything about the game file layout.

I think this is a worthwile effort, and I agree that option 5 sounds best…

While making z3 games as small as possible is mainly about getting games to run better on retro platforms, it’s important that they work fine on modern platforms as well.

First: I also vote for option 5.

Some analysis: Compiling the minimal.inf (one room with one object) with the PunyInform library shows that the library defines 104 globals and 874 bytes for arrays (buffers and stuff). The Standard Library will be something similiar. So, not a big problem in normal use.

Zilf always compact the globals, but places the globals between the abbreviation table and the object table, so the issue never happen.

But rearranging the order in which Inform output the tables is too big of a change so I vote for solution 5.

I made a run through all game files on The Obsessively Complete Infocom Catalog and these files all uses compact globals.

abyss-r1-s890320.z6
arthur-r40-s890502.z6
arthur-r41-s890504.z6
arthur-r54-s890606.z6
arthur-r63-s890622.z6
arthur-r74-s890714.z6
beyondzork-beta-r1-s870715.z5
beyondzork-r47-s870915.z5
beyondzork-r49-s870917.z5
beyondzork-r51-s870923.z5
beyondzork-r57-s871221.z5
beyondzork-r60-s880610.z5
borderzone-r9-s871008.z5
hitchhiker-invclues-r31-s871119.z5
journey-dev-r142-s890205.z6
journey-dev-r46-s880603.z5
journey-r10-s890313.z6
journey-r11-s890304.z6
journey-r2-s890303.z6
journey-r26-s890316.z6
journey-r3-s890310.z6
journey-r30-s890322.z6
journey-r5-s890310.z6
journey-r51-s890522.z6
journey-r54-s890526.z6
journey-r76-s890615.z6
journey-r77-s890616.z6
journey-r79-s890627.z6
journey-r83-s890706.z6
leathergoddesses-invclues-r4-s880405.z5
minizork-r34-s871124.z3
minizork2-r2-s871123.z3
planetfall-r39-s880501.z3
plunderedhearts-r26-s870730.z3
restaurant-r15-s880512.z5
restaurant-r184-s890412.z6
sherlock-dev-r97-s871026.z5
shogun-r278-s890209.z6
shogun-r278-s890211.z6
shogun-r279-s890217.z6
shogun-r280-s890217.z6
shogun-r281-s890222.z6
shogun-r282-s890224.z6
shogun-r283-s890228.z6
shogun-r284-s890302.z6
shogun-r286-s890306.z6
shogun-r288-s890308.z6
shogun-r289-s890309.z6
shogun-r290-s890311.z6
shogun-r291-s890313.z6
shogun-r292-s890314.z6
shogun-r295-s890321.z6
shogun-r311-s890510.z6
shogun-r320-s890627.z6
shogun-r321-s890629.z6
shogun-r322-s890706.z6
wishbringer-invclues-r23-s880706.z5
zork0-alpha-r96-s880224.z5
zork0-beta-r242-s880830.z6
zork0-demo-r366-s890323.z6
zork0-demo-r393-s890714.z6
zork0-ibm-r392-s890714.z6
zork0-oldparser-r1-s871030.z5
zork0-prealpha-r74-s880114.z5
zork0-r153-s880510.z5
zork0-r242-s880901.z6
zork0-r296-s881019.z6
zork0-r343-s890217.z6
zork0-r366-s890323.z6
zork0-r383-s890602.z6
zork0-r387-s890612.z6
zork0-r393-s890714.z6
zork0-r66-s890111.z6
zork1-german-beta-r3-s880113.z5
zork1-german-r15-s890613.z6
zork1-invclues-r52-s871125.z5
zork1-r119-s880429.z3

My guess is that Infocom made this change in Zilch 1987 to fit Mini Zork on a C64 cassette.

I think the wording for §6.2 need to change a little for version 1.2 (if it is still a work in progress…)

6.2

Global variables (variable numbers $10 to $ff) are stored in a table in the Z-machine’s dynamic memory, at a byte address given in word 6 of the header. The table consists of 240 2-byte words and the initial values of the global variables are the values initially contained in the table. (It is legal for a program to alter the table’s contents directly in play, though not for it to change the table’s address.)

1 Like

I think that Bocfel is being overly pedantic here (my interpreter is a lot more lax here, perhaps too much…)

That said, compilers definitely should be conversative in what they produce, as there are numerous interpreters out there, so option (5) is a good way to go.