Constant MEMORY_HEAP_SIZE -- what drives its value?

I’m helping someone with a project that is approaching memory limits for the Z-Machine (and it has to use Z-machine instead of Glulx). We’ve already activated memory economy, and it’s still managing to hit the overall memory limit – especially the readable memory limit.

The part that seems to be contributing the most to this is the addition of entries to a table with large text strings (up to 1800 characters). I can see that arrays are being added at the I6 level to represent the new rows, but these arrays don’t seem like they take up that much memory (maybe 30 bytes apiece). The strings are being parked in packed address memory as either constant strings or routines, and my understanding is that therefore the string sizes themselves shouldn’t affect the readable memory limit.

Cutting the table size down to the point where it will compile in the IDE shows almost 2K still available in readable memory, so it seems like it should be possible to add the requisite rows (about 12) without breaking the readable memory limit.

Right now, I’m looking at a constant generated by the I7 compiler called MEMORY_HEAP_SIZE. The first problem seems to occur when this value, which is determined by the compiler somehow, is increased from 16384 to 32768. This causes a compilation fault because of the line:

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Flex.i6t: The Heap
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

Array Flex_Heap -> MEMORY_HEAP_SIZE + 16; ! Plus 16 to allow room for head-free-block

At 32768, adding 16 breaks the signed integer limit for the Z-Machine, but increasing it to this amount also adds 16K to the readable memory limit because that’s where the heap lives. [Side note: since negative values for an array index don’t make sense, shouldn’t the compiler be treating this as an unsigned value? EDIT: Upon further consideration, it wouldn’t matter in the Z-Machine’s case, anyway, since a word array of size 32768 can’t be crammed into the available writable memory on that VM due to the presence of even minimum competing items such as class object definitions, and compiler 6.35 won’t allow an array size value higher than 32767.]

Manually modifying the value of MEMORY_HEAP_SIZE in the generated I6 code allows command line compilation of the generated I6 source, but it seems risky to do so. What is the basis by which the I7 compiler chooses the value for this constant?

2 Likes

I think the compiler is making a guess based on how many strings the game uses. Using a static string does not require heap memory, but modifying strings (concatenating them, etc) uses heap memory to store the new string text. Same goes for modifying dynamic lists.

It’s impossible to accurately predict how much heap memory a game will use, so the compiler estimates it as a fraction (or maybe multiple) of the total quantity of strings, lists, etc.

1 Like

So it’s not necessarily a definite need, just a power-of-two guess because the compiler can’t predict run-time needs? Am I correct in thinking that the only danger of manually modifying the value is the presumably increased chance of exhausting the heap, but that it’s not a given that this will happen? The showheap command shows most of the heap being free at start of runtime (less than 1K allocated).

I suppose there will be RTEs or other dysfunction if the heap is exhausted, so we’ll just try it for now. FYI for anyone running into this in the future, adding:

Include
(-

#Undef	MEMORY_HEAP_SIZE;
Constant	MEMORY_HEAP_SIZE = 16384;

-) before "The Heap" in "Flex.i6t".

allows redefinition of the constant, but note that any custom I6 code added before that point in the source will hardcode the value of the constant as decided by the compiler. (All observed uses of the constant in compiler-generated I6 code come after that point and pick up the new definition, however.)

Also, thank you for mentioning lists, zarf. The rows of the table in question each contain two lists of a new kind of value, so that is probably contributing. The array declarations for the lists seem to expand the readable memory requirement of each row by another 60 bytes or so. In some other posts, it is mentioned that declared “constant lists” are actually treated as list literals that can be expanded at run-time, so if the compiler is adding some extra padding to the heap for each one, that would explain how the relatively small number of table entries are causing the overflow.

That is correct.

A passage in WWI 20.2 Memory limitations discusses this in more detail. The use option dynamic memory allocation of at least seems to be linked to MEMORY_HEAP_SIZE indirectly, in that the latter is the former rounded up to the nearest power of two. I had seen this, but it didn’t seem to be directly related in the generated code – the compiler must take notice, however.

Whatever value one gives will be directly set to constant ‘DynamicMemoryAllocation’, e.g.:

Use dynamic memory allocation of at least 10000.

will result in:

! Use option:
 Constant DynamicMemoryAllocation = 10000; 

but will also result in:

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Output.i6t: Block Values
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

#ifdef TARGET_ZCODE;
Constant MEMORY_HEAP_SIZE = 16384;
#ifnot;
Constant MEMORY_HEAP_SIZE = 65536;
#endif;

due to rounding up.

The default value is 8192, and it seems like lesser values are ignored, which means that the only functionally available choices for Z-Machine are 8192 or 16384.

I didn’t think that DynamicMemoryAllocation and MEMORY_HEAP_SIZE were related because there’s no obvious connection in generated code. The most directly affected value is that for BLOCKV_STACK_SIZE – but only for Glulx.

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Output.i6t: Global Variables
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

...

#ifdef TARGET_ZCODE;
Constant BLOCKV_STACK_SIZE = 224;
#ifnot;
Constant BLOCKV_STACK_SIZE = DynamicMemoryAllocation/4;
#endif;

There’s reasons for i6 for I7 using “pure” powers of two for heap size or is possible to use sums of powers of two ? 10k is 8192+2048…

ok let’s pass this to Lord Nelson: implementing something similar to the -S switch for ni (that is, stop compilation after generating the i6 file, allowing hand-optimisation of the .i6t files) or add this to the TODO for the opensource ni (when is released…)

(what do a Naval historian AND programmer armed with a computer ? of course, everything related to ballistics, internal, external and terminal… hence my attention on code optimisation matters)

Best regards from Italy,
dott. Piergiorgio.

1 Like

The IDE is what’s responsible for running I6 as soon as ni has finished, but the generated I6 file is left behind. Nothing is stopping you from editing that file and re-running the I6 compiler by hand. (Well, except for the fact that you’d be wading through a source file that’s several megabytes large.)

To clarify what Adrian said: the ni compiler does stop after generating the I6 file.

You could modify a command-line script to insert an extra step between ni and inform6. (See thread: Command-line Inform 7: how to use ni, inform6, and cBlorb by CLI)

You could also do this by replacing the inform6 binary in the IDE with a shell script which modifies the source and then runs the real inform6.

(This is harder on Mac, though, because of app signing. Worst case, you could rebuild the IDE yourself: https://github.com/TobyLobster/Inform .)

1 Like

I put together an extension that allows definition of a dynamic memory amount of any size suitable to the VM. Notably, this means that:

  • if you are really short on memory for the Z-machine, you can allocate less than the standard minimum 8K to the heap
  • if you want to use a value above 16K, you can allocate more than the standard maximum 16K to the heap
  • if you want to use a value between 8K and 16K, you can allocate a non-standard amount in this range
  • for Z-machine, you can override the I7 compiler’s determination that an illegal minimum of 32K or above is needed (via a use option instead of an ugly I6 inclusion)

Initial testing seems promising. If anyone else is looking for something like this, let me know by PM.

4 Likes