Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

questions about saving stacks

3 views
Skip to first unread message

Kamal Khatri

unread,
Dec 1, 2011, 5:08:38 PM12/1/11
to perl5-...@perl.org
Hi all,

(Sorry about the previous incomplete post.)

I was looking at techniques to save perl stacks and restore them. I have looked at perlguts (and other docs)
and code back and forth for a while now. So far as I understand, and looking at the implementation
of Perl_init_stacks(pTHX), perl seems to use 6 separate stacks (PL_stack_sp, cxstack,  PL_tmps_stack,
PL_markstack, PL_scopestack, PL_savestack). I also noticed each of these stacks have max and index
markers (with floor marker for tmp stack).

What would be the right way to save these stacks so that they could be restored again. Currently
I am using the following techniques. However, I suspect I am not correctly handling saving
of the Perl data types. This results in unpredictable crashes.

1. PL_stack_sp

     for(i=0; i < (PL_stack_sp - PL_stack_base); i++)
         copy using newSVsv(*(PL_stack_base + i)); however this does not seems to work for SVt_PVCV

2.  cxstack
    
     for(i=0; i <= PL_curstackinfo->si_cxix ; i++)
           memcpy &cxstack[i]

3. PL_tmps_stack
 
     for(i=PL_tmps_floor+1; i <= PL_tmps_ix; i++)
            copy using newSVsv(PL_tmps_stack[i]); this also does not work for SVt_PVCV

4. PL_markstack

      memcpy(dest_markstack, PL_markstack, (PL_markstack_ptr - PL_markstack) * sizeof(I32));

5. PL_savestack

          memcpy(dest_savestack, PL_savestack, (PL_savestack_ix +  1) * sizeof(ANY));

6. PL_scopestack

           memcpy(dest_stk_scope, PL_scopestack, (PL_scopestack_ix + 1) * sizeof(I32));

Does the above way of saving stack make sense. Any help or pointers is highly appreciated.

thanks,
Kamal


    
     

Nicholas Clark

unread,
Dec 2, 2011, 10:59:20 AM12/2/11
to Kamal Khatri, perl5-...@perl.org
On Thu, Dec 01, 2011 at 05:08:38PM -0500, Kamal Khatri wrote:

> What would be the right way to save these stacks so that they could be
> restored again. Currently
> I am using the following techniques. However, I suspect I am not correctly
> handling saving
> of the Perl data types. This results in unpredictable crashes.
>
> 1. PL_stack_sp
>
> for(i=0; i < (PL_stack_sp - PL_stack_base); i++)
> copy using newSVsv(*(PL_stack_base + i)); however this does not
> seems to work for SVt_PVCV

I think it's not going to work for (at least) SVt_PVAV or SVt_PVHV either,
which potentially could end up on the stack. It should be fairly easy to
catch those and clone arrays and hashes. I doubt that it will correctly
copy SVt_PVIO or SVt_REGEXP either, and they aren't as easy to copy.
Also, I don't think that this approach will copy magic, so it's not
actually *cloning* the original, which I'm going to guess is what you're
trying to do.

> 5. PL_savestack
>
> memcpy(dest_savestack, PL_savestack, (PL_savestack_ix + 1) *
> sizeof(ANY));

I don't think that a copy is going to work, because the save stack contains
a lot of different things, including pointers to SVs, not all of which it
holds references for.

Look at Perl_ss_dup() for what it does to "copy" everything to a new thread.

You might be able to convince it to do the work for you, by setting up
the same structures as ithreads uses to clone to a new interpreter, but
instead having both the "old" and "new" interpreters actually being the
same interpreter.

However, I'm not sure if that will really work, because the intent of the
ithreads cloning code is to completely copy everything, so left unchecked,
if it finds a reference to (say) \%main:: from a stack it will clone
lots of things that you don't want cloned.

I'm also not sure whether it's possible to completely clone every type of
thing on the save stack. (This would also mean a limitation of fork
emulation on Win32). I believe that some uses of SAVEDESTRUCTOR or
SAVEDESTRUCTOR_X end up taking the address of C variables stored on the C
stack and placing those on the save stack. In turn, there's a careful
dance done when unwinding the stacks after catching an exception to make
sure that the longjmp() used to fire off the exception is caught deep
enough to ensure that the relevant C stack isn't (yet) blown away before
the scope stack is unwound, the pointer into the C stack is no longer needed,
and then the exception "rethrown" by another call to longjmp().

I *think* that this is what happens somewhere, as I remember a bug relating
to this not being done correctly, and some part of scope unwinding reading
a value that was no longer on a valid part of the stack.

But I can't find it currently.

> 6. PL_scopestack
>
> memcpy(dest_stk_scope, PL_scopestack, (PL_scopestack_ix + 1) *
> sizeof(I32));

Shouldn't that be sizeof(char *)?

> Does the above way of saving stack make sense. Any help or pointers is
> highly appreciated.

I'd suggest looking at what ithreads does to try to clone things.

Nicholas Clark

Nicholas Clark

unread,
Dec 2, 2011, 11:43:16 AM12/2/11
to Kamal Khatri, perl5-...@perl.org
On Fri, Dec 02, 2011 at 03:59:20PM +0000, Nicholas Clark wrote:

> I'm also not sure whether it's possible to completely clone every type of
> thing on the save stack. (This would also mean a limitation of fork
> emulation on Win32). I believe that some uses of SAVEDESTRUCTOR or
> SAVEDESTRUCTOR_X end up taking the address of C variables stored on the C
> stack and placing those on the save stack. In turn, there's a careful
> dance done when unwinding the stacks after catching an exception to make
> sure that the longjmp() used to fire off the exception is caught deep
> enough to ensure that the relevant C stack isn't (yet) blown away before
> the scope stack is unwound, the pointer into the C stack is no longer needed,
> and then the exception "rethrown" by another call to longjmp().

Mmm, and SSNEW(). Which allocates some space on the save stack.

> I *think* that this is what happens somewhere, as I remember a bug relating
> to this not being done correctly, and some part of scope unwinding reading
> a value that was no longer on a valid part of the stack.
>
> But I can't find it currently.

I think that it was this commit. But I'm not sure which bit of C stack
gets corrupted. So it may not be the bug that I remember, where valgrind
was reporting a read from the stack that "might be a false positive" (or
somesuch, related to "gcc" 2.96) except that in this case it was *not*
a false positive, it was a true positive.

commit 27e904532594b7fb224bdf9a05bf3b5336b8a39e
Author: David Mitchell <da...@iabyn.com>
Date: Thu Apr 8 13:16:56 2010 +0100

fix RT 23810: eval and tied methods

Something like the following ended up corrupted:
sub FETCH { eval 'BEGIN{syntax err}' }
The croak on error popped back the context stack etc to the EVAL pushed by
entereval, but the corresponding JUMPENV_PUSH(3) unwound all the way to the
outer perl_run, losing all the mg_get() related parts of the C stack.

It turns out that the run-time parts of pp_entereval were protected with
a new JUMPENV level, but the compile-time parts weren't. Add this.

diff --git a/pp_ctl.c b/pp_ctl.c
index 80c7b22..bbb2d15 100644
--- a/pp_ctl.c
+++ b/pp_ctl.c
@@ -1653,6 +1653,10 @@ Perl_die_where(pTHX_ SV *msv)
SV * const nsv = cx->blk_eval.old_namesv;
(void)hv_store(GvHVn(PL_incgv), SvPVX_const(nsv), SvCUR(nsv),
&PL_sv_undef, 0);
+ /* note that unlike pp_entereval, pp_require isn't
+ * supposed to trap errors. So now that we've popped the
+ * EVAL that pp_require pushed, and processed the error
+ * message, rethrow the error */
DIE(aTHX_ "%sCompilation failed in require",
*msg ? msg : "Unknown error\n");
}
@@ -3041,6 +3045,35 @@ Perl_find_runcv(pTHX_ U32 *db_seqp)
}


+/* Run yyparse() in a setjmp wrapper. Returns:
+ * 0: yyparse() successful
+ * 1: yyparse() failed
+ * 3: yyparse() died
+ */
+STATIC int
+S_try_yyparse(pTHX)
+{
+ int ret;
+ dJMPENV;
+
+ assert(CxTYPE(&cxstack[cxstack_ix]) == CXt_EVAL);
+ JMPENV_PUSH(ret);
+ switch (ret) {
+ case 0:
+ ret = yyparse() ? 1 : 0;
+ break;
+ case 3:
+ break;
+ default:
+ JMPENV_POP;
+ JMPENV_JUMP(ret);
+ /* NOTREACHED */
+ }
+ JMPENV_POP;
+ return ret;
+}
+
+
/* Compile a require/do, an eval '', or a /(?{...})/.
* In the last case, startop is non-null, and contains the address of
* a pointer that should be set to the just-compiled code.
@@ -3055,8 +3088,10 @@ S_doeval(pTHX_ int gimme, OP** startop, CV* outside, U32 seq)
{
dVAR; dSP;
OP * const saveop = PL_op;
+ bool in_require = (saveop && saveop->op_type == OP_REQUIRE);
+ int yystatus;

- PL_in_eval = ((saveop && saveop->op_type == OP_REQUIRE)
+ PL_in_eval = (in_require
? (EVAL_INREQUIRE | (PL_in_eval & EVAL_INEVAL))
: EVAL_INEVAL);

@@ -3108,27 +3143,39 @@ S_doeval(pTHX_ int gimme, OP** startop, CV* outside, U32 seq)
PL_in_eval |= EVAL_KEEPERR;
else
CLEAR_ERRSV();
- if (yyparse() || PL_parser->error_count || !PL_eval_root) {
+
+ /* note that yyparse() may raise an exception, e.g. C<BEGIN{die}>,
+ * so honour CATCH_GET and trap it here if necessary */
+
+ yystatus = (!in_require && CATCH_GET) ? S_try_yyparse(aTHX) : yyparse();
+
+ if (yystatus || PL_parser->error_count || !PL_eval_root) {
SV **newsp; /* Used by POPBLOCK. */
PERL_CONTEXT *cx = &cxstack[cxstack_ix];
- I32 optype = 0; /* Might be reset by POPEVAL. */
+ I32 optype; /* Used by POPEVAL. */
const char *msg;

+ PERL_UNUSED_VAR(newsp);
+ PERL_UNUSED_VAR(optype);
+
PL_op = saveop;
if (PL_eval_root) {
op_free(PL_eval_root);
PL_eval_root = NULL;
}
- SP = PL_stack_base + POPMARK; /* pop original mark */
- if (!startop) {
- POPBLOCK(cx,PL_curpm);
- POPEVAL(cx);
+ if (yystatus != 3) {
+ SP = PL_stack_base + POPMARK; /* pop original mark */
+ if (!startop) {
+ POPBLOCK(cx,PL_curpm);
+ POPEVAL(cx);
+ }
}
lex_end();
- LEAVE_with_name("eval"); /* pp_entereval knows about this LEAVE. */
+ if (yystatus != 3)
+ LEAVE_with_name("eval"); /* pp_entereval knows about this LEAVE. */

msg = SvPVx_nolen_const(ERRSV);
- if (optype == OP_REQUIRE) {
+ if (in_require) {
const SV * const nsv = cx->blk_eval.old_namesv;
(void)hv_store(GvHVn(PL_incgv), SvPVX_const(nsv), SvCUR(nsv),
&PL_sv_undef, 0);
@@ -3136,8 +3183,10 @@ S_doeval(pTHX_ int gimme, OP** startop, CV* outside, U32 seq)
*msg ? msg : "Unknown error\n");
}
else if (startop) {
- POPBLOCK(cx,PL_curpm);
- POPEVAL(cx);
+ if (yystatus != 3) {
+ POPBLOCK(cx,PL_curpm);
+ POPEVAL(cx);
+ }
Perl_croak(aTHX_ "%sCompilation failed in regexp",
(*msg ? msg : "Unknown error\n"));
}
@@ -3146,7 +3195,6 @@ S_doeval(pTHX_ int gimme, OP** startop, CV* outside, U32 seq)
sv_setpvs(ERRSV, "Compilation error");
}
}
- PERL_UNUSED_VAR(newsp);
PUSHs(&PL_sv_undef);
PUTBACK;
return FALSE;
diff --git a/t/op/tie.t b/t/op/tie.t
index a2e1d4a..0ec8050 100644
--- a/t/op/tie.t
+++ b/t/op/tie.t
@@ -658,3 +658,93 @@ sub STORE {
tie $SELECT, 'main';
$SELECT = *STDERR;
EXPECT
+########
+# RT 23810: eval in die in FETCH can corrupt context stack
+
+my $file = 'rt23810.pm';
+
+my $e;
+my $s;
+
+sub do_require {
+ my ($str, $eval) = @_;
+ open my $fh, '>', $file or die "Can't create $file: $!\n";
+ print $fh $str;
+ close $fh;
+ if ($eval) {
+ $s .= '-ERQ';
+ eval { require $pm; $s .= '-ENDE' }
+ }
+ else {
+ $s .= '-RQ';
+ require $pm;
+ }
+ $s .= '-ENDRQ';
+ unlink $file;
+}
+
+sub TIEHASH { bless {} }
+
+sub FETCH {
+ # 10 or more syntax errors makes yyparse croak()
+ my $bad = q{$x+;$x+;$x+;$x+;$x+;$x+;$x+;$x+;$x+$x+;$x+;$x+;$x+;$x+;;$x+;};
+
+ if ($_[1] eq 'eval') {
+ $s .= 'EVAL';
+ eval q[BEGIN { die; $s .= '-X1' }];
+ $s .= '-BD';
+ eval q[BEGIN { $x+ }];
+ $s .= '-BS';
+ eval '$x+';
+ $s .= '-E1';
+ $s .= '-S1' while $@ =~ /syntax error at/g;
+ eval $bad;
+ $s .= '-E2';
+ $s .= '-S2' while $@ =~ /syntax error at/g;
+ }
+ elsif ($_[1] eq 'require') {
+ $s .= 'REQUIRE';
+ my @text = (
+ q[BEGIN { die; $s .= '-X1' }],
+ q[BEGIN { $x+ }],
+ '$x+',
+ $bad
+ );
+ for my $i (0..$#text) {
+ $s .= "-$i";
+ do_require($txt[$i], 0) if $e;;
+ do_require($txt[$i], 1);
+ }
+ }
+ elsif ($_[1] eq 'exit') {
+ eval q[exit(0); print "overshot eval\n"];
+ }
+ else {
+ print "unknown key: '$_[1]'\n";
+ }
+ return "-R";
+}
+my %foo;
+tie %foo, "main";
+
+for my $action(qw(eval require)) {
+ $s = ''; $e = 0; $s .= main->FETCH($action); print "$action: s0=$s\n";
+ $s = ''; $e = 1; eval { $s .= main->FETCH($action)}; print "$action: s1=$s\n";
+ $s = ''; $e = 0; $s .= $foo{$action}; print "$action: s2=$s\n";
+ $s = ''; $e = 1; eval { $s .= $foo{$action}}; print "$action: s3=$s\n";
+}
+1 while unlink $file;
+
+$foo{'exit'};
+print "overshot main\n"; # shouldn't reach here
+
+EXPECT
+eval: s0=EVAL-BD-BS-E1-S1-E2-S2-S2-S2-S2-S2-S2-S2-S2-S2-S2-R
+eval: s1=EVAL-BD-BS-E1-S1-E2-S2-S2-S2-S2-S2-S2-S2-S2-S2-S2-R
+eval: s2=EVAL-BD-BS-E1-S1-E2-S2-S2-S2-S2-S2-S2-S2-S2-S2-S2-R
+eval: s3=EVAL-BD-BS-E1-S1-E2-S2-S2-S2-S2-S2-S2-S2-S2-S2-S2-R
+require: s0=REQUIRE-0-ERQ-ENDRQ-1-ERQ-ENDRQ-2-ERQ-ENDRQ-3-ERQ-ENDRQ-R
+require: s1=REQUIRE-0-RQ
+require: s2=REQUIRE-0-ERQ-ENDRQ-1-ERQ-ENDRQ-2-ERQ-ENDRQ-3-ERQ-ENDRQ-R
+require: s3=REQUIRE-0-RQ
+
Nicholas Clark

Nicholas Clark

unread,
Dec 2, 2011, 12:00:34 PM12/2/11
to Kamal Khatri, perl5-...@perl.org
On Fri, Dec 02, 2011 at 04:43:16PM +0000, Nicholas Clark wrote:
> On Fri, Dec 02, 2011 at 03:59:20PM +0000, Nicholas Clark wrote:
>
> > I'm also not sure whether it's possible to completely clone every type of
> > thing on the save stack. (This would also mean a limitation of fork
> > emulation on Win32). I believe that some uses of SAVEDESTRUCTOR or
> > SAVEDESTRUCTOR_X end up taking the address of C variables stored on the C
> > stack and placing those on the save stack. In turn, there's a careful
> > dance done when unwinding the stacks after catching an exception to make
> > sure that the longjmp() used to fire off the exception is caught deep
> > enough to ensure that the relevant C stack isn't (yet) blown away before
> > the scope stack is unwound, the pointer into the C stack is no longer needed,
> > and then the exception "rethrown" by another call to longjmp().

> I think that it was this commit. But I'm not sure which bit of C stack
> gets corrupted. So it may not be the bug that I remember, where valgrind
> was reporting a read from the stack that "might be a false positive" (or
> somesuch, related to "gcc" 2.96) except that in this case it was *not*
> a false positive, it was a true positive.

This code, subsequently changed in 2010 by commit 0c4d3b5ea916cf64:

/* Max number of items pushed there is 3*n or 4. We cannot fix
infinity, so we fix 4 (in fact 5): */
if (flags & 1) {
PL_savestack_ix += 5; /* Protect save in progress. */
SAVEDESTRUCTOR_X(S_unwind_handler_stack, (void*)&flags);
}

flags is an auto variable, hence on the C stack.

Bug in question was in this thread:

http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2005-11/msg00465.html

[explanation and solution a few messages further in, but the useful context
starts there.]

And I remember the fun, because it was all triggered by this seemingly sane
and innocent bug *fix*:

commit 5480675bc37b4a804a56a749cdedc70e27a270eb
Author: Nicholas Clark <ni...@ccl4.org>
Date: Sun Nov 13 11:27:48 2005 +0000

BEGIN blocks should start a new stack, as they can be called as a
side effect of "regular" Perl API calls within subroutines that have
already cached the current stack's address. If they don't, any stack
extension during the call may move the stack, rendering that cached
value invalid without the cachee realising. (For example, PP code
calling gv_fetchpv() which triggers a load of Errno.pm)

p4raw-id: //depot/maint-5.8/perl@26108

diff --git a/op.c b/op.c
index a319690..ebb6316 100644
--- a/op.c
+++ b/op.c
@@ -4506,8 +4506,10 @@ Perl_newATTRSUB(pTHX_ I32 floor, OP *o, OP *proto, OP *attrs, OP *block)
goto done;

if (strEQ(s, "BEGIN")) {
+ dSP;
const I32 oldscope = PL_scopestack_ix;
ENTER;
+ PUSHSTACKi(PERLSI_REQUIRE);
SAVECOPFILE(&PL_compiling);
SAVECOPLINE(&PL_compiling);

@@ -4520,6 +4522,7 @@ Perl_newATTRSUB(pTHX_ I32 floor, OP *o, OP *proto, OP *attrs, OP *block)

PL_curcop = &PL_compiling;
PL_compiling.op_private = (U8)(PL_hints & HINT_PRIVATE_MASK);
+ POPSTACK;
LEAVE;
}
else if (strEQ(s, "END") && !PL_error_count) {


I made that change, and then *this* code went wrong:

#!./perl -w

BEGIN {
$SIG{INT} = sub {exit(0)};
kill 'INT', $$;
}


Good eh?

The perl 5 VM is fragile.

Nicholas Clark

Kamal Khatri

unread,
Dec 2, 2011, 2:05:00 PM12/2/11
to Nicholas Clark, perl5-...@perl.org
> > 1. PL_stack_sp
> >
> >      for(i=0; i < (PL_stack_sp - PL_stack_base); i++)
> >          copy using newSVsv(*(PL_stack_base + i)); however this does not
> > seems to work for SVt_PVCV
>
> I think it's not going to work for (at least) SVt_PVAV or SVt_PVHV either,
> which potentially could end up on the stack. It should be fairly easy to
> catch those and clone arrays and hashes. I doubt that it will correctly
> copy SVt_PVIO or SVt_REGEXP either, and they aren't as easy to copy.
> Also, I don't think that this approach will copy magic, so it's not
> actually *cloning* the original, which I'm going to guess is what you're
> trying to do.

right, my usecase is to clone all perl state variables such that it
can be rollbacked
to. I guessed stacks represented perl execution states. Since execution states
need to be preserved full memory copy might be required for all perl data types.

Well, like your suggestion to look at the cloning functionality of ithreads,
I have now been looking at the ithreads clone() and dup()
implementations. While I look at ithread compilation option, for now I
blatantly copied
the ..dup() functions of ithreads and wherever applicable
replaced duplicating source as PL_.. global variables and duplicating
destination
as saved state variables. For PL_stack, i could then use PL_si_dup().
Now hopefully it
should handle all SV types.

However, even after dup()ing most of the states similar to the way
perl_clone() does, I am still
getting segfaults when restoring the saved states. (currently at
pp_ctl.c (pp_caller) at the following last line:
GV * const cvgv = CvGV(dbcx->blk_sub.cv);
if (isGV(cvgv)) {}

So, while exploring ideas to do the rollback, would it be possible to
use the ithreads clone
functionality to create a new PerlInterpreter everytime when saving
execution state is desired.
For rollback, an earlier cloned PerlInterpreter could be invoked. Does
this seem like feasible
idea ?

thanks for the help,
Kamal

Zefram

unread,
Dec 3, 2011, 4:09:58 PM12/3/11
to Kamal Khatri, perl5-...@perl.org
Kamal Khatri wrote:
>right, my usecase is to clone all perl state variables such that it
>can be rollbacked
>to.

I think what you want there is a core dump.

-zefram

Kamal Khatri

unread,
Dec 3, 2011, 10:06:56 PM12/3/11
to Zefram, perl5-...@perl.org
Are you referring to the core dump as described here:
http://perldoc.perl.org/functions/dump.html
I will definitely try it and see if it is all I need.

In the meantime, I looked at the ithreads and clone() features in
Perl and idea of using more than one PerlInterpreter at a time
(mentioned here as well:
http://www.perlmonks.org/?node_id=850886)

A simple approach I tried:

Perl_runops_debug(pTHX)
{
..
if(condition_to_save)
{
saved_interp = PL_curinterp;
perl_clone(saved_interp, CLONEf_COPY_STACKS);
}

if(condition_to_restore)
{
perl_clone(saved_interp, CLONEf_COPY_STACKS);
}
..
}

perl_clone does PERL_SET_THX(), so I guessed only perl_clone()
should be sufficient to restore.

Above code croaks with "panic: free from wrong pool". perl was compiled with:

./Configure -des -Dprefix=$HOME/localperl -Dusedevel -Accflags="-DDEBUGGING
-DPERL_TRACK_MEMPOOL -DPERL_USE_SAFE_PUTENV" -Doptimize='-g'
-Dusethreads -Dusemultiplicity

Can perl_clone() be used as above.

thanks,
Kamal

Simon Wistow

unread,
Dec 4, 2011, 12:06:05 PM12/4/11
to Kamal Khatri, Nicholas Clark, perl5-...@perl.org
On Fri, Dec 02, 2011 at 02:05:00PM -0500, Kamal Khatri said:
> right, my usecase is to clone all perl state variables such that it
> can be rollbacked to. I guessed stacks represented perl execution
> states. Since execution states need to be preserved full memory copy
> might be required for all perl data types.

I played around with something conceptually similar a few years back.

http://search.cpan.org/~simonw/Whatif-1.3/Whatif.pm

I tried various 'clever' things along the lines of

void do_magic(SV* coderef)
{
PerlInterpreter *orig, *copy;

orig = Perl_get_context();
copy = perl_clone(orig, FALSE);

PERL_SET_CONTEXT(copy);
perl_call_sv(coderef, G_DISCARD|G_NOARGS|G_EVAL);

/* Errk, it failed */
if (SvTRUE(ERRSV)) {
PERL_SET_CONTEXT(orig);
perl_free(copy);
/* ooh, it was fine */
} else {
perl_free(orig);
}
}

and trying to intercept writes to %::

In the end the easiest way to take a full copy of the stack was to
just fork - if the speculative code failed then it was killed and the
parent was replaced with the child.




Nicholas Clark

unread,
Dec 5, 2011, 7:34:01 AM12/5/11
to Kamal Khatri, perl5-...@perl.org, Zefram
On Fri, Dec 02, 2011 at 02:05:00PM -0500, Kamal Khatri wrote:

> So, while exploring ideas to do the rollback, would it be possible to
> use the ithreads clone
> functionality to create a new PerlInterpreter everytime when saving
> execution state is desired.
> For rollback, an earlier cloned PerlInterpreter could be invoked. Does
> this seem like feasible
> idea ?

Possibly, but I think that it won't work in the general case, and it's
likely to be fragile even for the places it usually seems to work.

Specifically, you're not going to be able to jump back into nested runloops.
require, overloading and tie (and probably some other things I've forgotten
or don't know about) call back into the runloop. The (complete) save stack
will have information related to all the levels of runloop calls and anything
in between that was saved to it. So if you attempt to save state from within
the inner runloop, that saved state won't be valid once you leave the inner
run loop, and it's going to go wrong if the outer runloop attempts to run
it. For added fun, I'm not even sure *how* the state saving code can know
which runloop level it is in.

On Sat, Dec 03, 2011 at 10:06:56PM -0500, Kamal Khatri wrote:

> In the meantime, I looked at the ithreads and clone() features in
> Perl and idea of using more than one PerlInterpreter at a time
> (mentioned here as well:
> http://www.perlmonks.org/?node_id=850886)
>
> A simple approach I tried:
>
> Perl_runops_debug(pTHX)
> {
> ..
> if(condition_to_save)
> {
> saved_interp = PL_curinterp;
> perl_clone(saved_interp, CLONEf_COPY_STACKS);
> }
>
> if(condition_to_restore)
> {
> perl_clone(saved_interp, CLONEf_COPY_STACKS);
> }
> ..
> }
>
> perl_clone does PERL_SET_THX(), so I guessed only perl_clone()
> should be sufficient to restore.
>
> Above code croaks with "panic: free from wrong pool". perl was compiled with:
>
> ./Configure -des -Dprefix=$HOME/localperl -Dusedevel -Accflags="-DDEBUGGING
> -DPERL_TRACK_MEMPOOL -DPERL_USE_SAFE_PUTENV" -Doptimize='-g'
> -Dusethreads -Dusemultiplicity
>
> Can perl_clone() be used as above.

No. Not that simply. It creates a new interpreter cloned from the current
interpreter. Assuming that the intent of your restore code is to copy the
saved interpreter (so that it can be restored from *again* if needed),
you can't call perl_clone() in void context. You'd need to clone the saved
state to get your "restored interpreter", then make the current thread
think that the "restored interpreter" was the correct interpreter, and
then destroy the original interpreter that you had been running (until then)
Else you'll be leaking an interpreter on every restore.

But you'll also need to deal with stopping END blocks running in the
interpreter you destroy, as the *interpreter* thinks that the program is
ending, whereas actually its not - a clone that it's unaware of is continuing
the program.

In the end, I don't know if this approach will ever work, even with the
noted restrictions about nested runloops. Possibly Simon's approach is the
only one that's going to work:

On Sun, Dec 04, 2011 at 05:06:05PM +0000, Simon Wistow wrote:
> On Fri, Dec 02, 2011 at 02:05:00PM -0500, Kamal Khatri said:
> > right, my usecase is to clone all perl state variables such that it
> > can be rollbacked to. I guessed stacks represented perl execution
> > states. Since execution states need to be preserved full memory copy
> > might be required for all perl data types.
>
> I played around with something conceptually similar a few years back.
>
> http://search.cpan.org/~simonw/Whatif-1.3/Whatif.pm
>
> I tried various 'clever' things along the lines of
>
> void do_magic(SV* coderef)
> {
> PerlInterpreter *orig, *copy;
>
> orig = Perl_get_context();
> copy = perl_clone(orig, FALSE);
>
> PERL_SET_CONTEXT(copy);
> perl_call_sv(coderef, G_DISCARD|G_NOARGS|G_EVAL);
>
> /* Errk, it failed */
> if (SvTRUE(ERRSV)) {
> PERL_SET_CONTEXT(orig);
> perl_free(copy);
> /* ooh, it was fine */
> } else {
> perl_free(orig);
> }
> }
>
> and trying to intercept writes to %::
>
> In the end the easiest way to take a full copy of the stack was to
> just fork - if the speculative code failed then it was killed and the
> parent was replaced with the child.

Nicholas Clark

Kamal Khatri

unread,
Dec 7, 2011, 12:48:02 AM12/7/11
to Nicholas Clark, perl5-...@perl.org, Zefram, Simon Wistow
On Mon, Dec 5, 2011 at 7:34 AM, Nicholas Clark <ni...@ccl4.org> wrote:
> On Fri, Dec 02, 2011 at 02:05:00PM -0500, Kamal Khatri wrote:
>
>> So, while exploring ideas to do the rollback, would it be possible to
>> use the ithreads clone
>> functionality to create a new PerlInterpreter everytime when saving
>> execution state is desired.
>> For rollback, an earlier cloned PerlInterpreter could be invoked. Does
>> this seem like feasible
>> idea ?
>
> Possibly, but I think that it won't work in the general case, and it's
> likely to be fragile even for the places it usually seems to work.
>
> Specifically, you're not going to be able to jump back into nested runloops.
> require, overloading and tie (and probably some other things I've forgotten
> or don't know about) call back into the runloop. The (complete) save stack
> will have information related to all the levels of runloop calls and anything
> in between that was saved to it. So if you attempt to save state from within
> the inner runloop, that saved state won't be valid once you leave the inner
> run loop, and it's going to go wrong if the outer runloop attempts to run
> it. For added fun, I'm not even sure *how* the state saving code can know
> which runloop level it is in.

Since currently I do not know much about runloop levels and nesting of
runloops,
I would not be able to figure out whether setting contexts similar to Simon's
suggestion would also suffer from this problem. let me know if this would still
be a problem with context setting added. I have included the updated
implementation
approach below.

> No. Not that simply. It creates a new interpreter cloned from the current
> interpreter. Assuming that the intent of your restore code is to copy the
> saved interpreter (so that it can be restored from *again* if needed),
> you can't call perl_clone() in void context. You'd need to clone the saved
> state to get your "restored interpreter", then make the current thread
> think that the "restored interpreter" was the correct interpreter, and
> then destroy the original interpreter that you had been running (until then)
> Else you'll be leaking an interpreter on every restore.

I have updated the implementation approach with the above comments and
Simon's code snippet. Following code also contains pseudocode
mixed with actual code. I have added some description after the code.

struct exec_state
{ PerlInterpreter *saved_interp;
branch_explored variable;/* pseudocode */
};

Perl_runops_debug(pTHX)
{
..
if(!clone_in_progress)
if(PL_op->op_type == OP_COND_EXPR || PL_op->op_type == OP_AND)
{
allocate exec_state variable st_var;
dSP;
if (SvTRUEx(TOPs))
set st_var->branch_explored to TRUE branch;
else
set st_var->branch_explored to FALSE branch;

st_var->saved_interp = Perl_get_context();
PerlInterpreter *newp = perl_clone(st_var->saved_interp, CLONEf_COPY_STACKS);
PERL_SET_CONTEXT(newp);

store st_var in list;
}

if(!clone_in_progress && condition_to_restore)
{
getnext:
pop out st_var from list;
PERL_SET_CONTEXT(st_var->saved_interp);

dSP;
if(st_var->branch_explored is TRUE branch)
SETs(boolSV(0));
else if(st_var->branch_explored is FALSE branch)
SETs(boolSV(0));
else
{ perl_free(st_var->saved_interp);
goto getnext;
}
}
..
}

The above code was intended to do branch exploration, saving states in each
branch condition (OP_COND_EXPR, OP_AND) and also recording the TRUE/FALSE
path taken. And when rollback is done, then the different TRUE/FALSE path is
taken if unexplored. If both paths are explored, then it rollbacks one
branch condition
prior. Perl_runops_debug() looked suitable to do this since it has visibility
into opcodes. I was focusing mosly on saving perl execution states
correctly. Most
of the functionality requiring external interaction (eg. database
operations, network
calls) may possibly be emulated.

Well, the above approach still segfaults after perl_free() is called.
This happens
anytime perl_free() is called either during saving or restoring. The
segfault occurs at:
PL_op = PL_op->op_ppaddr(aTHX);

> But you'll also need to deal with stopping END blocks running in the
> interpreter you destroy, as the *interpreter* thinks that the program is
> ending, whereas actually its not - a clone that it's unaware of is continuing
> the program.

Maybe this is the reason of program failing after perl_free() (or not). I will
certainly look more into it. Also, in the meantime, can you provide
any pointers
where I should be looking for it.

> In the end, I don't know if this approach will ever work, even with the
> noted restrictions about nested runloops. Possibly Simon's approach is the
> only one that's going to work:
>
> On Sun, Dec 04, 2011 at 05:06:05PM +0000, Simon Wistow wrote:
>> I played around with something conceptually similar a few years back.
>>
>>   http://search.cpan.org/~simonw/Whatif-1.3/Whatif.pm
>>
>> I tried various 'clever' things along the lines of
>>
>>   void do_magic(SV* coderef)
>>   {
>>      PerlInterpreter *orig, *copy;
>>
>>      orig = Perl_get_context();
>>      copy = perl_clone(orig, FALSE);
>>
>>      PERL_SET_CONTEXT(copy);
>>      perl_call_sv(coderef, G_DISCARD|G_NOARGS|G_EVAL);
>>
>>      /* Errk, it failed */
>>      if (SvTRUE(ERRSV)) {
>>        PERL_SET_CONTEXT(orig);
>>        perl_free(copy);
>>      /* ooh, it was fine */
>>      } else {
>>        perl_free(orig);
>>      }
>>   }
>>
>> and trying to intercept writes to %::
>> In the end the easiest way to take a full copy of the stack was to
>> just fork - if the speculative code failed then it was killed and the
>> parent was replaced with the child.

Since there would be large number of branch conditions, I do not
know if there would be problem during wait()/signal() due to large number
of created processes. I will see if this is a viable option as well.

On Sat, Dec 3, 2011 at 4:09 PM, Zefram <zef...@fysh.org> wrote:
> Kamal Khatri wrote:
>>right, my usecase is to clone all perl state variables such that it
>>can be rollbacked
>>to.
>
> I think what you want there is a core dump.
>

I looked at the core dump. But it seems to use unexec/undump, both of
which are not present in the perl source. Also, I could not find much
information
on it. I will probably try it later if the cloning approach does not work.

I also had a couple of questions regarding perl source. Any help on these
would also be highly helpful.

1. Is there a way to backreference which module, subroutine (of the executed
perl program) an op belongs to. Somehow I am not able to figure
whether I should
be looking at global variables PL_subname, PL_custname (as the comment in
intrpvar.h implies).

2. I also did not find any call made to perl_clone() in the perl source code.
Isn't the thread implementation supposed to call perl_clone() function.

Nicholas Clark

unread,
Dec 7, 2011, 5:42:50 AM12/7/11
to Kamal Khatri, perl5-...@perl.org, Zefram, Simon Wistow
On Wed, Dec 07, 2011 at 12:48:02AM -0500, Kamal Khatri wrote:

> Since currently I do not know much about runloop levels and nesting of
> runloops,
> I would not be able to figure out whether setting contexts similar to Simon's
> suggestion would also suffer from this problem. let me know if this would still
> be a problem with context setting added. I have included the updated
> implementation
> approach below.

No, it wouldn't suffer the problems of different runloops.
I believe it avoids most problems, but does reveal to the outside world that
the program is doing something "strange", as the process ID of the running
code changes.

> if(!clone_in_progress && condition_to_restore)
> {
> getnext:
> pop out st_var from list;
> PERL_SET_CONTEXT(st_var->saved_interp);
>
> dSP;
> if(st_var->branch_explored is TRUE branch)
> SETs(boolSV(0));
> else if(st_var->branch_explored is FALSE branch)
> SETs(boolSV(0));
> else
> { perl_free(st_var->saved_interp);
> goto getnext;
> }
> }
> ..
> }

> Well, the above approach still segfaults after perl_free() is called.
> This happens
> anytime perl_free() is called either during saving or restoring. The
> segfault occurs at:
> PL_op = PL_op->op_ppaddr(aTHX);

You might find the script Porting/expand-macro.pl useful.

Under ithreads, PL_op is a macro.

$ perl Porting/expand-macro.pl PL_op
`sh cflags "optimize='-ggdb3'" try.c` -E try.c > try.i
CCCMD = ccache gcc -DPERL_CORE -c -fno-common -DPERL_DARWIN -no-cpp-precomp -DNO_MATHOMS -DDEBUGGING -fno-strict-aliasing -pipe -fstack-protector -I/opt/local/include -std=c89 -ggdb3 -Wall -ansi -W -Wextra -Wdeclaration-after-statement -Wendif-labels -Wc++-compat -Wwrite-strings
# 4 "PL_op expands to"
(my_perl->Iop)

my_perl is supposed to be set to the current interpreter. I think that your
code will free the current interpreter, and as PERL_SET_CONTEXT doesn't
assign to my_perl, that leaves my_perl pointing to freed memory.

> > But you'll also need to deal with stopping END blocks running in the
> > interpreter you destroy, as the *interpreter* thinks that the program is
> > ending, whereas actually its not - a clone that it's unaware of is continuing
> > the program.
>
> Maybe this is the reason of program failing after perl_free() (or not). I will
> certainly look more into it. Also, in the meantime, can you provide
> any pointers
> where I should be looking for it.

END blocks are stored in PL_endav. I don't know more than that, so look for
code that manipulates PL_endav.

> Since there would be large number of branch conditions, I do not
> know if there would be problem during wait()/signal() due to large number
> of created processes. I will see if this is a viable option as well.

Whatever you do, it's going to be expensive. ithreads cloning isn't cheap,
and may be more expensive than fork.

> 1. Is there a way to backreference which module, subroutine (of the executed
> perl program) an op belongs to. Somehow I am not able to figure
> whether I should
> be looking at global variables PL_subname, PL_custname (as the comment in
> intrpvar.h implies).

Actual opcodes are listed in the file regen/opcodes
Historically each had a function to implement it, named with a pp_ prefix.
However, some have now been merged - there is a table of aliases in
regen/opcode.pl
The C code functions tend to be defined using a macro PP - try

grep ^PP *.c

> 2. I also did not find any call made to perl_clone() in the perl source code.
> Isn't the thread implementation supposed to call perl_clone() function.

The threads implementation is in dist/threads/threads.xs and
dist/threads-shared/shared.xs

Source code is not just in the top level directory.

Nicholas Clark

Reini Urban

unread,
Dec 9, 2011, 10:54:56 AM12/9/11
to Kamal Khatri, Nicholas Clark, perl5-...@perl.org, Zefram, Simon Wistow
That looks like an implementation of parallel amb, the ambiguity
operator for perl.
I believe Dmitry Karasik already implemented that via B::Generate.
http://search.cpan.org/dist/Amb/
Just simplier, not messing with stacks and parallelity.
Even without backtracking, not quite robust, but more robust than yours.

If you want to explore all branches in parallel use ithreads, or if
not just stack it.
--
Reini Urban
http://cpanel.net/   http://www.perl-compiler.org/

Kamal Khatri

unread,
Dec 11, 2011, 12:18:05 AM12/11/11
to Father Chrysostomos, perl5-...@perl.org
On Wed, Dec 7, 2011 at 1:21 AM, Father Chrysostomos <spr...@cpan.org> wrote:
> Kamal Khatri asked:
>> 1. Is there a way to backreference which module, subroutine (of the
>> executed perl program) an op belongs to. Somehow I am not able to
>> figure whether I should be looking at global variables PL_subname,
>> PL_custname (as the comment in intrpvar.h implies).
>
> If you mean the currently running sub, find_runcv will get it for you.
>
> Try something like Perl_sv_dump(aTHX_ find_runcv(0));

That was what i was looking for. Just to be sure that i am doing it
right, I got the
subroutine name and the package name using find_runcv() as follows:

GV *code_gv = CvGV(find_runcv(0));
GP *subroutine_gp = GvGP(code_gv);
GV *subroutine_gv = subroutine_gp->gp_egv;
HV *subroutine_stash = GvSTASH(subroutine_gv);

subroutine name = GvNAME(subroutine_gv)
package name = HvNAME(subroutine_stash)

Let me know if that was the right way to get those values.

thanks,
Kamal

Kamal Khatri

unread,
Dec 11, 2011, 12:41:49 AM12/11/11
to Nicholas Clark, perl5-...@perl.org, Zefram, Simon Wistow
> END blocks are stored in PL_endav. I don't know more than that, so look for
> code that manipulates PL_endav.

Using cloning approach, rollback is still far from a correct
solution. There seems to be large number of things to be handled which i am
currently unaware of. Though i am still looking at missing things for
this approach,
currently i used a simpler manual rerunning based approach to perform rollback.
When needing to rollback, the last recorded branch direction could be
changed during
another execution run. This seems to work so far.

>> Since there would be large number of branch conditions, I do not
>> know if there would be problem during wait()/signal() due to large number
>> of created processes. I will see if this is a viable option as well.
>
> Whatever you do, it's going to be expensive. ithreads cloning isn't cheap,
> and may be more expensive than fork.

with fork, there would be large number of processes. Except one, all would
be waiting and would be needed only when rollback to that process is
done (which
may not happen for most of the processes).
Doesn't it cost more memory (compared to PerlInterpreter cloning)
when a large perl program is executed.

>> 1. Is there a way to backreference which module, subroutine (of the executed
>> perl program) an op belongs to. Somehow I am not able to figure
>> whether I should
>> be looking at global variables PL_subname, PL_custname (as the comment in
>> intrpvar.h implies).
>
> Actual opcodes are listed in the file regen/opcodes
> Historically each had a function to implement it, named with a pp_ prefix.
> However, some have now been merged - there is a table of aliases in
> regen/opcode.pl
> The C code functions tend to be defined using a macro PP - try
>
>    grep ^PP *.c
>
>> 2. I also did not find any call made to perl_clone() in the perl source code.
>> Isn't the thread implementation supposed to call perl_clone() function.
>
> The threads implementation is in dist/threads/threads.xs and
> dist/threads-shared/shared.xs
>
> Source code is not just in the top level directory.

thanks for the pointers. it helped to get general idea regarding the
cloning.

thanks,
Kamal

Kamal Khatri

unread,
Dec 11, 2011, 12:51:59 AM12/11/11
to Reini Urban, Nicholas Clark, perl5-...@perl.org, Zefram, Simon Wistow
My usecase was to do this without requiring to change the source code since
the perl program would be large. Though i am still looking at the
cloning based approach
similar to the ithreads, currently i could do rollbacks by changing
the recorded branch directions and rerunning the perl program.

I will look into the ambiguity operator and see if similar technique
can be used.

thanks,
Kamal

Father Chrysostomos

unread,
Dec 11, 2011, 2:35:39 PM12/11/11
to Kamal Khatri, perl5-...@perl.org
You can just use GvENAME(code_gv) instead of going through the gp.

Please realise that CvGV is not part of the API, so if you rely on it, your code may have to change if you upgrade perl.

>
> thanks,
> Kamal

Nicholas Clark

unread,
Dec 20, 2011, 3:51:34 AM12/20/11
to Kamal Khatri, perl5-...@perl.org, Zefram, Simon Wistow
On Sun, Dec 11, 2011 at 12:41:49AM -0500, Kamal Khatri wrote:
> On Wed, Dec 7, 2011 at 5:42 AM, Nicholas Clark <ni...@ccl4.org> wrote:


> > Whatever you do, it's going to be expensive. ithreads cloning isn't cheap,
> > and may be more expensive than fork.
>
> with fork, there would be large number of processes. Except one, all would
> be waiting and would be needed only when rollback to that process is
> done (which
> may not happen for most of the processes).
> Doesn't it cost more memory (compared to PerlInterpreter cloning)
> when a large perl program is executed.

My hunch is "no". ithreads cloning copies pretty much all of the interpreter's
state. As it's doing this by using malloc() to allocate more memory while
copying, the OS will have to allocate it more memory.

fork() copies everything, but most of what it will copy would have been
copied by ithreads anyway. But the OS knows about fork(), and any decent
OS has copy-on-write semantics for the memory used by forked processes.
As most of the forked processes are just waiting, they won't be writing to
their own memory, so most of them will share actual memory allocation.
Together they are likely to consume less resources from the OS than one
processes with lots of ithread state.

Nicholas Clark
0 new messages