Team LiB   Previous Section   Next Section

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.[9]

[9] It also has a pad stack, which we'll discuss in Section 6.6.

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.

Saving a register frame to the backing stack doesn't alter the values stored in the registers; it simply copies the values:

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.

The user stack can be useful for holding onto some values that would otherwise be obliterated by a restoreall:

# ... 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
    Team LiB   Previous Section   Next Section