File Coverage

lib/Faster/Maths.xs
Criterion Covered Total %
statement 118 131 90.0
branch 76 126 60.3
condition n/a
subroutine n/a
pod n/a
total 194 257 75.4


line stmt bran cond sub pod time code
1             /* You may distribute under the terms of either the GNU General Public License
2             * or the Artistic License (the same terms as Perl itself)
3             *
4             * (C) Paul Evans, 2021 -- leonerd@leonerd.org.uk
5             */
6             #define PERL_NO_GET_CONTEXT
7              
8             #include "EXTERN.h"
9             #include "perl.h"
10             #include "XSUB.h"
11              
12             #define HAVE_PERL_VERSION(R, V, S) \
13             (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
14              
15             #if HAVE_PERL_VERSION(5,28,0)
16             # define GET_UNOP_AUX_item_pv(aux) ((aux).pv)
17             # define SET_UNOP_AUX_item_pv(aux,v) ((aux).pv = (v))
18             #else
19             # define GET_UNOP_AUX_item_pv(aux) INT2PTR(char *, (aux).uv)
20             # define SET_UNOP_AUX_item_pv(aux,v) ((aux).uv = PTR2UV(v))
21             #endif
22              
23             static XOP xop_multimath;
24 3174007           static OP *pp_multimath(pTHX)
25             {
26 3174007           dSP;
27 3174007           dTARGET;
28 3174007           UNOP_AUX_item *aux = cUNOP_AUX->op_aux;
29              
30 3174007           char *prog = GET_UNOP_AUX_item_pv(aux[0]) + 1; /* skip leading '(' */
31 3174007           U32 ntmps = PL_op->op_private;
32              
33             U32 tmpi = 1;
34             NV accum;
35              
36 3174007           U32 auxi = 1 + ntmps;
37              
38 3174007           switch(*(prog++)) {
39             case 'p':
40 2118006 100         accum = SvNV_nomg(PAD_SV(aux[auxi].pad_offset));
41 2118006           auxi++;
42 3174007           break;
43              
44             case 'c':
45 1056001 100         accum = SvNV_nomg(aux[auxi].sv);
46 1056001           auxi++;
47 1056001           break;
48              
49             default:
50 13752020 50         croak("ARGH: initial prog instruction");
    100          
    50          
51             }
52              
53 16926027 100         while(*prog) {
54             char op = *(prog++);
55             switch(op) {
56             case '+':
57             case '-':
58             case '*':
59             case '/':
60             {
61             NV rhs;
62 10578017           switch(*(prog++)) {
63             case 'p':
64 7404014 100         rhs = SvNV_nomg(PAD_SV(aux[auxi].pad_offset));
65 7404014           auxi++;
66 7404014           break;
67             case 'c':
68 0 0         rhs = SvNV_nomg(aux[auxi].sv);
69 0           auxi++;
70 0           break;
71             case ')':
72             rhs = accum;
73 3174003           tmpi--;
74 3174003 50         accum = SvNV_nomg(PAD_SV(aux[tmpi].pad_offset));
75             break;
76             default:
77 0           croak("ARGH MULTIMATH arg %c\n", prog[-1]);
78             }
79              
80 10578017           switch(op) {
81             case '+':
82 3174006           accum += rhs;
83 3174006           break;
84             case '-':
85 1056003           accum -= rhs;
86 1056003           break;
87             case '*':
88 6348008           accum *= rhs;
89 6348008           break;
90             case '/':
91 0           accum /= rhs;
92 0           break;
93             }
94             break;
95             }
96              
97             case '(':
98             {
99             NV val;
100 3174003           switch(*(prog++)) {
101             case 'p':
102 3174003 50         val = SvNV_nomg(PAD_SV(aux[auxi].pad_offset));
103 3174003           auxi++;
104 3174003           break;
105             case 'c':
106 0 0         val = SvNV_nomg(aux[auxi].sv);
107 0           auxi++;
108 0           break;
109             }
110 3174003           sv_setnv(PAD_SV(aux[tmpi].pad_offset), accum);
111 3174003           tmpi++;
112             accum = val;
113 3174003           break;
114             }
115              
116             default:
117 13752020           croak("TODO: MULTIMATH %c\n", op);
118             }
119             }
120              
121 3174007 50         EXTEND(SP, 1);
122 3174007 100         PUSHn(accum);
123 3174007           RETURN;
124             }
125              
126             #define optimize_maths(start, final) MY_optimize_maths(aTHX_ start, final)
127 10           static OP *MY_optimize_maths(pTHX_ OP *start, OP *final)
128             {
129             OP *o;
130              
131             /* Phase 1: just count the number of aux items we need
132             * We'll need one for every constant or padix
133             * Also count the maximum stack height
134             */
135             U32 nitems = 1; /* aux[0] is the program */
136             U32 height = 0, maxheight = 0;
137              
138 64 50         for(o = start; o; o = o->op_next) {
139 64 50         switch(o->op_type) {
    100          
    50          
140             case OP_CONST:
141             case OP_PADSV:
142 37           nitems++;
143 37           height++;
144 37           break;
145              
146             case OP_ADD:
147             case OP_SUBTRACT:
148             case OP_MULTIPLY:
149             case OP_DIVIDE:
150 27           height--;
151 27           break;
152             }
153              
154 64 100         if(height > maxheight)
155             maxheight = height;
156              
157 64 100         if(o == final)
158             break;
159             }
160              
161 10           U32 ntmps = maxheight - 1;
162              
163             UNOP_AUX_item *aux;
164 10           Newx(aux, nitems + ntmps, UNOP_AUX_item);
165              
166 10           SV *prog = newSV(0);
167 10           sv_setpvs(prog, "");
168              
169             U32 tmpi = 1;
170             U32 auxi = 1 + ntmps;
171              
172             /* Phase 2: collect up the constants and padices, build the program string */
173             char lastarg = ')';
174             char operator;
175 64 50         for(o = start; o; o = o->op_next) {
176 64           switch(o->op_type) {
177             case OP_CONST:
178 2 50         if(lastarg != ')')
179 0           sv_catpvf(prog, "(%c", lastarg);
180             lastarg = 'c';
181 4           aux[auxi++].sv = SvREFCNT_inc(cSVOPo->op_sv);
182 2           break;
183              
184             case OP_PADSV:
185 35 100         if(lastarg != ')')
186 16           sv_catpvf(prog, "(%c", lastarg);
187             lastarg = 'p';
188 35           aux[auxi++].pad_offset = o->op_targ;
189 35           break;
190              
191             case OP_ADD:
192             operator = '+';
193             goto do_BINOP;
194             case OP_SUBTRACT:
195             operator = '-';
196 4           goto do_BINOP;
197             case OP_MULTIPLY:
198             operator = '*';
199 14           goto do_BINOP;
200             case OP_DIVIDE:
201             operator = '/';
202 0           goto do_BINOP;
203              
204             do_BINOP:
205 27           sv_catpvf(prog, "%c%c", operator, lastarg);
206             lastarg = ')';
207              
208             /* Steal a padtmp because that won't be using it */
209 27 100         if(tmpi <= ntmps)
210 16           aux[tmpi++].pad_offset = o->op_targ;
211             break;
212              
213              
214             default:
215 0           croak("ARGH unsure how to optimize this op\n");
216             }
217              
218 64 100         if(o == final)
219             break;
220             }
221              
222 10 50         if(SvPVX(prog)[0] != '(')
223 0           croak("ARGH: expected prog to begin (");
224              
225             /* Steal the buffer */
226 10           SET_UNOP_AUX_item_pv(aux[0], SvPVX(prog)); SvLEN(prog) = 0;
227             SvREFCNT_dec(prog);
228              
229 10           OP *retop = newUNOP_AUX(OP_CUSTOM, 0, NULL, aux);
230 10           retop->op_ppaddr = &pp_multimath;
231 10           retop->op_private = ntmps;
232 10           retop->op_targ = final->op_targ;
233              
234 10           return retop;
235             }
236              
237             static void rpeep_for_fastermaths(pTHX_ OP *o, bool init_enabled);
238 449           static void rpeep_for_fastermaths(pTHX_ OP *o, bool init_enabled)
239             {
240             bool enabled = init_enabled;
241              
242             OP *prevo = NULL;
243              
244             /* In some cases (e.g. while(1) { ... } ) the ->op_next chain actually
245             * forms a closed loop. In order to detect this loop and break out we'll
246             * advance the `slowo` pointer half as fast as o. If o is ever equal to
247             * slowo then we have reached a cycle and should stop
248             */
249             OP *slowo = NULL;
250             int slowotick = 0;
251              
252 9655 100         while(o && o != slowo) {
253 9206 100         if(o->op_type == OP_NEXTSTATE) {
254 1270           SV *sv = cop_hints_fetch_pvs(cCOPo, "Faster::Maths/faster", 0);
255 1270 50         enabled = sv && sv != &PL_sv_placeholder && SvTRUE(sv);
    100          
    50          
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    0          
    0          
    50          
    0          
256 1270           goto next_o;
257             }
258 7936 100         if(!enabled)
259             goto next_o;
260              
261             /* There are no BASEOP mathsy ops, so any optimizable sequence necessarily
262             * starts with an argument
263             */
264 145           switch(o->op_type) {
265             case OP_CONST:
266             case OP_PADSV:
267             break;
268              
269             case OP_OR:
270             case OP_AND:
271             case OP_DOR:
272             #if HAVE_PERL_VERSION(5,32,0)
273             case OP_CMPCHAIN_AND:
274             #endif
275             case OP_COND_EXPR:
276             case OP_MAPWHILE:
277             case OP_ANDASSIGN:
278             case OP_ORASSIGN:
279             case OP_DORASSIGN:
280             case OP_RANGE:
281             case OP_ONCE:
282             #if HAVE_PERL_VERSION(5,26,0)
283             case OP_ARGDEFELEM:
284             #endif
285             /* Optimize on the righthand side of `or` / `and` operators and other.
286             * similar cases. This might catch more things than perl's own
287             * recursion inside because simple expressions don't begin with an
288             * OP_NEXTSTATE
289             */
290 7 50         if(cLOGOPo->op_other && cLOGOPo->op_other->op_type != OP_NEXTSTATE)
    100          
291 4           rpeep_for_fastermaths(aTHX_ cLOGOPo->op_other, enabled);
292             goto next_o;
293              
294             default:
295             goto next_o;
296             }
297              
298             /* Find a sequence of mathsy args/ops we can optimize */
299             OP *final = NULL; /* the final op in the optimizable chain */
300             U32 final_opcount = 0; /* total number of operations we found until final */
301             {
302             U32 opcount = 0;
303             U32 height = 0; /* running height of the stack */
304              
305             OP *scout;
306 201 50         for(scout = o; scout; scout = scout->op_next) {
307 201 100         switch(scout->op_type) {
    100          
    100          
308             /* OPs that push 1 argument */
309             case OP_CONST:
310             case OP_PADSV:
311 113           height++;
312 113           break;
313              
314             /* BINOPs that consume 2, push 1 */
315             case OP_ADD:
316             case OP_SUBTRACT:
317             case OP_MULTIPLY:
318             case OP_DIVIDE:
319 33 50         if(height < 2)
320             /* We never had enough arguments, meaning any of the initial
321             * ones for this op must have been of a kind we don't recognise
322             * Abort and go to the next outer loop; we'll pick up an
323             * optimizable inner sub-chain again later
324             */
325             goto next_o;
326              
327 33           opcount++;
328 33           height--;
329 33 100         if(height == 1) {
330             final = scout;
331             final_opcount = opcount;
332             }
333             break;
334              
335             default:
336 55 100         if(!final)
337             /* We never finished on an op that would give us a height of 1,
338             * which means we probably took too many initial argument ops.
339             * Abort now and go to the next outer loop; we'll pick up an
340             * optimizable inner sub-chain again later
341             */
342             goto next_o;
343              
344             goto done_scout;
345             }
346             }
347             done_scout:
348             ;
349             }
350              
351             /* If we found fewer than 2 operations there's no point optimizing them */
352 10 50         if(final_opcount < 2)
353             goto next_o;
354              
355             /* At this point we now know that the sequence o to final consists of
356             * optimizable ops yielding a single final scalar answer.
357             */
358 10           OP *newo = optimize_maths(o, final);
359              
360 10           newo->op_next = final->op_next;
361 10 100         if(prevo)
362 9           prevo->op_next = o = newo;
363             else {
364             /* we optimized starting at the very first op in this chain */
365 1           o->op_targ = o->op_type;
366 1           o->op_type = OP_NULL;
367 1           o->op_next = newo;
368              
369             o = newo;
370             }
371              
372             next_o:
373 9206 100         if(!slowo)
374             slowo = o;
375 8757 100         else if((slowotick++) % 2)
376 4268           slowo = slowo->op_next;
377              
378             prevo = o;
379 9206           o = o->op_next;
380             }
381 449           }
382              
383             static void (*next_rpeepp)(pTHX_ OP *o);
384              
385             static void
386 447           my_rpeepp(pTHX_ OP *o)
387             {
388 447 100         if(!o)
389             return;
390              
391 445           (*next_rpeepp)(aTHX_ o);
392              
393 445           rpeep_for_fastermaths(aTHX_ o, FALSE);
394             }
395              
396             MODULE = Faster::Maths PACKAGE = Faster::Maths
397              
398             BOOT:
399             /* TODO: find the correct wrapper function for this */
400 3           next_rpeepp = PL_rpeepp;
401 3           PL_rpeepp = &my_rpeepp;
402              
403 3           XopENTRY_set(&xop_multimath, xop_name, "multimath");
404 3           XopENTRY_set(&xop_multimath, xop_desc,
405             "combined maths operations");
406 3           XopENTRY_set(&xop_multimath, xop_class, OA_UNOP_AUX);
407 3           Perl_custom_op_register(aTHX_ &pp_multimath, &xop_multimath);