Texts, lists and other flexibly-sized structures make use of a pool of run-time storage called "the heap".
§1. Though we call it a "heap", the layout and policy of how this memory is handled at runtime is not our business here: all of that is delegated to kit-defined code in Flex (in BasicInformKit).
This means the Inform compiler itself can be blissfully ignorant of all that, though it does need to decide how much memory to allocate:
int total_heap_allocation = 0; void TheHeap::ensure_basic_heap_present(void) { total_heap_allocation += 256; enough for the initial free-space block }
§2. By now, we know that we need at least total_heap_allocation bytes on the heap, but the initial heap size has to be a power of 2, so we compute the smallest such which is big enough. On Glulx, we then multiply by 4: one factor of 2 is because the word size is twice as much — words are 4-byte, not 2-byte as on the Z-machine — while the other is, basically, because we can, and because we want to store text in particular using 2-byte characters (capable of storing Unicode) rather than 1-byte characters as on the Z-machine. Glulx has essentially no memory constraints compared with the Z-machine.
void TheHeap::compile_configuration(void) { int max_heap = 1; if (total_heap_allocation < global_compilation_settings.dynamic_memory_allocation) total_heap_allocation = global_compilation_settings.dynamic_memory_allocation; while (max_heap < total_heap_allocation) max_heap = max_heap*2; inter_name *iname = Hierarchy::find(MEMORY_HEAP_SIZE_HL); if (TargetVMs::is_16_bit(Task::vm())) Emit::numeric_constant(iname, (inter_ti) max_heap); else Emit::numeric_constant(iname, (inter_ti) (4*max_heap)); Hierarchy::make_available(iname); LOG("Providing for a total heap of %d, given requirement of %d\n", max_heap, total_heap_allocation); }
§3. The following should be called when we want to create a block value, such as a text or list, which can be used either globally or temporarily when a function runs. For more on the latter, see Stack Frames (in imperative).
It increases the heap size estimate accordingly, and returns a convenient structure to describe the new value — recording its kind and its position in the local M-stack frame. The stack_offset should be -1 for a global value, which is then not stored on the stack.
typedef struct heap_allocation { struct kind *allocated_kind; int stack_offset; } heap_allocation;
- The structure heap_allocation is private to this section.
§4. We want to make an estimate of the likely size needs of such a value if placed on the heap — its exact size needs if it is fixed in size, and a reasonable overestimate of typical usage if it is flexible.
The multiplier is used when we need to calculate the size of, say, a list of 20 texts; it would then, of course, be 20. Any stack_offset is simply passed through to the returned record; we don't understand any of that here.
heap_allocation TheHeap::make_allocation(kind *K, int multiplier, int stack_offset) { if (Kinds::Behaviour::uses_block_values(K) == FALSE) internal_error("unable to allocate heap storage for this kind of value"); int estimate = 2 + Kinds::Behaviour::get_short_block_size(K); if (Kinds::Behaviour::get_flexible_long_block_size(K) > 0) estimate += Kinds::Behaviour::get_flexible_long_block_size(K); else estimate += Kinds::Behaviour::get_long_block_size(K); total_heap_allocation += (estimate + 8)*multiplier; heap_allocation ha; ha.allocated_kind = K; ha.stack_offset = stack_offset; return ha; }
§5. That is usually followed quickly by call to this function, which compiles runtime code to create the value:
void TheHeap::emit_allocation(heap_allocation ha) { if (ha.stack_offset >= 0) { inter_name *iname = Hierarchy::find(CREATEPVONSTACK_HL); EmitCode::call(iname); EmitCode::down(); EmitCode::val_number((inter_ti) ha.stack_offset); RTKindIDs::emit_strong_ID_as_val(ha.allocated_kind); EmitCode::up(); } else { inter_name *iname = Hierarchy::find(CREATEPV_HL); EmitCode::call(iname); EmitCode::down(); RTKindIDs::emit_strong_ID_as_val(ha.allocated_kind); EmitCode::up(); } }
§6. It's not quite true that the Inform compiler knows nothing about the structure of data managed at runtime, because it does have to compile suitable constant lists, texts and such. Those occur in a "block" whose header conforms to the following format, but which then continues differently according to the kind of value being stored: see Enclosures for more.
These constants, and the logic below, must therefore match the understandings in Flex (in BasicInformKit).
define BLK_FLAG_MULTIPLE 0x00000001 define BLK_FLAG_WORD 0x00000004 define BLK_FLAG_RESIDENT 0x00000008 define BLK_FLAG_TRUNCMULT 0x00000010
void TheHeap::emit_block_value_header(kind *K, int individual, int size) { if (individual == FALSE) EmitArrays::numeric_entry(0); int n = 0, c = 1, w = 4; if (TargetVMs::is_16_bit(Task::vm())) w = 2; while (c < (size + 3)*w) { n++; c = c*2; } int flags = BLK_FLAG_RESIDENT + BLK_FLAG_WORD; if (Kinds::get_construct(K) == CON_list_of) flags += BLK_FLAG_TRUNCMULT; if (Kinds::get_construct(K) == CON_relation) flags += BLK_FLAG_MULTIPLE; if (TargetVMs::is_16_bit(Task::vm())) EmitArrays::numeric_entry((inter_ti) (0x100*n + flags)); else EmitArrays::numeric_entry((inter_ti) (0x1000000*n + 0x10000*flags)); EmitArrays::iname_entry(RTKindIDs::weak_iname(K)); EmitArrays::iname_entry(Hierarchy::find(MAX_POSITIVE_NUMBER_HL)); }
§7. And this is a simpler version used for short block constants where no long block exists at all.
define BLK_BVBITMAP_CONSTANT 0x40 define BLK_BVBITMAP_SBONLY 0x80
void TheHeap::emit_short_block_only_value_header(kind *K) { inter_ti flags = BLK_BVBITMAP_CONSTANT + BLK_BVBITMAP_SBONLY; EmitArrays::numeric_entry(flags); EmitArrays::iname_entry(RTKindIDs::weak_iname(K)); }