The history of functions¶
There are some specialized instructions to make function abstractions
easier to express in machine code, but under the hood it is all still
jumps. In the olden days (say, before the 1970s), every ‘subroutine’
had its own set of essentially global variables set aside at compile
time for its recordkeeping, like in the recent assignments where the
‘input’ and ‘output’ were just space set aside on the data or
bss segment. To ‘call’ such a subroutine, another piece of code puts
the right values in the ‘input’ places (mov
) and then jumps
to the first instruction of the subroutine (jmp
) and it’s almost
that simple.
Almost, because there is one other piece of information every subroutine absolutely must know: where to jump back when it is done. So when setting up the inputs, there also has to be one for the ‘return address’, i.e. the address of the next instruction in the calling code right after the jump to subroutine. The subroutine gives its results back by saving them into the prearranged ‘output’ places and jumping to that saved return address.
The whole collection of input locations, saved return address, output locations, and also any local storage for temporary working scratch variables, for a given subroutine is called its ‘frame’ or ‘activation record’. This old way of doing things, where the frame is set aside at compile time, was adequate for a lot of purposes but isn’t enough for everything a function call must be able to do—in particular, there’s only space for one copy of each subroutine to run at a time. Nonetheless, I want you to have a sense that all that is happening is setting up this frame and jumping in and then back out, because all that’s different now is where we put the frames.