Commit | Line | Data |
---|---|---|
b82b06b8 FC |
1 | #define PERL_NO_GET_CONTEXT /* we want efficiency */ |
2 | #include "EXTERN.h" | |
3 | #include "perl.h" | |
4 | #include "XSUB.h" | |
5 | ||
6 | /* ... op => info map ................................................. */ | |
7 | ||
8 | typedef struct { | |
9 | OP *(*old_pp)(pTHX); | |
10 | IV base; | |
11 | } ab_op_info; | |
12 | ||
13 | #define PTABLE_NAME ptable_map | |
14 | #define PTABLE_VAL_FREE(V) PerlMemShared_free(V) | |
15 | #include "ptable.h" | |
16 | #define ptable_map_store(T, K, V) ptable_map_store(aPTBLMS_ (T), (K), (V)) | |
17 | ||
18 | STATIC ptable *ab_op_map = NULL; | |
19 | ||
20 | #ifdef USE_ITHREADS | |
21 | STATIC perl_mutex ab_op_map_mutex; | |
22 | #endif | |
23 | ||
24 | STATIC const ab_op_info *ab_map_fetch(const OP *o, ab_op_info *oi) { | |
25 | const ab_op_info *val; | |
26 | ||
27 | #ifdef USE_ITHREADS | |
28 | MUTEX_LOCK(&ab_op_map_mutex); | |
29 | #endif | |
30 | ||
f39efbfa | 31 | val = (ab_op_info *)ptable_fetch(ab_op_map, o); |
b82b06b8 FC |
32 | if (val) { |
33 | *oi = *val; | |
34 | val = oi; | |
35 | } | |
36 | ||
37 | #ifdef USE_ITHREADS | |
38 | MUTEX_UNLOCK(&ab_op_map_mutex); | |
39 | #endif | |
40 | ||
41 | return val; | |
42 | } | |
43 | ||
44 | STATIC const ab_op_info *ab_map_store_locked( | |
45 | pPTBLMS_ const OP *o, OP *(*old_pp)(pTHX), IV base | |
46 | ) { | |
47 | #define ab_map_store_locked(O, PP, B) \ | |
48 | ab_map_store_locked(aPTBLMS_ (O), (PP), (B)) | |
49 | ab_op_info *oi; | |
50 | ||
f39efbfa TC |
51 | if (!(oi = (ab_op_info *)ptable_fetch(ab_op_map, o))) { |
52 | oi = (ab_op_info *)PerlMemShared_malloc(sizeof *oi); | |
b82b06b8 FC |
53 | ptable_map_store(ab_op_map, o, oi); |
54 | } | |
55 | ||
56 | oi->old_pp = old_pp; | |
57 | oi->base = base; | |
58 | return oi; | |
59 | } | |
60 | ||
61 | STATIC void ab_map_store( | |
62 | pPTBLMS_ const OP *o, OP *(*old_pp)(pTHX), IV base) | |
63 | { | |
64 | #define ab_map_store(O, PP, B) ab_map_store(aPTBLMS_ (O),(PP),(B)) | |
65 | ||
66 | #ifdef USE_ITHREADS | |
67 | MUTEX_LOCK(&ab_op_map_mutex); | |
68 | #endif | |
69 | ||
70 | ab_map_store_locked(o, old_pp, base); | |
71 | ||
72 | #ifdef USE_ITHREADS | |
73 | MUTEX_UNLOCK(&ab_op_map_mutex); | |
74 | #endif | |
75 | } | |
76 | ||
77 | STATIC void ab_map_delete(pTHX_ const OP *o) { | |
78 | #define ab_map_delete(O) ab_map_delete(aTHX_ (O)) | |
79 | #ifdef USE_ITHREADS | |
80 | MUTEX_LOCK(&ab_op_map_mutex); | |
81 | #endif | |
82 | ||
83 | ptable_map_store(ab_op_map, o, NULL); | |
84 | ||
85 | #ifdef USE_ITHREADS | |
86 | MUTEX_UNLOCK(&ab_op_map_mutex); | |
87 | #endif | |
88 | } | |
89 | ||
90 | /* ... $[ Implementation .............................................. */ | |
91 | ||
92 | #define hintkey "$[" | |
93 | #define hintkey_len (sizeof(hintkey)-1) | |
94 | ||
95 | STATIC SV * ab_hint(pTHX_ const bool create) { | |
96 | #define ab_hint(c) ab_hint(aTHX_ c) | |
97 | dVAR; | |
98 | SV **val | |
99 | = hv_fetch(GvHV(PL_hintgv), hintkey, hintkey_len, create); | |
100 | if (!val) | |
101 | return 0; | |
102 | return *val; | |
103 | } | |
104 | ||
105 | STATIC IV current_base(pTHX) { | |
106 | #define current_base() current_base(aTHX) | |
107 | SV *hsv = ab_hint(0); | |
108 | if (!hsv || !SvOK(hsv)) return 0; | |
109 | return SvIV(hsv); | |
110 | } | |
111 | ||
112 | STATIC void set_arybase_to(pTHX_ IV base) { | |
113 | #define set_arybase_to(base) set_arybase_to(aTHX_ (base)) | |
114 | dVAR; | |
115 | SV *hsv = ab_hint(1); | |
116 | sv_setiv_mg(hsv, base); | |
117 | } | |
118 | ||
546dd830 | 119 | #define old_ck(opname) STATIC OP *(*ab_old_ck_##opname)(pTHX_ OP *) = 0 |
b82b06b8 FC |
120 | old_ck(sassign); |
121 | old_ck(aassign); | |
122 | old_ck(aelem); | |
123 | old_ck(aslice); | |
124 | old_ck(lslice); | |
125 | old_ck(av2arylen); | |
126 | old_ck(splice); | |
127 | old_ck(keys); | |
128 | old_ck(each); | |
129 | old_ck(substr); | |
130 | old_ck(rindex); | |
131 | old_ck(index); | |
132 | old_ck(pos); | |
133 | ||
134 | STATIC bool ab_op_is_dollar_bracket(pTHX_ OP *o) { | |
135 | #define ab_op_is_dollar_bracket(o) ab_op_is_dollar_bracket(aTHX_ (o)) | |
136 | OP *c; | |
137 | return o->op_type == OP_RV2SV && (o->op_flags & OPf_KIDS) | |
138 | && (c = cUNOPx(o)->op_first) | |
139 | && c->op_type == OP_GV | |
45aff279 | 140 | && GvSTASH(cGVOPx_gv(c)) == PL_defstash |
b82b06b8 FC |
141 | && strEQ(GvNAME(cGVOPx_gv(c)), "["); |
142 | } | |
143 | ||
144 | STATIC void ab_neuter_dollar_bracket(pTHX_ OP *o) { | |
145 | #define ab_neuter_dollar_bracket(o) ab_neuter_dollar_bracket(aTHX_ (o)) | |
146 | OP *oldc, *newc; | |
147 | /* | |
148 | * Must replace the core's $[ with something that can accept assignment | |
149 | * of non-zero value and can be local()ised. Simplest thing is a | |
150 | * different global variable. | |
151 | */ | |
152 | oldc = cUNOPx(o)->op_first; | |
153 | newc = newGVOP(OP_GV, 0, | |
184a899d | 154 | gv_fetchpvs("arybase::leftbrack", GV_ADDMULTI, SVt_PVGV)); |
b82b06b8 FC |
155 | cUNOPx(o)->op_first = newc; |
156 | op_free(oldc); | |
157 | } | |
158 | ||
159 | STATIC void ab_process_assignment(pTHX_ OP *left, OP *right) { | |
160 | #define ab_process_assignment(l, r) \ | |
161 | ab_process_assignment(aTHX_ (l), (r)) | |
162 | if (ab_op_is_dollar_bracket(left) && right->op_type == OP_CONST) { | |
163 | set_arybase_to(SvIV(cSVOPx_sv(right))); | |
164 | ab_neuter_dollar_bracket(left); | |
36b2db7e FC |
165 | Perl_ck_warner_d(aTHX_ |
166 | packWARN(WARN_DEPRECATED), "Use of assignment to $[ is deprecated" | |
167 | ); | |
b82b06b8 FC |
168 | } |
169 | } | |
170 | ||
171 | STATIC OP *ab_ck_sassign(pTHX_ OP *o) { | |
172 | o = (*ab_old_ck_sassign)(aTHX_ o); | |
02523b6e | 173 | if (o->op_type == OP_SASSIGN) { |
b82b06b8 FC |
174 | OP *right = cBINOPx(o)->op_first; |
175 | OP *left = right->op_sibling; | |
176 | if (left) ab_process_assignment(left, right); | |
b82b06b8 | 177 | } |
02523b6e | 178 | return o; |
b82b06b8 FC |
179 | } |
180 | ||
181 | STATIC OP *ab_ck_aassign(pTHX_ OP *o) { | |
182 | o = (*ab_old_ck_aassign)(aTHX_ o); | |
02523b6e | 183 | if (o->op_type == OP_AASSIGN) { |
b82b06b8 FC |
184 | OP *right = cBINOPx(o)->op_first; |
185 | OP *left = cBINOPx(right->op_sibling)->op_first->op_sibling; | |
186 | right = cBINOPx(right)->op_first->op_sibling; | |
187 | ab_process_assignment(left, right); | |
b82b06b8 | 188 | } |
02523b6e | 189 | return o; |
b82b06b8 FC |
190 | } |
191 | ||
192 | void | |
193 | tie(pTHX_ SV * const sv, SV * const obj, HV *const stash) | |
194 | { | |
195 | SV *rv = newSV_type(SVt_RV); | |
196 | ||
197 | SvRV_set(rv, obj ? SvREFCNT_inc_simple_NN(obj) : newSV(0)); | |
198 | SvROK_on(rv); | |
199 | sv_bless(rv, stash); | |
200 | ||
201 | sv_unmagic((SV *)sv, PERL_MAGIC_tiedscalar); | |
202 | sv_magic((SV *)sv, rv, PERL_MAGIC_tiedscalar, NULL, 0); | |
203 | SvREFCNT_dec(rv); /* As sv_magic increased it by one. */ | |
204 | } | |
205 | ||
206 | /* This function converts from base-based to 0-based an index to be passed | |
207 | as an argument. */ | |
208 | static IV | |
209 | adjust_index(IV index, IV base) | |
210 | { | |
211 | if (index >= base || index > -1) return index-base; | |
212 | return index; | |
213 | } | |
214 | /* This function converts from 0-based to base-based an index to | |
215 | be returned. */ | |
216 | static IV | |
217 | adjust_index_r(IV index, IV base) | |
218 | { | |
219 | return index + base; | |
220 | } | |
221 | ||
222 | #define replace_sv(sv,base) \ | |
223 | ((sv) = sv_2mortal(newSViv(adjust_index(SvIV(sv),base)))) | |
224 | #define replace_sv_r(sv,base) \ | |
225 | ((sv) = sv_2mortal(newSViv(adjust_index_r(SvIV(sv),base)))) | |
226 | ||
227 | static OP *ab_pp_basearg(pTHX) { | |
228 | dVAR; dSP; | |
229 | SV **firstp = NULL; | |
230 | SV **svp; | |
231 | UV count = 1; | |
232 | ab_op_info oi; | |
233 | ab_map_fetch(PL_op, &oi); | |
234 | ||
235 | switch (PL_op->op_type) { | |
236 | case OP_AELEM: | |
237 | firstp = SP; | |
238 | break; | |
239 | case OP_ASLICE: | |
240 | firstp = PL_stack_base + TOPMARK + 1; | |
241 | count = SP-firstp; | |
242 | break; | |
243 | case OP_LSLICE: | |
244 | firstp = PL_stack_base + *(PL_markstack_ptr-2)+1; | |
245 | count = TOPMARK - *(PL_markstack_ptr-2); | |
246 | if (GIMME != G_ARRAY) { | |
247 | firstp += count-1; | |
248 | count = 1; | |
249 | } | |
250 | break; | |
251 | case OP_SPLICE: | |
252 | if (SP - PL_stack_base - TOPMARK >= 2) | |
253 | firstp = PL_stack_base + TOPMARK + 2; | |
254 | else count = 0; | |
255 | break; | |
256 | case OP_SUBSTR: | |
257 | firstp = SP-(PL_op->op_private & 7)+2; | |
258 | break; | |
259 | default: | |
260 | DIE(aTHX_ | |
261 | "panic: invalid op type for arybase.xs:ab_pp_basearg: %d", | |
262 | PL_op->op_type); | |
263 | } | |
264 | svp = firstp; | |
265 | while (count--) replace_sv(*svp,oi.base), svp++; | |
266 | return (*oi.old_pp)(aTHX); | |
267 | } | |
268 | ||
269 | static OP *ab_pp_av2arylen(pTHX) { | |
270 | dSP; dVAR; | |
271 | SV *sv; | |
272 | ab_op_info oi; | |
273 | OP *ret; | |
274 | ab_map_fetch(PL_op, &oi); | |
275 | ret = (*oi.old_pp)(aTHX); | |
276 | if (PL_op->op_flags & OPf_MOD || LVRET) { | |
277 | sv = newSV(0); | |
278 | tie(aTHX_ sv, TOPs, gv_stashpv("arybase::mg",1)); | |
279 | SETs(sv); | |
280 | } | |
281 | else { | |
282 | SvGETMAGIC(TOPs); | |
283 | if (SvOK(TOPs)) replace_sv_r(TOPs, oi.base); | |
284 | } | |
285 | return ret; | |
286 | } | |
287 | ||
288 | static OP *ab_pp_keys(pTHX) { | |
289 | dVAR; dSP; | |
290 | ab_op_info oi; | |
291 | OP *retval; | |
292 | const I32 offset = SP - PL_stack_base; | |
293 | SV **svp; | |
294 | ab_map_fetch(PL_op, &oi); | |
295 | retval = (*oi.old_pp)(aTHX); | |
296 | if (GIMME_V == G_SCALAR) return retval; | |
297 | SPAGAIN; | |
298 | svp = PL_stack_base + offset; | |
299 | while (svp <= SP) replace_sv_r(*svp,oi.base), ++svp; | |
300 | return retval; | |
301 | } | |
302 | ||
303 | static OP *ab_pp_each(pTHX) { | |
304 | dVAR; dSP; | |
305 | ab_op_info oi; | |
306 | OP *retval; | |
307 | const I32 offset = SP - PL_stack_base; | |
308 | ab_map_fetch(PL_op, &oi); | |
309 | retval = (*oi.old_pp)(aTHX); | |
310 | SPAGAIN; | |
311 | if (GIMME_V == G_SCALAR) { | |
312 | if (SvOK(TOPs)) replace_sv_r(TOPs,oi.base); | |
313 | } | |
314 | else if (offset < SP - PL_stack_base) replace_sv_r(TOPm1s,oi.base); | |
315 | return retval; | |
316 | } | |
317 | ||
318 | static OP *ab_pp_index(pTHX) { | |
319 | dVAR; dSP; | |
320 | ab_op_info oi; | |
321 | OP *retval; | |
322 | ab_map_fetch(PL_op, &oi); | |
323 | if (MAXARG == 3 && TOPs) replace_sv(TOPs,oi.base); | |
324 | retval = (*oi.old_pp)(aTHX); | |
325 | SPAGAIN; | |
326 | replace_sv_r(TOPs,oi.base); | |
327 | return retval; | |
328 | } | |
329 | ||
330 | static OP *ab_ck_base(pTHX_ OP *o) | |
331 | { | |
332 | OP * (*old_ck)(pTHX_ OP *o) = 0; | |
333 | OP * (*new_pp)(pTHX) = ab_pp_basearg; | |
334 | switch (o->op_type) { | |
335 | case OP_AELEM : old_ck = ab_old_ck_aelem ; break; | |
336 | case OP_ASLICE : old_ck = ab_old_ck_aslice ; break; | |
337 | case OP_LSLICE : old_ck = ab_old_ck_lslice ; break; | |
338 | case OP_AV2ARYLEN: old_ck = ab_old_ck_av2arylen; break; | |
339 | case OP_SPLICE : old_ck = ab_old_ck_splice ; break; | |
340 | case OP_KEYS : old_ck = ab_old_ck_keys ; break; | |
341 | case OP_EACH : old_ck = ab_old_ck_each ; break; | |
342 | case OP_SUBSTR : old_ck = ab_old_ck_substr ; break; | |
343 | case OP_RINDEX : old_ck = ab_old_ck_rindex ; break; | |
344 | case OP_INDEX : old_ck = ab_old_ck_index ; break; | |
345 | case OP_POS : old_ck = ab_old_ck_pos ; break; | |
a3f353cf RU |
346 | default: |
347 | DIE(aTHX_ | |
348 | "panic: invalid op type for arybase.xs:ab_ck_base: %d", | |
349 | PL_op->op_type); | |
b82b06b8 FC |
350 | } |
351 | o = (*old_ck)(aTHX_ o); | |
352 | /* We need two switch blocks, as the type may have changed. */ | |
353 | switch (o->op_type) { | |
354 | case OP_AELEM : | |
355 | case OP_ASLICE : | |
356 | case OP_LSLICE : | |
357 | case OP_SPLICE : | |
358 | case OP_SUBSTR : break; | |
359 | case OP_POS : | |
360 | case OP_AV2ARYLEN: new_pp = ab_pp_av2arylen ; break; | |
361 | case OP_AKEYS : new_pp = ab_pp_keys ; break; | |
362 | case OP_AEACH : new_pp = ab_pp_each ; break; | |
363 | case OP_RINDEX : | |
364 | case OP_INDEX : new_pp = ab_pp_index ; break; | |
365 | default: return o; | |
366 | } | |
367 | { | |
368 | IV const base = current_base(); | |
369 | if (base) { | |
370 | ab_map_store(o, o->op_ppaddr, base); | |
371 | o->op_ppaddr = new_pp; | |
372 | /* Break the aelemfast optimisation */ | |
373 | if (o->op_type == OP_AELEM && | |
374 | cBINOPo->op_first->op_sibling->op_type == OP_CONST) { | |
375 | cBINOPo->op_first->op_sibling | |
376 | = newUNOP(OP_NULL,0,cBINOPo->op_first->op_sibling); | |
377 | } | |
378 | } | |
379 | else ab_map_delete(o); | |
380 | } | |
381 | return o; | |
382 | } | |
383 | ||
384 | ||
385 | STATIC U32 ab_initialized = 0; | |
386 | ||
387 | /* --- XS ------------------------------------------------------------- */ | |
388 | ||
389 | MODULE = arybase PACKAGE = arybase | |
390 | PROTOTYPES: DISABLE | |
391 | ||
392 | BOOT: | |
393 | { | |
394 | GV *const gv = gv_fetchpvn("[", 1, GV_ADDMULTI|GV_NOTQUAL, SVt_PV); | |
395 | tie(aTHX_ GvSV(gv), NULL, GvSTASH(CvGV(cv))); | |
396 | ||
397 | if (!ab_initialized++) { | |
398 | ab_op_map = ptable_new(); | |
399 | #ifdef USE_ITHREADS | |
400 | MUTEX_INIT(&ab_op_map_mutex); | |
401 | #endif | |
402 | #define check(uc,lc,ck) ab_old_ck_##lc = PL_check[OP_##uc]; \ | |
403 | PL_check[OP_##uc] = ab_ck_##ck | |
404 | check(SASSIGN, sassign, sassign); | |
405 | check(AASSIGN, aassign, aassign); | |
406 | check(AELEM, aelem, base); | |
407 | check(ASLICE, aslice, base); | |
408 | check(LSLICE, lslice, base); | |
409 | check(AV2ARYLEN,av2arylen,base); | |
410 | check(SPLICE, splice, base); | |
411 | check(KEYS, keys, base); | |
412 | check(EACH, each, base); | |
413 | check(SUBSTR, substr, base); | |
414 | check(RINDEX, rindex, base); | |
415 | check(INDEX, index, base); | |
416 | check(POS, pos, base); | |
417 | } | |
418 | } | |
419 | ||
420 | void | |
421 | FETCH(...) | |
422 | PREINIT: | |
423 | SV *ret = cop_hints_fetch_pvs(PL_curcop, "$[", 0); | |
424 | PPCODE: | |
425 | if (!SvOK(ret)) mXPUSHi(0); | |
426 | else XPUSHs(ret); | |
427 | ||
428 | void | |
429 | STORE(SV *sv, IV newbase) | |
430 | PREINIT: | |
431 | SV *base = cop_hints_fetch_pvs(PL_curcop, "$[", 0); | |
432 | CODE: | |
433 | if (SvOK(base) ? SvIV(base) == newbase : !newbase) XSRETURN_EMPTY; | |
434 | Perl_croak(aTHX_ "That use of $[ is unsupported"); | |
435 | ||
436 | ||
437 | MODULE = arybase PACKAGE = arybase::mg | |
438 | PROTOTYPES: DISABLE | |
439 | ||
440 | void | |
441 | FETCH(SV *sv) | |
442 | PPCODE: | |
443 | if (!SvROK(sv) || SvTYPE(SvRV(sv)) >= SVt_PVAV) | |
444 | Perl_croak(aTHX_ "Not a SCALAR reference"); | |
445 | { | |
446 | SV *base = cop_hints_fetch_pvs(PL_curcop, "$[", 0); | |
447 | SvGETMAGIC(SvRV(sv)); | |
448 | if (!SvOK(SvRV(sv))) XSRETURN_UNDEF; | |
449 | mXPUSHi(adjust_index_r( | |
450 | SvIV_nomg(SvRV(sv)), SvOK(base)?SvIV(base):0 | |
451 | )); | |
452 | } | |
453 | ||
454 | void | |
455 | STORE(SV *sv, SV *newbase) | |
456 | CODE: | |
457 | if (!SvROK(sv) || SvTYPE(SvRV(sv)) >= SVt_PVAV) | |
458 | Perl_croak(aTHX_ "Not a SCALAR reference"); | |
459 | { | |
460 | SV *base = cop_hints_fetch_pvs(PL_curcop, "$[", 0); | |
461 | SvGETMAGIC(newbase); | |
462 | if (!SvOK(newbase)) SvSetMagicSV(SvRV(sv),&PL_sv_undef); | |
463 | else | |
464 | sv_setiv_mg( | |
465 | SvRV(sv), | |
466 | adjust_index(SvIV_nomg(newbase),SvOK(base)?SvIV(base):0) | |
467 | ); | |
468 | } |