File Coverage

lib/Future/AsyncAwait/Hooks.xs
Criterion Covered Total %
statement 81 83 97.5
branch 24 36 66.6
condition n/a
subroutine n/a
pod n/a
total 105 119 88.2


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, 2023 -- leonerd@leonerd.org.uk
5             */
6             #include "EXTERN.h"
7             #include "perl.h"
8             #include "XSUB.h"
9              
10             #include "AsyncAwait.h"
11              
12             #include "XSParseKeyword.h"
13              
14             #include "perl-backcompat.c.inc"
15             #include "forbid_outofblock_ops.c.inc"
16             #include "newOP_CUSTOM.c.inc"
17              
18             enum {
19             HOOK_SUSPEND = 1,
20             HOOK_RESUME,
21             };
22              
23 14           static void S_call_block_noargs(pTHX_ SV *blocksv)
24             {
25 14 50         OP *start = NUM2PTR(OP *, SvUV(blocksv));
26 14           I32 was_cxstack_ix = cxstack_ix;
27              
28 14           cx_pushblock(CXt_BLOCK, G_VOID, PL_stack_sp, PL_savestack_ix);
29 14           ENTER;
30 14           SAVETMPS;
31              
32 14           SAVEOP();
33 14           PL_op = start;
34 14           CALLRUNOPS(aTHX);
35              
36 14 50         FREETMPS;
37 14           LEAVE;
38              
39 14 50         if(cxstack_ix != was_cxstack_ix + 1) {
40 0           croak("panic: A non-local control flow operation exited a suspend/resume block");
41             }
42              
43 14           PERL_CONTEXT *cx = CX_CUR();
44 14           PL_stack_sp = PL_stack_base + cx->blk_oldsp;
45              
46 14           dounwind(was_cxstack_ix);
47 14           }
48             #define call_block_noargs(blocksv) S_call_block_noargs(aTHX_ blocksv)
49              
50 5           static void hook_pre_suspend(pTHX_ CV *cv, HV *modhookdata, void *hookdata)
51             {
52 5           SV **svp = hv_fetchs(modhookdata, "Future::AsyncAwait::Hooks/hooklist", 0);
53 5 50         if(!svp)
54             return;
55              
56 5           AV *hooklist = (AV *)*svp;
57 5           svp = AvARRAY(hooklist);
58              
59             I32 i;
60 19 50         for(i = 0; i <= AvFILL(hooklist); i += 2) {
    100          
61 14 50         int type = SvIV(svp[i]);
62 14 100         if(type == HOOK_SUSPEND)
63 7           call_block_noargs(svp[i+1]);
64             }
65             }
66              
67 5           static void hook_post_resume(pTHX_ CV *cv, HV *modhookdata, void *hookdata)
68             {
69 5           SV **svp = hv_fetchs(modhookdata, "Future::AsyncAwait::Hooks/hooklist", 0);
70 5 50         if(!svp)
71             return;
72              
73 5           AV *hooklist = (AV *)*svp;
74 5           svp = AvARRAY(hooklist);
75              
76             I32 i;
77 19 50         for(i = AvFILL(hooklist)-1; i >= 0; i -= 2) {
    100          
78 14 50         int type = SvIV(svp[i]);
79 14 100         if(type == HOOK_RESUME)
80 7           call_block_noargs(svp[i+1]);
81             }
82             }
83              
84             static const struct AsyncAwaitHookFuncs faa_hooks = {
85             .pre_suspend = &hook_pre_suspend,
86             .post_resume = &hook_post_resume,
87             };
88              
89             #define SAVEAVLEN(av) S_save_avlen(aTHX_ av)
90             /* This would be a lot neater if perl had a SAVEFUNCANY2() */
91             struct AvWithLength {
92             AV *av;
93             U32 len;
94             };
95 14           void restore_av_len(pTHX_ void *_avl)
96             {
97 14           struct AvWithLength *avl = _avl;
98 14           AV *av = avl->av;
99              
100 56 50         while(av_count(av) > avl->len)
    100          
101 28           SvREFCNT_dec(av_pop(av));
102              
103 14           Safefree(avl);
104 14           }
105 14           static void S_save_avlen(pTHX_ AV *av)
106             {
107             struct AvWithLength *avl;
108 14           Newx(avl, 1, struct AvWithLength);
109              
110 14           avl->av = av;
111 14 50         avl->len = av_count(av);
112              
113 14           SAVEDESTRUCTOR_X(restore_av_len, avl);
114 14           }
115              
116 14           static OP *pp_pushhook(pTHX)
117             {
118 14           OP *blockstart = cLOGOP->op_other;
119 14           int type = PL_op->op_private;
120              
121 14           HV *modhookdata = future_asyncawait_get_modhookdata(find_runcv(0), FAA_MODHOOK_CREATE, PL_op->op_targ);
122 14 50         if(!modhookdata)
123 0           croak("panic: expected modhookdata");
124              
125             AV *hooklist;
126 14           SV **svp = hv_fetchs(modhookdata, "Future::AsyncAwait::Hooks/hooklist", 0);
127 14 100         if(svp)
128 10           hooklist = (AV *)*svp;
129             else
130 4           hv_stores(modhookdata, "Future::AsyncAwait::Hooks/hooklist", (SV *)(hooklist = newAV()));
131              
132 14           SAVEAVLEN(hooklist);
133              
134 14           av_push(hooklist, newSViv(type));
135             /* We can't push an OP * to the AV, but we can wrap it */
136 14           av_push(hooklist, newSVuv(PTR2UV(blockstart)));
137              
138 14           return PL_op->op_next;
139             }
140              
141 12           static OP *build_pushhook_op(pTHX_ OP *block, int phase, const char *name)
142             {
143 12           forbid_outofblock_ops(block, name);
144              
145 12           OP *o = newLOGOP_CUSTOM(&pp_pushhook, 0,
146             newOP(OP_NULL, 0), block);
147              
148             /* The actual pp_pushhook LOGOP is the op_first of o */
149 12           LOGOP *pushhooko = (LOGOP *)cUNOPo->op_first;
150 12           pushhooko->op_targ = future_asyncawait_make_precreate_padix();
151 12           pushhooko->op_private = phase;
152              
153             /* ensure the block will terminate properly */
154 12           block->op_next = NULL;
155              
156 12           return o;
157             }
158              
159 6           static int build_suspend(pTHX_ OP **out, XSParseKeywordPiece *arg0, void *hookdata)
160             {
161 6           OP *block = arg0->op;
162              
163 6           *out = build_pushhook_op(aTHX_ block, HOOK_SUSPEND, "a suspend block");
164              
165 6           return KEYWORD_PLUGIN_STMT;
166             }
167              
168 6           static int build_resume(pTHX_ OP **out, XSParseKeywordPiece *arg0, void *hookdata)
169             {
170 6           OP *block = arg0->op;
171              
172 6           *out = build_pushhook_op(aTHX_ block, HOOK_RESUME, "a resume block");
173              
174 6           return KEYWORD_PLUGIN_STMT;
175             }
176              
177             static const struct XSParseKeywordHooks hooks_suspend = {
178             .permit_hintkey = "Future::AsyncAwait::Hooks/suspend",
179             .piece1 = XPK_BLOCK,
180             .build1 = &build_suspend,
181             };
182              
183             static const struct XSParseKeywordHooks hooks_resume = {
184             .permit_hintkey = "Future::AsyncAwait::Hooks/resume",
185             .piece1 = XPK_BLOCK,
186             .build1 = &build_resume,
187             };
188              
189             MODULE = Future::AsyncAwait::Hooks PACKAGE = Future::AsyncAwait::Hooks
190              
191             BOOT:
192 2           boot_future_asyncawait(0.64);
193 2           boot_xs_parse_keyword(0.13);
194              
195 2           register_future_asyncawait_hook(&faa_hooks, NULL);
196              
197 2           register_xs_parse_keyword("suspend", &hooks_suspend, NULL);
198 2           register_xs_parse_keyword("resume", &hooks_resume, NULL);