6.5 Stacks and Register Frames
Parrot provides 32 registers of each type: integer, floating-point number, string, and PMC. This is a generous number of registers, but it's still too restrictive for the average use. You can hardly limit your code to 32 integers at a time. This is especially true when you start working with subroutines and need a way to store the caller's values and the subroutine's values. So, Parrot also provides stacks for storing values outside the 32 registers. Parrot has seven basic stacks, each used for a different purpose: the user stack, the control stack, the integer stack, and the four register backing stacks.
6.5.1 User Stack
The user stack, also known as the general-purpose stack, stores individual values. The two main opcodes for working with the user stack are save, to push a value onto the stack, and restore, to pop one off the stack:
save 42 # push onto user stack restore I1 # pop off user stack
The one argument to save can be either a constant or a register. The user stack is a typed stack, so restore will only pop a value into a register of the same type as the original value:
save 1 set I0, 4 restore I0 print I0 # prints 1 end
If that restore were restore N0 instead of an integer register, you'd get an exception, "Wrong type on top of stack!"
A handful of other instructions are useful for manipulating the user stack. rotate_up rotates a given number of elements on the user stack to put a different element on the top of the stack. The depth opcode returns the number of entries currently on the stack. The entrytype opcode returns the type of the stack entry at a given depth, and lookback returns the value of an element at the given depth without popping the element off the stack:
save 1 save 2.3 set S0, "hi\n" save S0 save P0 entrytype I0, 0 print I0 # prints 4 (PMC) entrytype I0, 1 print I0 # prints 3 (STRING) entrytype I0, 2 print I0 # prints 2 (FLOATVAL) entrytype I0, 3 print I0 # prints 1 (INTVAL) print "\n" depth I2 # get entries print I2 # prints 4 print "\n" lookback S1, 1 # get entry at depth 1 print S1 # prints "hi\n" depth I2 # unchanged print I2 # prints 4 print "\n" end
This example pushes four elements onto the user stack: an integer, a floating-point number, a string, and a PMC. It checks the entrytype of all four elements and prints them out. It then checks the depth of the stack, gets the value of the second element with a lookback, and checks that the number of elements hasn't changed.
6.5.2 Control Stack
The control stack, also known as the call stack, stores the addresses for returning from subroutine calls. There are no instructions for directly manipulating the control stack, only calls to subroutines and returns from them. These opcodes are described in Section 6.7 later in this chapter.
6.5.3 Integer Stack
The integer stack, also known as the high-speed intstack, is a stack for storing integers. It's much faster than the general-purpose stack, because its operations are streamlined for a single, simple data type. The integer stack opcodes are intsave to push a value onto the integer stack, intrestore to pop a value off, and intdepth to get the current depth of the integer stack.
6.5.4 Register Frames
The final set of stacks are the register backing stacks. Instead of saving and restoring individual values, they work with register frames. Each register frame is the full set of 32 registers for one type. Parrot has four backing stacks, one for each type of register. The backing stacks are commonly used for saving the contents of all the registers before a subroutine call, so they can be restored when control returns to the caller.
PASM has five opcodes for storing register frames, one for each register type and one that saves all four at once:
pushi # copy I-register frame pushn # copy N-register frame pushs # copy S-register frame pushp # copy P-register frame saveall # copy all register frames
Each pushi, pushn, pushs, or pushp pushes a register frame containing all the current values of one register type onto the backing stack of that type. saveall simply calls pushi, pushn, pushs, and pushp.
There are also five opcodes to restore register frames:
popi # restore I-register frame popn # restore N-register frame pops # restore S-register frame popp # restore P-register frame restoreall # restore all register frames
The popi, popn, pops, and popp opcodes pop a single register frame off a particular stack and replace the values in all 32 registers of that type with the values in the restored register frame. restoreall calls popi, popn, pops, and popp, restoring every register of every type to values saved earlier.
set I0, 1 print I0 # prints 1 pushi # copy away I0..I31 print I0 # unchanged, still 1 inc I0 print I0 # now 2 popi # restore registers to state of previous pushi print I0 # old value restored, now 1 print "\n" end
This example sets the value of I0 to 1 and stores the complete set of integer registers. Before I0 is incremented, it has the same value as before the pushi.
PASM also has opcodes to clear a register frame called cleari, clearn, clears, and clearp. These reset the numeric registers to 0 values and the string and PMC registers to null pointers, which is the same state they have when the interpreter first starts.
# ... coming from a subroutine save I5 # Push some registers save I6 # holding the return values save N5 # of the sub. restoreall # restore registers to state before calling subroutine restore N0 # pop off last pushed restore I0 # pop 2nd restore I1 # and so on