We know the order of execution of the operations, and what some of them do. Now it's time to look at how they're actually implemented - the source code inside the interpreter that actually carries out print, +, and other operations.
The functions which implement operations are known as "PP Code" - "Push / Pop Code" - because most of their work involves popping off elements from a stack, performing some operation on it, and then pushing the result back. PP code can be found in several files: pp_hot.c contains frequently used code, put into a single object to encourage CPU caching; pp_ctl.c contains operations related to flow control; pp_sys.c contains the system-specific operations such as file and network handling; pack and unpack recently moved to pp_pack.c, and pp.c contains everything else.
We've already talked a little about the argument stack. The Perl interpreter makes use of several stacks, but the argument stack is the main one.
The best way to see how the argument stack is used is to watch it in operation. With a debugging build of Perl, the -Ds command line switch prints out the contents of the stack in symbolic format between operations. Here is a portion of the output of running $a=5; $b=10; print $a+$b;:
(-e:1) nextstate => (-e:1) pushmark => * (-e:1) gvsv(main::a) => * IV(5) (-e:1) gvsv(main::b) => * IV(5) IV(10) (-e:1) add => * IV(15) (-e:1) print => SV_YESAt the beginning of a statement, the stack is typically empty. First, Perl pushes a mark onto the stack to know when to stop pushing off arguments for print. Next, the values of $a and $b are retrieved and pushed onto the stack.
The addition operator is a binary operator, and hence, logically, it takes two values off the stack, adds them together and puts the result back onto the stack. Finally, print takes all of the values off the stack up to the previous bookmark and prints them out. Let's not forget that print itself has a return value, the true value SV_YES which it pushes back onto the stack.
Let's now take a look at one of the PP functions, the integer addition function pp_i_add. The code may look formidable, but it's a good example of how the PP functions manipulate values on the stack.
PP(pp_i_add) { dSP; dATARGET; tryAMAGICbin(add,opASSIGN); { dPOPTOPiirl_ul; SETi( left + right ); RETURN; } }
The _ul? Look up the definition in pp.h and work it out...
As you might have guessed, there are a number of macros for controlling what happens to the stack; these can be found in pp.h. The more common of these are:
Pop an SV off the stack and return it.
Pop a string off the stack and return it. (Note: requires a variable "STRLEN n_a" to be in scope.)
Pop an NV off the stack.
Pop an IV off the stack.
Return the top SV on the stack, but do not pop it. (The macros TOPpx, TOPn, etc. are analogous)
Return the penultimate SV on the stack. (There is no TOPm1px, etc.)
Push the scalar onto the stack; you must ensure that the stack has enough space to accommodate it.
Set the NV of the target to the given value, and push it onto the stack. PUSHi, etc. are analogous.
There is also an XPUSHs, XPUSHn, etc. which extends the stack if necessary.
This sets the top element of the stack to the given SV. SETn, etc. are analogous.
These declare a variable called sv, and either return the top entry from the stack or pop an entry and set sv to it.
These are similar, but declare a variable called value of the appropriate type. dTOPiv and so on are analogous.
In some cases, the PP code is purely concerned with rearranging the stack, and the PP function will call out to another function in doop.c to actually perform the relevant operation.