1286 1570 1743 1771 1990 1844 1604 1727 1464 1790 1268 1061 1807 1541 1964 1407 1116 1079 1509 1628 1828 1056 1827 1937 1164 1733 1833 1615 1830 1314 1333 1172 1357 1282 1603 1180 1027 1477 1815 1108 1272 1049 1586 1527 1497 1509 1806 1447 1574 1177 1947 1469 1534 1517 1301 1360 1714 1733 1013 1749 1139 1540 1834 1271 1049 1109 1657 1432 1546 1363 1638 1655 1486 1632 1197 1154 1743 1850 1593 1527 1227 1072 1769 1590 1352 1984 1980 1051 1802 1958 1762 1384 1405 1007 1612 1640 1432 1438 1883 Stack Machines: Stack Frames | PHPnews.io

PHPnews.io

Stack Machines: Stack Frames

Written by igorw / Original link on Aug. 24, 2019

Stack Machines: Stack Frames

fundamentals << rpn-calculator << shunting-yard << io << jumps << conditionals << comments << calls << variables << stack-frames << heap << compilers

The last two posts introduced two separate concepts: calls and variables. The variables so far are global. We will now see that by applying lessons learned from procedure calls, variables can be made local!

Local variables

What does it mean to have locally scoped variables, and why is that desirable?

Locally scoped variables are unaffected by procedure calls. This makes understanding a procedure easier, because there is sufficient isolation between it and the rest of the system.

It comes down to reasoning about effects.

Call stack

The call stack has been defined as a place where return addresses are stored. Every time a procedure is called, the instruction pointer $ip is backed up into the call stack. On ret, that instruction pointer is restored.

You might already be familiar with the call stack from a few places. For example, if you have an unbounded recursive call, you will produce a stack overflow in most languages. This just means you have exceeded the maximum size of the call stack.

Another common place where the call stack is visible is when dealing with Exceptions, as it is very common for them to include a stack trace. A stack trace is just a visualization of the call stack. And since the call stack has return addresses, it is a log of how you got to your current location.

trace.png

Frames

So far we have defined the execution context to just be $ip. But we can extend that definition.

In addition to $ip, it is possible to backup other parts of the execution context onto the call stack when performing a call, that can be restored later on. For example, the variables!

To group those values on the call stack, we put them into a box. That box is called a stack frame, and it looks like this:

frame.png

Implementation

Implementation is simply a matter of adding $vars next to the instruction pointer on the call stack.

Backing up values when performing a call:

if (preg_match('/^call\((.+)\)$/', $op, $match)) {
    $label = $match[1];
    $calls->push([$ip, $vars]);
    $ip = $labels[$label];
    continue;
}

To prevent the called procedure from having access to the vars of its caller, it might be a good idea to reset them to an empty hash map as part of the call instruction:

$vars = [];

Restoring values when returning from a call:

switch ($op) {
    // ...
    case 'ret':
        list($ip, $vars) = $calls->pop();
        break;
}

Example

And here is an example program that verifies the locality of variables:

jmp(start)

label(foo)
    1 !var(i)
    ret

label(start)
    0 !var(i)
    call(foo)
    var(i) .num

It prints 0, showing that the local store in foo had no effect on the main execution scope.

money.gif

Summary

The call stack holds a trace of stack frames, which contain instruction pointers representing execution contexts, and all the state necessary to restore those contexts.

fundamentals << rpn-calculator << shunting-yard << io << jumps << conditionals << comments << calls << variables << stack-frames << heap << compilers

igor

« Stack Machines: Heap - Stack Machines: Variables »