summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embed.fnc2
-rw-r--r--embed.h2
-rw-r--r--ext/XS-APItest/APItest.xs57
-rw-r--r--ext/XS-APItest/t/peep.t39
-rw-r--r--op.c25
-rw-r--r--perl.h8
-rw-r--r--pod/perlguts.pod60
-rw-r--r--proto.h6
8 files changed, 179 insertions, 20 deletions
diff --git a/embed.fnc b/embed.fnc
index dc667b7fac..8f9cebf7e6 100644
--- a/embed.fnc
+++ b/embed.fnc
@@ -888,7 +888,7 @@ sd |void |pad_reset
: Used in op.c
pd |void |pad_swipe |PADOFFSET po|bool refadjust
: FIXME
-p |void |peep |NULLOK OP* o
+p |void |peep |NULLOK OP* o|NN peep_next_t *next_peep
: Defined in doio.c, used only in pp_hot.c
dopM |PerlIO*|start_glob |NN SV *tmpglob|NN IO *io
#if defined(USE_REENTRANT_API)
diff --git a/embed.h b/embed.h
index 07aa965d29..5312d22bdd 100644
--- a/embed.h
+++ b/embed.h
@@ -3150,7 +3150,7 @@
#endif
#ifdef PERL_CORE
#define pad_swipe(a,b) Perl_pad_swipe(aTHX_ a,b)
-#define peep(a) Perl_peep(aTHX_ a)
+#define peep(a,b) Perl_peep(aTHX_ a,b)
#endif
#if defined(USE_REENTRANT_API)
#define reentrant_size() Perl_reentrant_size(aTHX)
diff --git a/ext/XS-APItest/APItest.xs b/ext/XS-APItest/APItest.xs
index 3b90d95443..9e5ebe8ec4 100644
--- a/ext/XS-APItest/APItest.xs
+++ b/ext/XS-APItest/APItest.xs
@@ -17,6 +17,8 @@ typedef struct {
AV *cscav;
AV *bhkav;
bool bhk_record;
+ peep_t orig_peep;
+ AV *peep_record;
} my_cxt_t;
START_MY_CXT
@@ -327,6 +329,23 @@ blockhook_test_eval(pTHX_ OP *const o)
STATIC BHK bhk_csc, bhk_test;
+STATIC void
+my_peep (pTHX_ OP *o, peep_next_t *next_peep)
+{
+ dMY_CXT;
+
+ if (!o)
+ return;
+
+ CALL_FPTR(MY_CXT.orig_peep)(aTHX_ o, next_peep);
+
+ for (; o; o = o->op_next) {
+ if (o->op_type == OP_CONST && cSVOPx_sv(o) && SvPOK(cSVOPx_sv(o))) {
+ av_push(MY_CXT.peep_record, newSVsv(cSVOPx_sv(o)));
+ }
+ }
+}
+
#include "const-c.inc"
MODULE = XS::APItest:Hash PACKAGE = XS::APItest::Hash
@@ -618,9 +637,9 @@ refcounted_he_fetch(key, level=0)
SvREFCNT_inc(RETVAL);
OUTPUT:
RETVAL
-
+
#endif
-
+
=pod
sub TIEHASH { bless {}, $_[0] }
@@ -693,25 +712,28 @@ BOOT:
BhkENTRY_set(&bhk_test, eval, blockhook_test_eval);
Perl_blockhook_register(aTHX_ &bhk_test);
- MY_CXT.cscgv = gv_fetchpvs("XS::APItest::COMPILE_SCOPE_CONTAINER",
+ MY_CXT.cscgv = gv_fetchpvs("XS::APItest::COMPILE_SCOPE_CONTAINER",
GV_ADDMULTI, SVt_PVAV);
MY_CXT.cscav = GvAV(MY_CXT.cscgv);
BhkENTRY_set(&bhk_csc, start, blockhook_csc_start);
BhkENTRY_set(&bhk_csc, pre_end, blockhook_csc_pre_end);
Perl_blockhook_register(aTHX_ &bhk_csc);
-}
+
+ MY_CXT.peep_record = newAV();
+}
void
CLONE(...)
CODE:
MY_CXT_CLONE;
MY_CXT.sv = newSVpv("initial_clone",0);
- MY_CXT.cscgv = gv_fetchpvs("XS::APItest::COMPILE_SCOPE_CONTAINER",
+ MY_CXT.cscgv = gv_fetchpvs("XS::APItest::COMPILE_SCOPE_CONTAINER",
GV_ADDMULTI, SVt_PVAV);
MY_CXT.cscav = NULL;
MY_CXT.bhkav = get_av("XS::APItest::bhkav", GV_ADDMULTI);
MY_CXT.bhk_record = 0;
+ MY_CXT.peep_record = newAV();
void
print_double(val)
@@ -1090,3 +1112,28 @@ bhk_record(bool on)
MY_CXT.bhk_record = on;
if (on)
av_clear(MY_CXT.bhkav);
+
+void
+peep_enable ()
+ PREINIT:
+ dMY_CXT;
+ CODE:
+ av_clear(MY_CXT.peep_record);
+ MY_CXT.orig_peep = PL_peepp;
+ PL_peepp = my_peep;
+
+AV *
+peep_record ()
+ PREINIT:
+ dMY_CXT;
+ CODE:
+ RETVAL = MY_CXT.peep_record;
+ OUTPUT:
+ RETVAL
+
+void
+peep_record_clear ()
+ PREINIT:
+ dMY_CXT;
+ CODE:
+ av_clear(MY_CXT.peep_record);
diff --git a/ext/XS-APItest/t/peep.t b/ext/XS-APItest/t/peep.t
new file mode 100644
index 0000000000..fa61dc3420
--- /dev/null
+++ b/ext/XS-APItest/t/peep.t
@@ -0,0 +1,39 @@
+#!perl -w
+
+BEGIN {
+ push @INC, "::lib:$MacPerl::Architecture:" if $^O eq 'MacOS';
+ require Config; import Config;
+ if ($Config{'extensions'} !~ /\bXS\/APItest\b/) {
+ # Look, I'm using this fully-qualified variable more than once!
+ my $arch = $MacPerl::Architecture;
+ print "1..0 # Skip: XS::APItest was not built\n";
+ exit 0;
+ }
+}
+
+use strict;
+use warnings;
+
+BEGIN {
+ require '../../t/test.pl';
+ plan(6);
+ use_ok('XS::APItest')
+};
+
+my $record = XS::APItest::peep_record;
+
+XS::APItest::peep_enable;
+
+# our peep got called and remembered the string constant
+eval q[my $foo = q/affe/];
+is(scalar @{ $record }, 1);
+is($record->[0], 'affe');
+
+XS::APItest::peep_record_clear;
+
+# peep got called for each root op of the branch
+$::moo = $::moo = 0;
+eval q[my $foo = $::moo ? q/x/ : q/y/];
+is(scalar @{ $record }, 2);
+is($record->[0], 'x');
+is($record->[1], 'y');
diff --git a/op.c b/op.c
index 5a0962bbb3..9539248455 100644
--- a/op.c
+++ b/op.c
@@ -103,7 +103,14 @@ recursive, but it's recursive on basic blocks, not on tree nodes.
#include "perl.h"
#include "keywords.h"
-#define CALL_PEEP(o) CALL_FPTR(PL_peepp)(aTHX_ o)
+#define CALL_A_PEEP(peep, o) CALL_FPTR((peep)->fn)(aTHX_ o, peep)
+
+#define CALL_PEEP(o) \
+ STMT_START { \
+ peep_next_t _next_peep = { PL_peepp, NULL }; \
+ CALL_A_PEEP(&_next_peep, o); \
+ } STMT_END
+
#define CALL_OPFREEHOOK(o) if (PL_opfreehook) CALL_FPTR(PL_opfreehook)(aTHX_ o)
#if defined(PL_OP_SLAB_ALLOC)
@@ -8515,11 +8522,13 @@ S_is_inplace_av(pTHX_ OP *o, OP *oright) {
* peep() is called */
void
-Perl_peep(pTHX_ register OP *o)
+Perl_peep(pTHX_ register OP *o, peep_next_t *next_peep)
{
dVAR;
register OP* oldop = NULL;
+ PERL_ARGS_ASSERT_PEEP;
+
if (!o || o->op_opt)
return;
ENTER;
@@ -8714,7 +8723,7 @@ Perl_peep(pTHX_ register OP *o)
sop = fop->op_sibling;
while (cLOGOP->op_other->op_type == OP_NULL)
cLOGOP->op_other = cLOGOP->op_other->op_next;
- peep(cLOGOP->op_other); /* Recursive calls are not replaced by fptr calls */
+ CALL_A_PEEP(next_peep, cLOGOP->op_other); /* Recursive calls are not replaced by fptr calls */
stitch_keys:
o->op_opt = 1;
@@ -8765,20 +8774,20 @@ Perl_peep(pTHX_ register OP *o)
case OP_ONCE:
while (cLOGOP->op_other->op_type == OP_NULL)
cLOGOP->op_other = cLOGOP->op_other->op_next;
- peep(cLOGOP->op_other); /* Recursive calls are not replaced by fptr calls */
+ CALL_A_PEEP(next_peep, cLOGOP->op_other); /* Recursive calls are not replaced by fptr calls */
break;
case OP_ENTERLOOP:
case OP_ENTERITER:
while (cLOOP->op_redoop->op_type == OP_NULL)
cLOOP->op_redoop = cLOOP->op_redoop->op_next;
- peep(cLOOP->op_redoop);
+ CALL_A_PEEP(next_peep, cLOOP->op_redoop);
while (cLOOP->op_nextop->op_type == OP_NULL)
cLOOP->op_nextop = cLOOP->op_nextop->op_next;
- peep(cLOOP->op_nextop);
+ CALL_A_PEEP(next_peep, cLOOP->op_nextop);
while (cLOOP->op_lastop->op_type == OP_NULL)
cLOOP->op_lastop = cLOOP->op_lastop->op_next;
- peep(cLOOP->op_lastop);
+ CALL_A_PEEP(next_peep, cLOOP->op_lastop);
break;
case OP_SUBST:
@@ -8787,7 +8796,7 @@ Perl_peep(pTHX_ register OP *o)
cPMOP->op_pmstashstartu.op_pmreplstart->op_type == OP_NULL)
cPMOP->op_pmstashstartu.op_pmreplstart
= cPMOP->op_pmstashstartu.op_pmreplstart->op_next;
- peep(cPMOP->op_pmstashstartu.op_pmreplstart);
+ CALL_A_PEEP(next_peep, cPMOP->op_pmstashstartu.op_pmreplstart);
break;
case OP_EXEC:
diff --git a/perl.h b/perl.h
index 7fcff2f6ba..32cf7873e1 100644
--- a/perl.h
+++ b/perl.h
@@ -4833,7 +4833,13 @@ struct perl_debug_pad {
PERL_DEBUG_PAD(i))
/* Enable variables which are pointers to functions */
-typedef void (CPERLscope(*peep_t))(pTHX_ OP* o);
+struct peep_next;
+typedef void (CPERLscope(*peep_t))(pTHX_ OP* o, struct peep_next *next);
+typedef struct peep_next {
+ peep_t fn;
+ void *user_data;
+} peep_next_t;
+
typedef regexp*(CPERLscope(*regcomp_t)) (pTHX_ char* exp, char* xend, PMOP* pm);
typedef I32 (CPERLscope(*regexec_t)) (pTHX_ regexp* prog, char* stringarg,
char* strend, char* strbeg, I32 minend,
diff --git a/pod/perlguts.pod b/pod/perlguts.pod
index 62e99bd386..6a244b7d79 100644
--- a/pod/perlguts.pod
+++ b/pod/perlguts.pod
@@ -1821,9 +1821,63 @@ of free()ing (i.e. their type is changed to OP_NULL).
After the compile tree for a subroutine (or for an C<eval> or a file)
is created, an additional pass over the code is performed. This pass
is neither top-down or bottom-up, but in the execution order (with
-additional complications for conditionals). These optimizations are
-done in the subroutine peep(). Optimizations performed at this stage
-are subject to the same restrictions as in the pass 2.
+additional complications for conditionals). Optimizations performed
+at this stage are subject to the same restrictions as in the pass 2.
+
+Peephole optimizations are done by calling the function pointed to by
+the global variable C<PL_peepp>. By default, C<PL_peepp> points to the
+function C<Perl_peep>. However, extensions may provide their own
+peephole optimizers, like this:
+
+ peep_t original_peep;
+
+ void my_peep (pTHX_ OP *o, peep_next_t *next_peep)
+ {
+ /* Delegate perl's original optimizer. The function pointer
+ * in next_peep->fn will point to the optimizer function
+ * initially invoked, so when perl's peep recurses into some
+ * branch of the optree, it'll call back to my_peep.
+ */
+ CALL_FPTR(original_peep)(aTHX_ o, next_peep);
+
+ if (!o)
+ return;
+
+ for (; o; o = o->op_next) {
+ /* custom optimisations */
+ }
+ }
+
+ /* later, for example in a BOOT section */
+ original_peep = PL_peepp;
+ PL_peepp = my_peep;
+
+Do note that the peephole optimizer is called for each root of an
+optree. It has to traverse that optree itself, if necessary.
+
+However, it is not normally necessary for peep extensions to walk into
+branches of conditions. Perl's original optimizer, which extensions should
+always delegate to, already implements that and will call the optimizer
+pointed to by C<next_peep> for each root OP of branches. By default,
+C<next_peep> points to whatever is in C<PL_peepp>, but it is also possible
+to make the default optimizer call back to different optimizers:
+
+ void my_peep (pTHX_ OP *o, peep_next_t *next_peep)
+ {
+ peep_next_t other_peep = { my_other_peep, NULL };
+
+ /* call the original peep, and have it call my_other_peep when
+ * recursing into branches */
+ CALL_FPTR(original_peep)(aTHX_ o, &other_peep);
+ }
+
+The second member of C<peep_next_t>, C<user_data>, which is just set to
+C<NULL> in the above example, may be used to pass along arbitrary data to
+later invocations of peep functions.
+
+Also note that, under some conditions, the peephole optimizer will be
+called with a C<NULL> opcode. That is perfectly normal and optimizer
+functions need to accomodate for that.
=head2 Pluggable runops
diff --git a/proto.h b/proto.h
index 8ad7e66144..274509ad5d 100644
--- a/proto.h
+++ b/proto.h
@@ -2570,7 +2570,11 @@ PERL_CALLCONV void Perl_pad_free(pTHX_ PADOFFSET po);
STATIC void S_pad_reset(pTHX);
#endif
PERL_CALLCONV void Perl_pad_swipe(pTHX_ PADOFFSET po, bool refadjust);
-PERL_CALLCONV void Perl_peep(pTHX_ OP* o);
+PERL_CALLCONV void Perl_peep(pTHX_ OP* o, peep_next_t *next_peep)
+ __attribute__nonnull__(pTHX_2);
+#define PERL_ARGS_ASSERT_PEEP \
+ assert(next_peep)
+
PERL_CALLCONV PerlIO* Perl_start_glob(pTHX_ SV *tmpglob, IO *io)
__attribute__nonnull__(pTHX_1)
__attribute__nonnull__(pTHX_2);