ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/PApp-SQL/SQL.xs
Revision: 1.31
Committed: Thu Sep 9 13:57:41 2021 UTC (5 years ago) by root
Branch: MAIN
Changes since 1.30: +4 -2 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #include "EXTERN.h"
2     #include "perl.h"
3     #include "XSUB.h"
4    
5 root 1.22 /* import some stuff from DBIXS.h and DBI.xs */
6     #define DBIXS_VERSION 93
7     #define DBI_MAGIC '~'
8    
9     #define DBISTATE_PERLNAME "DBI::_dbistate"
10 root 1.24 #define DBISTATE_ADDRSV (perl_get_sv (DBISTATE_PERLNAME, 0x05))
11 root 1.22 #define DBIS_PUBLISHED_LVALUE (*(INT2PTR(dbistate_t**, &SvIVX(DBISTATE_ADDRSV))))
12    
13 root 1.24 static SV *sql_varchar, *sql_integer, *sql_double;
14     static SV *tmp_iv;
15    
16 root 1.22 struct dbistate_st {
17     #define DBISTATE_VERSION 94 /* Must change whenever dbistate_t does */
18     /* this must be the first member in structure */
19     void (*check_version) _((const char *name,
20     int dbis_cv, int dbis_cs, int need_dbixs_cv,
21     int drc_s, int dbc_s, int stc_s, int fdc_s));
22    
23     /* version and size are used to check for DBI/DBD version mis-match */
24     U16 version; /* version of this structure */
25     U16 size;
26     U16 xs_version; /* version of the overall DBIXS / DBD interface */
27     U16 spare_pad;
28     };
29     typedef struct dbistate_st dbistate_t;
30    
31     #define DBIcf_ACTIVE 0x000004 /* needs finish/disconnect before clear */
32    
33     typedef U32 imp_sth;
34    
35     /* not strictly part of the API... */
36     static imp_sth *
37     sth_get_imp (SV *sth)
38     {
39     MAGIC *mg = mg_find (SvRV (sth), PERL_MAGIC_tied);
40     sth = mg->mg_obj;
41     mg = mg_find (SvRV (sth), DBI_MAGIC);
42     return (imp_sth *)SvPVX (mg->mg_obj);
43     }
44    
45     #define DBI_STH_ACTIVE(imp) (*(imp) & DBIcf_ACTIVE)
46    
47     /* end of import section */
48    
49 root 1.3 #if (PERL_VERSION < 5) || ((PERL_VERSION == 5) && (PERL_SUBVERSION <= 6))
50 root 1.5 # define get_sv perl_get_sv
51 root 1.3 # define call_method perl_call_method
52 root 1.5 # define call_sv perl_call_sv
53 root 1.3 #endif
54    
55 root 1.11 #if (PERL_VERSION > 5) || ((PERL_VERSION == 5) && (PERL_SUBVERSION >= 6))
56 root 1.5 # define CAN_UTF8 1
57     #endif
58    
59 root 1.22 #define MAX_CACHED_STATEMENT_SIZE 2048
60 root 1.14
61 root 1.5 static SV *
62     sql_upgrade_utf8 (SV *sv)
63     {
64     #if CAN_UTF8
65 root 1.24 if (SvPOKp (sv))
66 root 1.5 sv_utf8_upgrade (sv);
67     #endif
68     return sv;
69     }
70    
71     static SV *
72 root 1.13 mortalcopy_and_maybe_force_utf8(int utf8, SV *sv)
73 root 1.5 {
74 root 1.13 sv = sv_mortalcopy (sv);
75 root 1.5 #if CAN_UTF8
76 root 1.24 if (utf8 && SvPOKp (sv))
77 root 1.5 SvUTF8_on (sv);
78     #endif
79     return sv;
80     }
81    
82     #define maybe_upgrade_utf8(utf8,sv) ((utf8) ? sql_upgrade_utf8 (sv) : (sv))
83    
84 root 1.2 #define is_dbh(sv) ((sv) && sv_isobject (sv) && sv_derived_from ((sv), "DBI::db"))
85 root 1.1
86 root 1.24 typedef struct mc_node
87     {
88     struct mc_node *next;
89     HV *stash;
90     U32 gen;
91    
92     /* DBH */
93     SV *prepare;
94    
95     /* STH */
96     SV *execute;
97     SV *bind_param;
98     SV *bind_columns;
99     SV *fetchrow_arrayref;
100     SV *fetchall_arrayref;
101     SV *finish;
102     } mc_node;
103    
104     static mc_node *first;
105    
106     static mc_node *
107     mc_find (HV *stash)
108     {
109     mc_node *mc;
110     U32 gen = PL_sub_generation;
111    
112     #ifdef HvMROMETA
113     gen += HvMROMETA (stash)->cache_gen;
114     #endif
115    
116     for (mc = first; mc; mc = mc->next)
117     if (mc->stash == stash && mc->gen == gen)
118     return mc;
119    
120     if (!mc)
121     {
122     Newz (0, mc, 1, mc_node);
123     mc->stash = stash;
124    
125     mc->next = first;
126     first = mc;
127     }
128     else
129     {
130     mc->execute =
131     mc->bind_param =
132     mc->bind_columns =
133     mc->fetchrow_arrayref =
134     mc->fetchall_arrayref =
135     mc->finish = 0;
136     }
137    
138     mc->gen = gen;
139    
140     return mc;
141     }
142    
143     static void
144     mc_cache (mc_node *mc, SV **method, const char *name)
145     {
146     *method = (SV *)gv_fetchmethod_autoload (mc->stash, name, 0);
147    
148     if (!method)
149 root 1.28 croak ("%s: method not found in stash, please report.", name);
150 root 1.24 }
151    
152     #define mc_cache(mc, method) mc_cache ((mc), &((mc)->method), # method)
153    
154     typedef struct lru_node
155     {
156 root 1.1 struct lru_node *next;
157     struct lru_node *prev;
158 root 1.24
159     U32 hash;
160 root 1.1 SV *dbh;
161     SV *sql;
162    
163     SV *sth;
164 root 1.22 imp_sth *sth_imp;
165 root 1.24
166     mc_node *mc;
167 root 1.1 } lru_node;
168    
169     static lru_node lru_list;
170     static int lru_size;
171     static int lru_maxsize;
172    
173 root 1.22 #define lru_init() lru_list.next = &lru_list; lru_list.prev = &lru_list /* other fields are zero */
174 root 1.1
175     /* this is primitive, yet effective */
176     /* the returned value must never be zero (or bad things will happen) */
177 root 1.24 static U32
178     lru_hash (SV *dbh, SV *sql)
179     {
180 root 1.31 /* use a variant of fnv1a */
181 root 1.24 STRLEN i, l;
182     char *b = SvPV (sql, l);
183 root 1.26 U32 hash = 2166136261U;
184 root 1.24
185     hash = (hash ^ (U32)dbh) * 16777619U;
186     hash = (hash ^ l) * 16777619U;
187    
188 root 1.31 /* start hashing at char 7, as this skips the "select " prefix */
189     /* also skip more and more octets */
190 root 1.24 for (i = 7; i < l; i += i >> 2)
191 root 1.31 hash = (hash ^ b [i]) * 16777619U;
192 root 1.24
193     return hash;
194     }
195 root 1.1
196     /* fetch and "use" */
197 root 1.24 static lru_node *
198 root 1.22 lru_fetch (SV *dbh, SV *sql)
199 root 1.1 {
200     lru_node *n;
201     U32 hash;
202    
203     dbh = SvRV (dbh);
204 root 1.24 hash = lru_hash (dbh, sql);
205 root 1.1
206     n = &lru_list;
207     do {
208     n = n->next;
209 root 1.22
210 root 1.1 if (!n->hash)
211     return 0;
212     } while (n->hash != hash
213 root 1.22 || DBI_STH_ACTIVE (n->sth_imp)
214 root 1.1 || !sv_eq (n->sql, sql)
215     || n->dbh != dbh);
216    
217     /* found, so return to the start of the list */
218     n->prev->next = n->next;
219     n->next->prev = n->prev;
220    
221     n->next = lru_list.next;
222     n->prev = &lru_list;
223     lru_list.next->prev = n;
224     lru_list.next = n;
225    
226 root 1.24 return n;
227 root 1.1 }
228    
229 root 1.22 static void
230     lru_trim (void)
231 root 1.1 {
232 root 1.22 while (lru_size > lru_maxsize)
233     {
234     /* nuke at the end */
235     lru_node *n = lru_list.prev;
236 root 1.1
237 root 1.22 n = lru_list.prev;
238 root 1.1
239 root 1.22 lru_list.prev = n->prev;
240     n->prev->next = &lru_list;
241 root 1.1
242 root 1.22 SvREFCNT_dec (n->dbh);
243     SvREFCNT_dec (n->sql);
244     SvREFCNT_dec (n->sth);
245     Safefree (n);
246    
247     lru_size--;
248     }
249 root 1.1 }
250    
251     /* store a not-yet existing entry(!) */
252 root 1.22 static void
253 root 1.24 lru_store (SV *dbh, SV *sql, SV *sth, mc_node *mc)
254 root 1.1 {
255     lru_node *n;
256     U32 hash;
257    
258 root 1.22 if (!lru_maxsize)
259     return;
260    
261 root 1.1 dbh = SvRV (dbh);
262 root 1.24 hash = lru_hash (dbh, sql);
263 root 1.1
264     lru_size++;
265 root 1.22 lru_trim ();
266 root 1.1
267     New (0, n, 1, lru_node);
268    
269 root 1.22 n->hash = hash;
270     n->dbh = dbh; SvREFCNT_inc (dbh); /* note: this is the dbi hash itself, not the reference */
271     n->sql = newSVsv (sql);
272     n->sth = sth; SvREFCNT_inc (sth);
273     n->sth_imp = sth_get_imp (sth);
274 root 1.24 n->mc = mc;
275 root 1.1
276 root 1.22 n->next = lru_list.next;
277     n->prev = &lru_list;
278 root 1.1 lru_list.next->prev = n;
279     lru_list.next = n;
280     }
281    
282 root 1.22 static void
283     lru_cachesize (int size)
284 root 1.1 {
285     if (size >= 0)
286     {
287     lru_maxsize = size;
288 root 1.22 lru_trim ();
289 root 1.1 }
290     }
291    
292     static GV *sql_exec;
293     static GV *DBH;
294 root 1.5
295     #define newconstpv(str) newSVpvn ((str), sizeof (str))
296 root 1.1
297     MODULE = PApp::SQL PACKAGE = PApp::SQL
298    
299     PROTOTYPES: DISABLE
300    
301     BOOT:
302     {
303 root 1.22 struct dbistate_st *dbis = DBIS_PUBLISHED_LVALUE;
304    
305 root 1.23 /* this is actually wrong, we should call the check member, apparently */
306 root 1.22 assert (dbis->version == DBISTATE_VERSION);
307     assert (dbis->xs_version == DBIXS_VERSION);
308    
309 root 1.24 tmp_iv = newSViv (0);
310    
311 root 1.1 sql_exec = gv_fetchpv ("PApp::SQL::sql_exec", TRUE, SVt_PV);
312     DBH = gv_fetchpv ("PApp::SQL::DBH" , TRUE, SVt_PV);
313    
314     /* apache might BOOT: twice :( */
315     if (lru_size)
316     lru_cachesize (0);
317    
318 root 1.22 lru_init ();
319 root 1.24 lru_cachesize (100);
320 root 1.1 }
321    
322 root 1.24 void
323     boot2 (SV *t_str, SV *t_int, SV *t_dbl)
324     CODE:
325     sql_varchar = newSVsv (t_str);
326     sql_integer = newSVsv (t_int);
327     sql_double = newSVsv (t_dbl);
328    
329 root 1.1 int
330     cachesize(size = -1)
331     int size
332     CODE:
333     RETVAL = lru_maxsize;
334     lru_cachesize (size);
335     OUTPUT:
336     RETVAL
337    
338     void
339     sql_exec(...)
340     ALIAS:
341 root 1.5 sql_uexec = 1
342 root 1.27 sql_fetch = 2
343 root 1.5 sql_ufetch = 3
344     sql_fetchall = 4
345     sql_ufetchall = 5
346     sql_exists = 6
347     sql_uexists = 7
348 root 1.1 PPCODE:
349     {
350     if (items == 0)
351     croak ("Usage: sql_exec [database-handle,] [bind-var-refs,... ] \"sql-statement\", [arguments, ...]");
352     else
353     {
354 root 1.24 int i;
355 root 1.1 int arg = 0;
356     int bind_first, bind_last;
357     int count;
358 root 1.24 lru_node *lru;
359 root 1.1 SV *dbh = ST(0);
360     SV *sth;
361     SV *sql;
362     SV *execute;
363 root 1.24 mc_node *mc;
364 root 1.9 STRLEN dc, dd; /* dummy */
365 root 1.24 I32 orig_stack = SP - PL_stack_base;
366 root 1.1
367     /* save our arguments against destruction through function calls */
368     SP += items;
369    
370     /* first check wether we should use an explicit db handle */
371     if (!is_dbh (dbh))
372     {
373 root 1.12 /* the next line doesn't work - check why later maybe */
374     /* dbh = get_sv ("DBH", FALSE);
375 root 1.1 if (!is_dbh (dbh))
376 root 1.12 {*/
377 root 1.20 dbh = GvSV (DBH);
378 root 1.1 if (!is_dbh (dbh))
379 root 1.12 croak ("sql_exec: no $DBH argument and no fallback in $PApp::SQL::DBH");
380     /*croak ("sql_exec: no $DBH found in current package or in PApp::SQL::");
381     }*/
382 root 1.1 }
383     else
384     arg++; /* we consumed one argument */
385    
386 root 1.20 /* be more Coro-friendly by keeping a copy, so different threads */
387 root 1.21 /* can replace their global handles */
388 root 1.20 dbh = sv_2mortal (newSVsv (dbh));
389    
390 root 1.1 /* count the remaining references (for bind_columns) */
391     bind_first = arg;
392     while (items > arg && SvROK (ST(arg)))
393     arg++;
394    
395     bind_last = arg;
396    
397     /* consume the sql-statement itself */
398     if (items <= arg)
399     croak ("sql_exec: required argument \"sql-statement\" missing");
400    
401     if (!SvPOK (ST(arg)))
402     croak ("sql_exec: sql-statement must be a string");
403    
404     sql = ST(arg); arg++;
405    
406 root 1.5 if ((ix & ~1) == 6)
407 root 1.1 {
408     SV *neu = sv_2mortal (newSVpv ("select count(*) > 0 from ", 0));
409     sv_catsv (neu, sql);
410     sv_catpv (neu, " limit 1");
411     sql = neu;
412 root 1.7 ix -= 4; /* sql_fetch */
413 root 1.1 }
414    
415 root 1.24 /* now prepare all parameters, by unmagicalising them and upgrading them */
416     for (i = arg; i < items; ++i)
417     {
418     SV *sv = ST (i);
419    
420     /* we sv_mortalcopy magical values since DBI seems to have a memory
421     * leak when magical values are passed into execute().
422     */
423     if (SvMAGICAL (sv))
424     ST (i) = sv = sv_mortalcopy (sv);
425    
426     if ((ix & 1) && SvPOKp (sv) && !SvUTF8 (sv))
427     {
428     ST (i) = sv = sv_mortalcopy (sv);
429     sv_utf8_upgrade (sv);
430     }
431     }
432    
433 root 1.4 /* check cache for existing statement handle */
434 root 1.24 lru = SvCUR (sql) <= MAX_CACHED_STATEMENT_SIZE
435     ? lru_fetch (dbh, sql)
436     : 0;
437     if (!lru)
438 root 1.1 {
439 root 1.24 mc = mc_find (SvSTASH (SvRV (dbh)));
440    
441     if (!mc->prepare)
442     mc_cache (mc, prepare);
443    
444 root 1.1 PUSHMARK (SP);
445     EXTEND (SP, 2);
446     PUSHs (dbh);
447     PUSHs (sql);
448     PUTBACK;
449 root 1.24 count = call_sv (mc->prepare, G_SCALAR);
450 root 1.1 SPAGAIN;
451    
452     if (count != 1)
453     croak ("sql_exec: unable to prepare() statement '%s': %s",
454 root 1.3 SvPV (sql, dc),
455 root 1.9 SvPV (get_sv ("DBI::errstr", TRUE), dd));
456 root 1.1
457     sth = POPs;
458    
459 root 1.25 if (!SvROK (sth))
460     croak ("sql_exec: buggy DBD driver, prepare returned non-reference for '%s': %s",
461     SvPV (sql, dc),
462     SvPV (get_sv ("DBI::errstr", TRUE), dd));
463    
464 root 1.24 mc = mc_find (SvSTASH (SvRV (sth)));
465    
466     if (!mc->bind_param)
467     {
468     mc_cache (mc, bind_param);
469     mc_cache (mc, execute);
470     mc_cache (mc, finish);
471     }
472    
473     if (SvCUR (sql) <= MAX_CACHED_STATEMENT_SIZE)
474     lru_store (dbh, sql, sth, mc);
475    
476     /* on first execution we unfortunately need to use bind_param
477     * to mark any numeric parameters as such.
478     */
479     SvIV_set (tmp_iv, 0);
480    
481     while (items > arg)
482     {
483     SV *sv = ST (arg);
484     /* we sv_mortalcopy magical values since DBI seems to have a memory
485     * leak when magical values are passed into execute().
486     */
487    
488     PUSHMARK (SP);
489     EXTEND (SP, 4);
490     PUSHs (sth);
491     SvIVX (tmp_iv)++;
492 root 1.29 SvIOK_only (tmp_iv);
493 root 1.24 PUSHs (tmp_iv);
494     PUSHs (sv);
495    
496     PUSHs (
497     SvPOKp (sv) ? sql_varchar
498     : SvNOKp (sv) ? sql_double
499     : SvIOKp (sv) ? sql_integer
500     : sql_varchar
501     );
502    
503     PUTBACK;
504     call_sv (mc->bind_param, G_VOID);
505     SPAGAIN;
506    
507     arg++;
508     }
509    
510     /* now use execute without any arguments */
511     PUSHMARK (SP);
512     EXTEND (SP, 1);
513     PUSHs (sth);
514 root 1.1 }
515 root 1.24 else
516     {
517     sth = sv_2mortal (SvREFCNT_inc (lru->sth));
518     mc = lru->mc;
519 root 1.1
520 root 1.24 /* we have previously executed this statement, so we
521     * use the cached types and use execute with arguments.
522 root 1.15 */
523 root 1.24
524     PUSHMARK (SP);
525     EXTEND (SP, items - arg + 1);
526     PUSHs (sth);
527     while (items > arg)
528     {
529     SV *sv = ST (arg);
530 root 1.27 PUSHs (sv);
531 root 1.24 arg++;
532     }
533 root 1.1 }
534    
535     PUTBACK;
536     /* { static GV *execute;
537     if (!execute) execute = gv_fetchmethod_autoload(SvSTASH(SvRV(sth)), "execute", 0);
538     count = call_sv(GvCV(execute), G_SCALAR);
539     }*/
540 root 1.24 count = call_sv (mc->execute, G_SCALAR);
541 root 1.1 SPAGAIN;
542    
543     if (count != 1)
544     croak ("sql_exec: execute() didn't return any value ('%s'): %s",
545 root 1.3 SvPV (sql, dc),
546 root 1.10 SvPV (get_sv ("DBI::errstr", TRUE), dd));
547 root 1.1
548     execute = POPs;
549    
550     if (!SvTRUE (execute))
551     croak ("sql_exec: unable to execute statement '%s' (%s)",
552 root 1.3 SvPV (sql, dc),
553 root 1.10 SvPV (get_sv ("DBI::errstr", TRUE), dd));
554 root 1.1
555 root 1.24 sv_setsv (GvSV (sql_exec), execute);
556 root 1.1
557     if (bind_first != bind_last)
558     {
559     PUSHMARK (SP);
560     EXTEND (SP, bind_last - bind_first + 2);
561     PUSHs (sth);
562     do {
563 stefan 1.16 #if CAN_UTF8
564 root 1.17 if (ix & 1)
565     SvUTF8_on (SvRV(ST(bind_first)));
566 stefan 1.16 #endif
567 root 1.1 PUSHs (ST(bind_first));
568     bind_first++;
569     } while (bind_first != bind_last);
570    
571     PUTBACK;
572 root 1.24
573     if (!mc->bind_columns)
574     mc_cache (mc, bind_columns);
575    
576     count = call_sv (mc->bind_columns, G_SCALAR);
577    
578 root 1.1 SPAGAIN;
579    
580     if (count != 1)
581     croak ("sql_exec: bind_columns() didn't return any value ('%s'): %s",
582 root 1.3 SvPV (sql, dc),
583 root 1.10 SvPV (get_sv ("DBI::errstr", TRUE), dd));
584 root 1.1
585 root 1.19 if (!SvOK (TOPs))
586 root 1.1 croak ("sql_exec: bind_columns() didn't return a true ('%s'): %s",
587 root 1.3 SvPV (sql, dc),
588 root 1.10 SvPV (get_sv ("DBI::errstr", TRUE), dd));
589 root 1.19
590     POPs;
591 root 1.1 }
592    
593 root 1.5 if ((ix & ~1) == 2)
594 root 1.1 { /* sql_fetch */
595     SV *row;
596    
597     PUSHMARK (SP);
598     XPUSHs (sth);
599     PUTBACK;
600 root 1.24
601     if (!mc->fetchrow_arrayref)
602     mc_cache (mc, fetchrow_arrayref);
603    
604     count = call_sv (mc->fetchrow_arrayref, G_SCALAR);
605 root 1.1 SPAGAIN;
606    
607     if (count != 1)
608     abort ();
609    
610     row = POPs;
611    
612 root 1.24 SP = PL_stack_base + orig_stack;
613    
614 root 1.1 if (SvROK (row))
615     {
616     AV *av;
617    
618     switch (GIMME_V)
619     {
620     case G_VOID:
621     /* no thing */
622     break;
623     case G_SCALAR:
624     /* the first element */
625 root 1.29 XPUSHs (mortalcopy_and_maybe_force_utf8 (ix & 1, AvARRAY ((AV *)SvRV (row))[0]));
626 root 1.24 count = 1;
627 root 1.1 break;
628     case G_ARRAY:
629     av = (AV *)SvRV (row);
630     count = AvFILL (av) + 1;
631     EXTEND (SP, count);
632     for (arg = 0; arg < count; arg++)
633 root 1.13 PUSHs (mortalcopy_and_maybe_force_utf8 (ix & 1, AvARRAY (av)[arg]));
634 root 1.1
635     break;
636     default:
637     abort ();
638     }
639     }
640     }
641 root 1.5 else if ((ix & ~1) == 4)
642 root 1.1 { /* sql_fetchall */
643     SV *rows;
644    
645     PUSHMARK (SP);
646     XPUSHs (sth);
647     PUTBACK;
648 root 1.24
649     if (!mc->fetchall_arrayref)
650     mc_cache (mc, fetchall_arrayref);
651    
652     count = call_sv (mc->fetchall_arrayref, G_SCALAR);
653 root 1.1 SPAGAIN;
654    
655     if (count != 1)
656     abort ();
657    
658     rows = POPs;
659    
660 root 1.24 SP = PL_stack_base + orig_stack;
661    
662 root 1.1 if (SvROK (rows))
663     {
664     AV *av = (AV *)SvRV (rows);
665     count = AvFILL (av) + 1;
666    
667     if (count)
668     {
669 root 1.6 int columns = AvFILL ((AV *) SvRV (AvARRAY (av)[0])) + 1; /* columns? */
670 root 1.1
671     EXTEND (SP, count);
672     if (columns == 1)
673     for (arg = 0; arg < count; arg++)
674 root 1.13 PUSHs (mortalcopy_and_maybe_force_utf8 (ix & 1, AvARRAY ((AV *)SvRV (AvARRAY (av)[arg]))[0]));
675 root 1.1 else
676     for (arg = 0; arg < count; arg++)
677 root 1.13 PUSHs (mortalcopy_and_maybe_force_utf8 (ix & 1, AvARRAY (av)[arg]));
678 root 1.1 }
679     }
680     }
681     else
682 root 1.24 {
683     SP = PL_stack_base + orig_stack;
684     XPUSHs (sth);
685     }
686 root 1.1
687 root 1.5 if (ix > 1 || GIMME_V == G_VOID)
688 root 1.1 {
689 root 1.24 orig_stack = SP - PL_stack_base;
690    
691 root 1.1 PUSHMARK (SP);
692     XPUSHs (sth);
693     PUTBACK;
694 root 1.24
695     if (!mc->finish)
696     mc_cache (mc, finish);
697    
698     call_sv (mc->finish, G_DISCARD);
699 root 1.1 SPAGAIN;
700 root 1.24
701     SP = PL_stack_base + orig_stack;
702 root 1.1 }
703     }
704     }
705    
706    
707