From: Dave Mitchell Date: Tue, 3 May 2005 22:10:45 +0000 (+0000) Subject: document the internals of exception handling X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=dfc98234fc15f52c0776048cec291c9d1b2dec2b;p=p5sagit%2Fp5-mst-13.2.git document the internals of exception handling p4raw-id: //depot/perl@24381 --- diff --git a/pod/perlhack.pod b/pod/perlhack.pod index 78226bd..83e16d1 100644 --- a/pod/perlhack.pod +++ b/pod/perlhack.pod @@ -866,6 +866,157 @@ implement control structures (C, C and the like) and F contains everything else. These are, if you like, the C code for Perl's built-in functions and operators. +Note that each C function is expected to return a pointer to the next +op. Calls to perl subs (and eval blocks) are handled within the same +runops loop, and do not consume extra space on the C stack. For example, +C and C just push a C or C block +struct onto the context stack which contain the address of the op +following the sub call or eval. They then return the first op of that sub +or eval block, and so execution continues of that sub or block. Later, a +C or C op pops the C or C, +retrieves the return op from it, and returns it. + +=item Exception handing + +Perl's exception handing (ie C etc) is built on top of the low-level +C/C C-library functions. These basically provide a +way to capture the current PC and SP registers and later restore them; ie +a C continues at the point in code where a previous C +was done, with anything further up on the C stack being lost. This is why +code should always save values using C rather than in auto +variables. + +The perl core wraps C etc in the macros C and +C. The basic rule of perl exceptions is that C, and +C (in the absence of C) perform a C, while +C within C does a C. + +At entry points to perl, such as C, C and +C each does a C, then enter a runops +loop or whatever, and handle possible exception returns. For a 2 return, +final cleanup is performed, such as popping stacks and calling C or +C blocks. Amongst other things, this is how scope cleanup still +occurs during an C. + +If a C can find a C block on the context stack, then the +stack is popped to that level and the return op in that block is assigned +to C; then a C is performed. This normally +passes control back to the guard. In the case of C and +C, a non-null C triggers re-entry to the runops +loop. The is the normal way that C or C is handled within an +C. + +Sometimes ops are executed within an inner runops loop, such as tie, sort +or overload code. In this case, something like + + sub FETCH { eval { die } } + +would cause a longjmp right back to the guard in C, popping both +runops loops, which is clearly incorrect. One way to avoid this is for the +tie code to do a C before executing C in the inner +runops loop, but for efficiency reasons, perl in fact just sets a flag, +using C. The C, C and +C ops check this flag, and if true, they call C, +which does a C and starts a new runops level to execute the +code, rather than doing it on the current loop. + +As a further optimisation, on exit from the eval block in the C, +execution of the code following the block is still carried on in the inner +loop. When an exception is raised, C compares the C +level of the C with C and if they differ, just +re-throws the exception. In this way any inner loops get popped. + +Here's an example. + + 1: eval { tie @a, 'A' }; + 2: sub A::TIEARRAY { + 3: eval { die }; + 4: die; + 5: } + +To run this code, C is called, which does a C then +enters a runops loop. This loop executes the eval and tie ops on line 1, +with the eval pushing a C onto the context stack. + +The C does a C, then starts a second runops loop +to execute the body of C. When it executes the entertry op on +line 3, C is true, so C calls C which +does a C and starts a third runops loop, which then executes +the die op. At this point the C call stack looks like this: + + Perl_pp_die + Perl_runops # third loop + S_docatch_body + S_docatch + Perl_pp_entertry + Perl_runops # second loop + S_call_body + Perl_call_sv + Perl_pp_tie + Perl_runops # first loop + S_run_body + perl_run + main + +and the context and data stacks, as shown by C<-Dstv>, look like: + + STACK 0: MAIN + CX 0: BLOCK => + CX 1: EVAL => AV() PV("A"\0) + retop=leave + STACK 1: MAGIC + CX 0: SUB => + retop=(null) + CX 1: EVAL => * + retop=nextstate + +The die pops the first C off the context stack, sets +C from it, does a C, and control returns to +the top C. This then starts another third-level runops level, +which executes the nextstate, pushmark and die ops on line 4. At the point +that the second C is called, the C call stack looks exactly like +that above, even though we are no longer within an inner eval; this is +because of the optimization mentioned earlier. However, the context stack +now looks like this, ie with the top CxEVAL popped: + + STACK 0: MAIN + CX 0: BLOCK => + CX 1: EVAL => AV() PV("A"\0) + retop=leave + STACK 1: MAGIC + CX 0: SUB => + retop=(null) + +The die on line 4 pops the context stack back down to the CxEVAL, leaving +it as: + + STACK 0: MAIN + CX 0: BLOCK => + +As usual, C is extracted from the C, and a +C done, which pops the C stack back to the docatch: + + S_docatch + Perl_pp_entertry + Perl_runops # second loop + S_call_body + Perl_call_sv + Perl_pp_tie + Perl_runops # first loop + S_run_body + perl_run + main + +In this case, because the C level recorded in the C +differs from the current one, C just does a C +and the C stack unwinds to: + + perl_run + main + +Because C is non-null, C starts a new runops loop +and execution continues. + =back =head2 Internal Variable Types