I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

MStack.i6t

MStack contents

The Memory Stack.

The M-Stack, or memory stack, is a sequence of frames, piled upwards. If we had an accessible stack in memory, we could use that, but neither the Z-machine nor Glulx has such a stack, alas, alas, alas. The following is not a very good solution, but it just about works.

15Constant MAX_MSTACK_FRAME = 2 + {-value:max_frame_size_needed}; 16Constant MSTACK_CAPACITY = 20; 17Constant MSTACK_SIZE = MSTACK_CAPACITY*MAX_MSTACK_FRAME; 18 19Array MStack --> MSTACK_SIZE; 20Global MStack_Top = 0; ! Topmost word currently used

Create Frame.

A frame is created by calling the following function with two arguments: creator, a function which initialises a block of variables, and an ID number identifying the owner.

The creator function is called with the address at which to initialise the variables as its first argument, and the value 1 as the second argument. (The idea is that the same function can be used later to deallocate the variables, and then the second argument will be -1.) The creator function returns the extent of the block of memory it has used, in words. Thus is required to be strictly less than MAX_MSTACK_FRAME minus 1.

36[ Mstack_Create_Frame creator id extent; 37    if (creator == 0) rfalse; 38    extent = creator.call(MStack_Top+2, 1); 39    if (extent == 0) rfalse; 40    if (MStack_Top + MAX_MSTACK_FRAME >= MSTACK_SIZE + 2) { 41        RunTimeProblem(RTP_MSTACKMEMORY, MSTACK_SIZE); 42        Mstack_Backtrace(); 43        rfalse; 44    } 45    MStack_Top++; 46    MStack-->MStack_Top = id; 47    MStack_Top++; 48    MStack_Top = MStack_Top + extent; 49    MStack-->MStack_Top = -(extent+2); 50    rtrue; 51];

Destroy Frame.

As sketched above, the same creator function and ID number are passed to the following routine to destroy the frame again. It takes the stack down to the level of the most recently created frame with this ID number: note that each action, for instance, has its own ID number for this purpose, but can be taking place several times in a nested fashion – one taking action might have caused another taking action which caused a third, for instance, so that there are three incomplete taking actions at once. In that case, there will be three independent sets of taking action variables on the M-stack, all with the same ID number. We remove the topmost one: the implication of that is that frames must always be destroyed in reverse order of creation.

In practice, I7 uses frames such that the frame sought should always be the topmost one in any case, and so that frames are always explicitly destroyed, not wiped by being undercut when an earlier-created frame is destroyed.

72[ Mstack_Destroy_Frame creator id pos; 73    pos = Mstack_Seek_Frame(id); 74    if (pos == 0) rfalse; ! Not found: do nothing 75    MStack_Top = pos - 2; ! Clear mstack down to just below this frame 76    if (creator) creator.call(pos, -1); 77    rtrue; 78];

Seek Frame.

We return the position on the M-stack of the most recently created frame with the given ID number (see above), or 0 if no such frame exists; the size is stored in the global variable MStack_Frame_Extent. (Because word 0 on the stack is used as a sentinel – all frames are placed above it – no frame can actually begin at word 0 on the stack, so 0 is safe to use as an exception.)

88Global MStack_Frame_Extent = 0; 89 90[ Mstack_Seek_Frame id pos; 91    pos = MStack_Top; 92    while ((pos > 0) && (MStack-->pos ~= 0)) { 93        MStack_Frame_Extent = MStack-->pos; 94        pos = pos + MStack_Frame_Extent; 95        MStack_Frame_Extent = (-2) - MStack_Frame_Extent; 96        if (MStack-->(pos+1) == id) return pos+2; 97    } 98    MStack_Frame_Extent = 0; 99    return 0; ! Not found 100];

Backtrace.

Purely for debugging purposes, and giving feedback if the stack runs out of memory:

107[ Mstack_Backtrace pos k; 108    print "Mstack backtrace: size ", MStack_Top+1, " words^"; 109    pos = MStack_Top; 110    while (MStack-->pos ~= 0) { 111        MStack_Frame_Extent = MStack-->pos; 112        pos = pos + MStack_Frame_Extent; 113        MStack_Frame_Extent = (-2) - MStack_Frame_Extent; 114        print "Block at ", pos+2, 115            " owner ID ", MStack-->(pos+1), " size ", MStack_Frame_Extent, "^"; 116        for (k=0: k<MStack_Frame_Extent: k++) print MStack-->(pos+2+k), " "; 117        print "^"; 118    } 119];

Access to Variables.

An M-stack variable is identified by a combination of ID number and offset: for instance ID 20007, offset 1, is the variable "room gone to" belonging to the going action. The following routine converts that into an address on the M-stack, in the topmost block with the given ID number (since "room gone to", for instance, always means its value in the most current going action of those now under way). Typechecking in the compiler should mean that it is impossible to produce either error message below: NI will only compile valid uses of MstVO ("M-stack variable offset") where the seek succeeds and the offset is within range.

133[ MstVO id off pos; 134    pos = Mstack_Seek_Frame(id); 135    if (pos == 0) { 136        print "Variable unavailable for this action, activity or rulebook: ", 137            "internal ID number ", 138            id, "/", off, "^"; 139        rfalse; 140    } 141    if ((off<0) || (off >= MStack_Frame_Extent)) { 142        print "Variable stack offset wrong: ", id, "/", off, " at ", pos, "^"; 143        rfalse; 144    } 145    return pos+off; 146];

Access to Nonexistent Variables.

A long-standing point where I7 is not as strict in type-checking as it might be occurs when checking rule preambles like "Before going to a dead end...". Such a preamble must be checked whatever the current action is – in many cases, it will not be a going action at all; which means that "room gone to", a value implied by the "to" clause, will not exist. If the type-checking were stricter, it would be a nuisance for authors, and instead we relax a little by accessing such variables using a more forgiving routine. Here, if a variable does not exist, we return 0 to mean that it can be read at M-stack position 0: this is the sentinel word, which is not part of any frame, and which contains 0. Thus the variable reads as if it is 0, the default for the kind of value "object", which is the KOV for action variables such as "room gone to".

The routine may only be used where the variable is being read, and never where it is to be written, of course: that would corrupt the sentinel.

166[ MstVON id off pos; 167    pos = Mstack_Seek_Frame(id); 168    if (pos == 0) { 169        return 0; ! word position 0 on the M-stack 170    } 171    if ((off<0) || (off >= MStack_Frame_Extent)) { 172        print "Variable stack offset wrong: ", id, "/", off, " at ", pos, "^"; 173        rfalse; 174    } 175    return pos+off; 176];

Rulebook Variables.

Each rulebook has a slate of variables, usually empty, with ID number the same as the rulebook's own ID number. (Rulebook IDs number upwards from 0 in order of creation in the source text.) The associated creator functions, usually null, are stored in an array if there is no problem about memory usage, but with a switch statement if MEMORY_ECONOMY is in force; this costs a very small amount of time, but saves 1K of readable memory.

187{-array:Rulebooks::rulebook_var_creators} 188 189[ MStack_CreateRBVars rb cr; 190{-call:Rulebooks::rulebook_var_creators_lookup} 191    if (cr == 0) return; 192    Mstack_Create_Frame(cr, rb); 193]; 194 195[ MStack_DestroyRBVars rb cr; 196{-call:Rulebooks::rulebook_var_creators_lookup} 197    if (cr == 0) return; 198    Mstack_Destroy_Frame(cr, rb); 199];

Activity Variables.

Exactly the same goes for activity variables except that here the ID number is 10000+N, where N is the allocation ID of the activity. (This would fail if there were more than 10,000 rulebooks, but this is very difficult to see happening.)

208{-array:Activities::activity_var_creators} 209 210[ MStack_CreateAVVars av cr; 211    cr = activity_var_creators-->av; 212    if (cr == 0) return; 213    Mstack_Create_Frame(cr, av + 10000); 214]; 215 216[ MStack_DestroyAVVars av cr; 217    cr = activity_var_creators-->av; 218    if (cr == 0) return; 219    Mstack_Destroy_Frame(cr, av + 10000); 220];