File Coverage

lib/Faster/Maths.xs
Criterion Covered Total %
statement 107 123 86.9
branch 72 126 57.1
condition n/a
subroutine n/a
pod n/a
total 179 249 71.8


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 2112007           static OP *pp_multimath(pTHX)
25             {
26 2112007           dSP;
27 2112007           UNOP_AUX_item *aux = cUNOP_AUX->op_aux;
28              
29 2112007           char *prog = GET_UNOP_AUX_item_pv(aux[0]) + 1; /* skip leading '(' */
30 2112007           U32 ntmps = PL_op->op_private;
31              
32             U32 tmpi = 1;
33 2112007           SV *accum = PAD_SV(aux[tmpi].pad_offset);
34              
35 2112007           U32 auxi = 1 + ntmps;
36              
37 2112007           switch(*(prog++)) {
38             case 'p':
39 1056006 100         sv_setnv(accum, SvNV_nomg(PAD_SV(aux[auxi].pad_offset)));
40 1056006           auxi++;
41 2112007           break;
42              
43             case 'c':
44 1056001 100         sv_setnv(accum, SvNV_nomg(aux[auxi].sv));
45 1056001           auxi++;
46 1056001           break;
47              
48             default:
49 9504020 50         croak("ARGH: initial prog instruction");
    100          
    50          
50             }
51              
52 11616027 100         while(*prog) {
53             char op = *(prog++);
54             switch(op) {
55             case '+':
56             case '-':
57             case '*':
58             case '/':
59             {
60             SV *rhs;
61 7392017           switch(*(prog++)) {
62             case 'p':
63 5280014           rhs = PAD_SV(aux[auxi].pad_offset);
64 5280014           auxi++;
65 5280014           break;
66             case 'c':
67 0           rhs = aux[auxi].sv;
68 0           auxi++;
69 0           break;
70             case ')':
71             rhs = accum;
72 2112003           tmpi--;
73 2112003           accum = PAD_SV(aux[tmpi].pad_offset);
74 2112003           break;
75             default:
76 0           croak("ARGH MULTIMATH arg %c\n", prog[-1]);
77             }
78              
79 7392017           switch(op) {
80             case '+':
81 2112006 50         sv_setnv(accum, SvNV_nomg(accum) + SvNV_nomg(rhs));
    100          
82 9504020           break;
83             case '-':
84 1056003 50         sv_setnv(accum, SvNV_nomg(accum) - SvNV_nomg(rhs));
    50          
85 1056003           break;
86             case '*':
87 4224008 50         sv_setnv(accum, SvNV_nomg(accum) * SvNV_nomg(rhs));
    50          
88 4224008           break;
89             case '/':
90 0 0         sv_setnv(accum, SvNV_nomg(accum) / SvNV_nomg(rhs));
    0          
91 0           break;
92             }
93             break;
94             }
95              
96             case '(':
97             {
98             SV *val;
99 2112003           switch(*(prog++)) {
100             case 'p':
101 2112003           val = PAD_SV(aux[auxi].pad_offset);
102 2112003           auxi++;
103 2112003           break;
104             case 'c':
105 0           val = aux[auxi].sv;
106 0           auxi++;
107 0           break;
108             }
109 2112003           tmpi++;
110 2112003           accum = PAD_SV(aux[tmpi].pad_offset);
111 2112003 50         sv_setnv(accum, SvNV_nomg(val));
112 2112003           break;
113             }
114              
115             default:
116 0           croak("TODO: MULTIMATH %c\n", op);
117             }
118             }
119              
120 2112007 50         EXTEND(SP, 1);
121 2112007           PUSHs(accum);
122 2112007           RETURN;
123             }
124              
125             #define optimize_maths(start, final) MY_optimize_maths(aTHX_ start, final)
126 9           static OP *MY_optimize_maths(pTHX_ OP *start, OP *final)
127             {
128             OP *o;
129              
130             /* Phase 1: just count the number of aux items we need
131             * We'll need one for every constant or padix
132             * Also count the maximum stack height
133             */
134             U32 nitems = 1; /* aux[0] is the program */
135             U32 height = 0, maxheight = 0;
136              
137 57 50         for(o = start; o; o = o->op_next) {
138 57 50         switch(o->op_type) {
    100          
    50          
139             case OP_CONST:
140             case OP_PADSV:
141 33           nitems++;
142 33           height++;
143 33           break;
144              
145             case OP_ADD:
146             case OP_SUBTRACT:
147             case OP_MULTIPLY:
148             case OP_DIVIDE:
149 24           height--;
150 24           break;
151             }
152              
153 57 100         if(height > maxheight)
154             maxheight = height;
155              
156 57 100         if(o == final)
157             break;
158             }
159              
160 9           U32 ntmps = maxheight - 1;
161              
162             UNOP_AUX_item *aux;
163 9           Newx(aux, nitems + ntmps, UNOP_AUX_item);
164              
165 9           SV *prog = newSV(0);
166 9           sv_setpvs(prog, "");
167              
168             U32 tmpi = 1;
169             U32 auxi = 1 + ntmps;
170              
171             /* Phase 2: collect up the constants and padices, build the program string */
172             char lastarg = ')';
173             char operator;
174 57 50         for(o = start; o; o = o->op_next) {
175 57           switch(o->op_type) {
176             case OP_CONST:
177 2 50         if(lastarg != ')')
178 0           sv_catpvf(prog, "(%c", lastarg);
179             lastarg = 'c';
180 4           aux[auxi++].sv = SvREFCNT_inc(cSVOPo->op_sv);
181 2           break;
182              
183             case OP_PADSV:
184 31 100         if(lastarg != ')')
185 14           sv_catpvf(prog, "(%c", lastarg);
186             lastarg = 'p';
187 31           aux[auxi++].pad_offset = o->op_targ;
188 31           break;
189              
190             case OP_ADD:
191             operator = '+';
192             goto do_BINOP;
193             case OP_SUBTRACT:
194             operator = '-';
195 4           goto do_BINOP;
196             case OP_MULTIPLY:
197             operator = '*';
198 12           goto do_BINOP;
199             case OP_DIVIDE:
200             operator = '/';
201 0           goto do_BINOP;
202              
203             do_BINOP:
204 24           sv_catpvf(prog, "%c%c", operator, lastarg);
205             lastarg = ')';
206              
207             /* Steal a padtmp because that won't be using it */
208 24 100         if(tmpi <= ntmps)
209 14           aux[tmpi++].pad_offset = o->op_targ;
210             break;
211              
212              
213             default:
214 0           croak("ARGH unsure how to optimize this op\n");
215             }
216              
217 57 100         if(o == final)
218             break;
219             }
220              
221 9 50         if(SvPVX(prog)[0] != '(')
222 0           croak("ARGH: expected prog to begin (");
223              
224             /* Steal the buffer */
225 9           SET_UNOP_AUX_item_pv(aux[0], SvPVX(prog)); SvLEN(prog) = 0;
226             SvREFCNT_dec(prog);
227              
228 9           OP *retop = newUNOP_AUX(OP_CUSTOM, 0, NULL, aux);
229 9           retop->op_ppaddr = &pp_multimath;
230 9           retop->op_private = ntmps;
231              
232 9           return retop;
233             }
234              
235             static void (*next_rpeepp)(pTHX_ OP *o);
236              
237             static void
238 447           my_rpeepp(pTHX_ OP *o)
239             {
240 447 100         if(!o)
241             return;
242              
243 445           (*next_rpeepp)(aTHX_ o);
244              
245             bool enabled = FALSE;
246              
247             OP *prevo = NULL;
248              
249 9621 100         while(o) {
250 9176 100         if(o->op_type == OP_NEXTSTATE) {
251 1266           SV *sv = cop_hints_fetch_pvs(cCOPo, "Faster::Maths/faster", 0);
252 1266 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          
253 1266           goto next_o;
254             }
255 7910 100         if(!enabled)
256             goto next_o;
257              
258             /* There are no BASEOP mathsy ops, so any optimizable sequence necessarily
259             * starts with an argument
260             */
261 114 100         switch(o->op_type) {
262             case OP_CONST:
263             case OP_PADSV:
264             break;
265              
266             default:
267             goto next_o;
268             }
269              
270             /* Find a sequence of mathsy args/ops we can optimize */
271             OP *final = NULL; /* the final op in the optimizable chain */
272             U32 final_opcount = 0; /* total number of operations we found until final */
273             {
274             U32 opcount = 0;
275             U32 height = 0; /* running height of the stack */
276              
277             OP *scout;
278 176 50         for(scout = o; scout; scout = scout->op_next) {
279 176 100         switch(scout->op_type) {
    100          
    100          
280             /* OPs that push 1 argument */
281             case OP_CONST:
282             case OP_PADSV:
283 100           height++;
284 100           break;
285              
286             /* BINOPs that consume 2, push 1 */
287             case OP_ADD:
288             case OP_SUBTRACT:
289             case OP_MULTIPLY:
290             case OP_DIVIDE:
291 30 50         if(height < 2)
292             /* We never had enough arguments, meaning any of the initial
293             * ones for this op must have been of a kind we don't recognise
294             * Abort and go to the next outer loop; we'll pick up an
295             * optimizable inner sub-chain again later
296             */
297             goto next_o;
298              
299 30           opcount++;
300 30           height--;
301 30 100         if(height == 1) {
302             final = scout;
303             final_opcount = opcount;
304             }
305             break;
306              
307             default:
308 46 100         if(!final)
309             /* We never finished on an op that would give us a height of 1,
310             * which means we probably took too many initial argument ops.
311             * Abort now and go to the next outer loop; we'll pick up an
312             * optimizable inner sub-chain again later
313             */
314             goto next_o;
315              
316             goto done_scout;
317             }
318             }
319             done_scout:
320             ;
321             }
322              
323             /* If we found fewer than 2 operations there's no point optimizing them */
324 9 50         if(final_opcount < 2)
325             goto next_o;
326              
327             /* At this point we now know that the sequence o to final consists of
328             * optimizable ops yielding a single final scalar answer.
329             */
330 9           OP *newo = optimize_maths(o, final);
331              
332 9           newo->op_next = final->op_next;
333 9 50         if(prevo)
334 9           prevo->op_next = o = newo;
335             else {
336             /* we optimized starting at the very first op in this chain */
337 0           o->op_type = OP_NULL;
338 0           o->op_next = newo;
339              
340             o = newo;
341             }
342              
343             next_o:
344             prevo = o;
345 9176           o = o->op_next;
346             }
347             }
348              
349             MODULE = Faster::Maths PACKAGE = Faster::Maths
350              
351             BOOT:
352             /* TODO: find the correct wrapper function for this */
353 3           next_rpeepp = PL_rpeepp;
354 3           PL_rpeepp = &my_rpeepp;
355              
356 3           XopENTRY_set(&xop_multimath, xop_name, "multimath");
357 3           XopENTRY_set(&xop_multimath, xop_desc,
358             "combined maths operations");
359 3           XopENTRY_set(&xop_multimath, xop_class, OA_UNOP_AUX);
360 3           Perl_custom_op_register(aTHX_ &pp_multimath, &xop_multimath);