diff options
-rw-r--r-- | embed.h | 4 | ||||
-rw-r--r-- | ext/Thread/Thread.xs | 2 | ||||
-rw-r--r-- | global.sym | 4 | ||||
-rw-r--r-- | op.c | 73 | ||||
-rw-r--r-- | op.h | 1 | ||||
-rw-r--r-- | perl.c | 10 | ||||
-rw-r--r-- | perl.h | 8 | ||||
-rw-r--r-- | pp.c | 11 | ||||
-rw-r--r-- | pp_ctl.c | 26 | ||||
-rw-r--r-- | pp_hot.c | 6 | ||||
-rw-r--r-- | proto.h | 3 | ||||
-rw-r--r-- | scope.c | 19 | ||||
-rwxr-xr-x | t/lib/english.t | 10 | ||||
-rw-r--r-- | thread.h | 2 | ||||
-rw-r--r-- | toke.c | 8 | ||||
-rw-r--r-- | util.c | 15 |
16 files changed, 135 insertions, 67 deletions
@@ -216,6 +216,7 @@ #define filter_add Perl_filter_add #define filter_del Perl_filter_del #define filter_read Perl_filter_read +#define find_threadsv Perl_find_threadsv #define fold Perl_fold #define fold_constants Perl_fold_constants #define fold_locale Perl_fold_locale @@ -516,7 +517,6 @@ #define padix Perl_padix #define patleave Perl_patleave #define peep Perl_peep -#define per_thread_magicals Perl_per_thread_magicals #define pidgone Perl_pidgone #define pidstatus Perl_pidstatus #define pmflag Perl_pmflag @@ -938,6 +938,7 @@ #define save_scalar Perl_save_scalar #define save_sptr Perl_save_sptr #define save_svref Perl_save_svref +#define save_threadsv Perl_save_threadsv #define savepv Perl_savepv #define savepvn Perl_savepvn #define savestack Perl_savestack @@ -1084,6 +1085,7 @@ #define thisexpr Perl_thisexpr #define thr_key Perl_thr_key #define threads_mutex Perl_threads_mutex +#define threadsv_names Perl_threadsv_names #define timesbuf Perl_timesbuf #define tokenbuf Perl_tokenbuf #define too_few_arguments Perl_too_few_arguments diff --git a/ext/Thread/Thread.xs b/ext/Thread/Thread.xs index db1b5d7dd2..0844312dd4 100644 --- a/ext/Thread/Thread.xs +++ b/ext/Thread/Thread.xs @@ -147,7 +147,7 @@ threadstart(void *arg) SvREFCNT_dec(curstack); #endif SvREFCNT_dec(thr->cvcache); - SvREFCNT_dec(thr->magicals); + SvREFCNT_dec(thr->threadsv); SvREFCNT_dec(thr->specific); SvREFCNT_dec(thr->errsv); SvREFCNT_dec(thr->errhv); diff --git a/global.sym b/global.sym index b720369667..ebd6d7e3f5 100644 --- a/global.sym +++ b/global.sym @@ -63,6 +63,7 @@ exp_amg expect expectterm fallback_amg +find_threadsv fold fold_locale freq @@ -163,7 +164,6 @@ pad_reset_pending padix padix_floor patleave -per_thread_magicals pidstatus pow_amg pow_ass_amg @@ -223,6 +223,7 @@ sv_no sv_undef sv_yes thisexpr +threadsv_names thr_key timesbuf tokenbuf @@ -1047,6 +1048,7 @@ save_pptr save_scalar save_sptr save_svref +save_threadsv savepv savepvn savestack_grow @@ -40,6 +40,7 @@ static OP *too_many_arguments _((OP *o, char* name)); static void null _((OP* o)); static PADOFFSET pad_findlex _((char* name, PADOFFSET newoff, U32 seq, CV* startcv, I32 cx_ix)); +static OP *newDEFSVOP _((void)); static char* gv_ename(GV *gv) @@ -496,42 +497,47 @@ pad_reset(void) } #ifdef USE_THREADS -/* find_thread_magical is not reentrant */ +/* find_threadsv is not reentrant */ PADOFFSET -find_thread_magical(char *name) +find_threadsv(char *name) { dTHR; char *p; PADOFFSET key; SV **svp; - /* We currently only handle single character magicals */ - p = strchr(per_thread_magicals, *name); + /* We currently only handle names of a single character */ + p = strchr(threadsv_names, *name); if (!p) return NOT_IN_PAD; - key = p - per_thread_magicals; - svp = av_fetch(thr->magicals, key, FALSE); + key = p - threadsv_names; + svp = av_fetch(thr->threadsv, key, FALSE); if (!svp) { SV *sv = NEWSV(0, 0); - av_store(thr->magicals, key, sv); + av_store(thr->threadsv, key, sv); /* * Some magic variables used to be automagically initialised * in gv_fetchpv. Those which are now per-thread magicals get * initialised here instead. */ switch (*name) { + case '_': + break; case ';': sv_setpv(sv, "\034"); + sv_magic(sv, 0, 0, name, 1); break; case '&': case '`': case '\'': sawampersand = TRUE; SvREADONLY_on(sv); + sv_magic(sv, 0, 0, name, 1); break; + default: + sv_magic(sv, 0, 0, name, 1); } - sv_magic(sv, 0, 0, name, 1); DEBUG_L(PerlIO_printf(PerlIO_stderr(), - "find_thread_magical: new SV %p for $%s%c\n", + "find_threadsv: new SV %p for $%s%c\n", sv, (*name < 32) ? "^" : "", (*name < 32) ? toCTRL(*name) : *name)); } @@ -565,7 +571,7 @@ op_free(OP *o) break; #ifdef USE_THREADS case OP_THREADSV: - o->op_targ = 0; /* Was holding index into thr->magicals AV. */ + o->op_targ = 0; /* Was holding index into thr->threadsv AV. */ break; #endif /* USE_THREADS */ default: @@ -613,7 +619,7 @@ op_free(OP *o) static void null(OP *o) { - if (o->op_type != OP_NULL && o->op_targ > 0) + if (o->op_type != OP_NULL && o->op_type != OP_THREADSV && o->op_targ > 0) pad_free(o->op_targ); o->op_targ = o->op_type; o->op_type = OP_NULL; @@ -1529,6 +1535,18 @@ block_end(I32 floor, OP *seq) return retval; } +static OP * +newDEFSVOP(void) +{ +#ifdef USE_THREADS + OP *o = newOP(OP_THREADSV, 0); + o->op_targ = find_threadsv("_"); + return o; +#else + return newSVREF(newGVOP(OP_GV, 0, defgv)); +#endif /* USE_THREADS */ +} + void newPROG(OP *o) { @@ -1592,7 +1610,7 @@ jmaybe(OP *o) OP *o2; #ifdef USE_THREADS o2 = newOP(OP_THREADSV, 0); - o2->op_targ = find_thread_magical(";"); + o2->op_targ = find_threadsv(";"); #else o2 = newSVREF(newGVOP(OP_GV, 0, gv_fetchpv(";", TRUE, SVt_PV))), #endif /* USE_THREADS */ @@ -2110,7 +2128,7 @@ pmruntime(OP *o, OP *expr, OP *repl) #ifdef USE_THREADS else if (repl->op_type == OP_THREADSV && strchr("&`'123456789+", - per_thread_magicals[repl->op_targ])) + threadsv_names[repl->op_targ])) { curop = 0; } @@ -2793,7 +2811,7 @@ newLOOPOP(I32 flags, I32 debuggable, OP *expr, OP *block) if (expr->op_type == OP_READLINE || expr->op_type == OP_GLOB || (expr->op_type == OP_NULL && expr->op_targ == OP_GLOB)) { expr = newUNOP(OP_DEFINED, 0, - newASSIGNOP(0, newSVREF(newGVOP(OP_GV, 0, defgv)), 0, expr) ); + newASSIGNOP(0, newDEFSVOP(), 0, expr) ); } } @@ -2827,7 +2845,7 @@ newWHILEOP(I32 flags, I32 debuggable, LOOP *loop, I32 whileline, OP *expr, OP *b if (expr && (expr->op_type == OP_READLINE || expr->op_type == OP_GLOB || (expr->op_type == OP_NULL && expr->op_targ == OP_GLOB))) { expr = newUNOP(OP_DEFINED, 0, - newASSIGNOP(0, newSVREF(newGVOP(OP_GV, 0, defgv)), 0, expr) ); + newASSIGNOP(0, newDEFSVOP(), 0, expr) ); } if (!block) @@ -2914,11 +2932,22 @@ newFOROP(I32 flags,char *label,line_t forline,OP *sv,OP *expr,OP *block,OP *cont op_free(sv); sv = Nullop; } + else if (sv->op_type == OP_THREADSV) { /* per-thread variable */ + padoff = sv->op_targ; + iterflags |= OPf_SPECIAL; + op_free(sv); + sv = Nullop; + } else croak("Can't use %s for loop variable", op_desc[sv->op_type]); } else { +#ifdef USE_THREADS + padoff = find_threadsv("_"); + iterflags |= OPf_SPECIAL; +#else sv = newGVOP(OP_GV, 0, defgv); +#endif } if (expr->op_type == OP_RV2AV || expr->op_type == OP_PADAV) { expr = scalar(ref(expr, OP_ITER)); @@ -3846,7 +3875,7 @@ ck_eval(OP *o) } else { op_free(o); - o = newUNOP(OP_ENTEREVAL, 0, newSVREF(newGVOP(OP_GV, 0, defgv))); + o = newUNOP(OP_ENTEREVAL, 0, newDEFSVOP()); } o->op_targ = (PADOFFSET)hints; return o; @@ -3974,7 +4003,7 @@ ck_ftst(OP *o) return newGVOP(type, OPf_REF, gv_fetchpv("main::STDIN", TRUE, SVt_PVIO)); else - return newUNOP(type, 0, newSVREF(newGVOP(OP_GV, 0, defgv))); + return newUNOP(type, 0, newDEFSVOP()); } return o; } @@ -4007,7 +4036,7 @@ ck_fun(OP *o) kid = kid->op_sibling; } if (!kid && opargs[type] & OA_DEFGV) - *tokid = kid = newSVREF(newGVOP(OP_GV, 0, defgv)); + *tokid = kid = newDEFSVOP(); while (oa && kid) { numargs++; @@ -4105,7 +4134,7 @@ ck_fun(OP *o) } else if (opargs[type] & OA_DEFGV) { op_free(o); - return newUNOP(type, 0, newSVREF(newGVOP(OP_GV, 0, defgv))); + return newUNOP(type, 0, newDEFSVOP()); } if (oa) { @@ -4123,7 +4152,7 @@ ck_glob(OP *o) GV *gv; if ((o->op_flags & OPf_KIDS) && !cLISTOPo->op_first->op_sibling) - append_elem(OP_GLOB, o, newSVREF(newGVOP(OP_GV, 0, defgv))); + append_elem(OP_GLOB, o, newDEFSVOP()); if (!((gv = gv_fetchpv("glob", FALSE, SVt_PVCV)) && GvIMPORTED_CV(gv))) gv = gv_fetchpv("CORE::GLOBAL::glob", FALSE, SVt_PVCV); @@ -4260,7 +4289,7 @@ ck_listiob(OP *o) } if (!kid) - append_elem(o->op_type, o, newSVREF(newGVOP(OP_GV, 0, defgv)) ); + append_elem(o->op_type, o, newDEFSVOP()); o = listkids(o); @@ -4485,7 +4514,7 @@ ck_split(OP *o) scalar(kid); if (!kid->op_sibling) - append_elem(OP_SPLIT, o, newSVREF(newGVOP(OP_GV, 0, defgv)) ); + append_elem(OP_SPLIT, o, newDEFSVOP()); kid = kid->op_sibling; scalar(kid); @@ -74,6 +74,7 @@ typedef U32 PADOFFSET; /* On UNOPs, saw bare parens, e.g. eof(). */ /* On OP_ENTERSUB || OP_NULL, saw a "do". */ /* On OP_(ENTER|LEAVE)EVAL, don't clear $@ */ + /* On OP_ENTERITER, loop var is per-thread */ /* old names; don't use in new code, but don't break them, either */ #define OPf_LIST 1 @@ -937,7 +937,7 @@ print \" \\@INC:\\n @INC\\n\";"); SvREFCNT_dec(rs); rs = SvREFCNT_inc(nrs); #ifdef USE_THREADS - sv_setsv(*av_fetch(thr->magicals, find_thread_magical("/"), FALSE), rs); + sv_setsv(*av_fetch(thr->threadsv, find_threadsv("/"), FALSE), rs); #else sv_setsv(GvSV(gv_fetchpv("/", TRUE, SVt_PV)), rs); #endif /* USE_THREADS */ @@ -1052,10 +1052,10 @@ perl_get_sv(char *name, I32 create) GV *gv; #ifdef USE_THREADS if (name[1] == '\0' && !isALPHA(name[0])) { - PADOFFSET tmp = find_thread_magical(name); + PADOFFSET tmp = find_threadsv(name); if (tmp != NOT_IN_PAD) { dTHR; - return *av_fetch(thr->magicals, tmp, FALSE); + return *av_fetch(thr->threadsv, tmp, FALSE); } } #endif /* USE_THREADS */ @@ -2498,7 +2498,7 @@ init_predump_symbols(void) GV *othergv; #ifdef USE_THREADS - sv_setpvn(*av_fetch(thr->magicals,find_thread_magical("\""),FALSE)," ", 1); + sv_setpvn(*av_fetch(thr->threadsv,find_threadsv("\""),FALSE)," ", 1); #else sv_setpvn(GvSV(gv_fetchpv("\"", TRUE, SVt_PV)), " ", 1); #endif /* USE_THREADS */ @@ -2786,7 +2786,7 @@ init_main_thread() Newz(53, thr, 1, struct thread); curcop = &compiling; thr->cvcache = newHV(); - thr->magicals = newAV(); + thr->threadsv = newAV(); thr->specific = newAV(); thr->errhv = newHV(); thr->flags = THRf_R_JOINABLE; @@ -462,9 +462,13 @@ register struct op *op asm(stringify(OP_IN_REGISTER)); #ifdef USE_THREADS # define ERRSV (thr->errsv) # define ERRHV (thr->errhv) +# define DEFSV *av_fetch(thr->threadsv, find_threadsv("_"), FALSE) +# define SAVE_DEFSV save_threadsv(find_threadsv("_")) #else # define ERRSV GvSV(errgv) # define ERRHV GvHV(errgv) +# define DEFSV GvSV(defgv) +# define SAVE_DEFSV SAVESPTR(GvSV(defgv)) #endif /* USE_THREADS */ #ifndef errno @@ -1349,7 +1353,7 @@ int runops_standard _((void)); int runops_debug _((void)); #endif -#define PER_THREAD_MAGICALS "123456789&`'+/.,\\\";^-%=|~:\001\005!@" +#define THREADSV_NAMES "_123456789&`'+/.,\\\";^-%=|~:\001\005!@" /****************/ /* Truly global */ @@ -1367,7 +1371,7 @@ EXT struct thread * eval_owner; /* Owner thread for doeval */ EXT int nthreads; /* Number of threads currently */ EXT perl_mutex threads_mutex; /* Mutex for nthreads and thread list */ EXT perl_cond nthreads_cond; /* Condition variable for nthreads */ -EXT char * per_thread_magicals INIT(PER_THREAD_MAGICALS); +EXT char * threadsv_names INIT(THREADSV_NAMES); #ifdef FAKE_THREADS EXT struct thread * thr; /* Currently executing (fake) thread */ #endif @@ -590,7 +590,7 @@ PP(pp_trans) if (op->op_flags & OPf_STACKED) sv = POPs; else { - sv = GvSV(defgv); + sv = DEFSV; EXTEND(SP,1); } TARG = sv_newmortal(); @@ -2714,7 +2714,7 @@ PP(pp_reverse) if (SP - MARK > 1) do_join(TARG, &sv_no, MARK, SP); else - sv_setsv(TARG, (SP > MARK) ? *SP : GvSV(defgv)); + sv_setsv(TARG, (SP > MARK) ? *SP : DEFSV); up = SvPV_force(TARG, len); if (len > 1) { down = SvPVX(TARG) + len - 1; @@ -4310,14 +4310,11 @@ PP(pp_threadsv) { djSP; #ifdef USE_THREADS - SV **svp = av_fetch(thr->magicals, op->op_targ, FALSE); - if (!svp) - croak("panic: pp_threadsv"); EXTEND(sp, 1); if (op->op_private & OPpLVAL_INTRO) - PUSHs(save_svref(svp)); + PUSHs(*save_threadsv(op->op_targ)); else - PUSHs(*svp); + PUSHs(*av_fetch(thr->threadsv, op->op_targ, FALSE)); #else DIE("tried to access per-thread data in non-threaded perl"); #endif /* USE_THREADS */ @@ -546,14 +546,17 @@ PP(pp_grepstart) ENTER; /* enter outer scope */ SAVETMPS; - SAVESPTR(GvSV(defgv)); - +#if 0 + SAVE_DEFSV; +#else + save_sptr(av_fetch(thr->threadsv, find_threadsv("_"), FALSE)); +#endif ENTER; /* enter inner scope */ SAVESPTR(curpm); src = stack_base[*markstack_ptr]; SvTEMP_off(src); - GvSV(defgv) = src; + DEFSV = src; PUTBACK; if (op->op_type == OP_MAPSTART) @@ -623,7 +626,7 @@ PP(pp_mapwhile) src = stack_base[markstack_ptr[-1]]; SvTEMP_off(src); - GvSV(defgv) = src; + DEFSV = src; RETURNOP(cLOGOP->op_other); } @@ -1334,12 +1337,19 @@ PP(pp_enteriter) ENTER; SAVETMPS; - if (op->op_targ) - svp = &curpad[op->op_targ]; /* "my" variable */ +#ifdef USE_THREADS + if (op->op_flags & OPf_SPECIAL) + svp = save_threadsv(op->op_targ); /* per-thread variable */ else +#endif /* USE_THREADS */ + if (op->op_targ) { + svp = &curpad[op->op_targ]; /* "my" variable */ + SAVESPTR(*svp); + } + else { svp = &GvSV((GV*)POPs); /* symbol table variable */ - - SAVESPTR(*svp); + SAVESPTR(*svp); + } ENTER; @@ -759,7 +759,7 @@ PP(pp_match) if (op->op_flags & OPf_STACKED) TARG = POPs; else { - TARG = GvSV(defgv); + TARG = DEFSV; EXTEND(SP,1); } PUTBACK; /* EVAL blocks need stack_sp. */ @@ -1432,7 +1432,7 @@ PP(pp_subst) if (op->op_flags & OPf_STACKED) TARG = POPs; else { - TARG = GvSV(defgv); + TARG = DEFSV; EXTEND(SP,1); } if (SvREADONLY(TARG) @@ -1705,7 +1705,7 @@ PP(pp_grepwhile) src = stack_base[*markstack_ptr]; SvTEMP_off(src); - GvSV(defgv) = src; + DEFSV = src; RETURNOP(cLOGOP->op_other); } @@ -136,7 +136,7 @@ void dump_sub _((GV* gv)); void fbm_compile _((SV* sv)); char* fbm_instr _((unsigned char* big, unsigned char* bigend, SV* littlesv)); #ifdef USE_THREADS -PADOFFSET find_thread_magical _((char *name)); +PADOFFSET find_threadsv _((char *name)); #endif OP* force_list _((OP* arg)); OP* fold_constants _((OP* arg)); @@ -443,6 +443,7 @@ SV* save_scalar _((GV* gv)); void save_pptr _((char** pptr)); void save_sptr _((SV** sptr)); SV* save_svref _((SV** sptr)); +SV** save_threadsv _((PADOFFSET i)); OP* sawparens _((OP* o)); OP* scalar _((OP* o)); OP* scalarkids _((OP* o)); @@ -330,6 +330,22 @@ save_sptr(SV **sptr) SSPUSHINT(SAVEt_SPTR); } +SV ** +save_threadsv(PADOFFSET i) +{ +#ifdef USE_THREADS + dTHR; + SV **svp = av_fetch(thr->threadsv, i, FALSE); + DEBUG_L(PerlIO_printf(PerlIO_stderr(), "save_threadsv %u: %p %p:%s\n", + i, svp, *svp, SvPEEK(*svp))); + save_svref(svp); + return svp; +#else + croak("panic: save_threadsv called in non-threaded perl"); + return 0; +#endif /* USE_THREADS */ +} + void save_nogv(GV *gv) { @@ -475,6 +491,9 @@ leave_scope(I32 base) ptr = SSPOPPTR; restore_sv: sv = *(SV**)ptr; + DEBUG_L(PerlIO_printf(PerlIO_stderr(), + "restore svref: %p %p:%s -> %p:%s\n", + ptr, sv, SvPEEK(sv), value, SvPEEK(value))); if (SvTYPE(sv) >= SVt_PVMG && SvMAGIC(sv) && SvTYPE(sv) != SVt_PVGV) { diff --git a/t/lib/english.t b/t/lib/english.t index 68a587091f..1a96c772fe 100755 --- a/t/lib/english.t +++ b/t/lib/english.t @@ -5,19 +5,23 @@ print "1..16\n"; BEGIN { @INC = '../lib' } use English; use Config; -my $threads = $Config{'ccflags'} =~ /-DUSE_THREADS\b/; +my $threads = $Config{archname} =~ /-thread$/; print $PID == $$ ? "ok 1\n" : "not ok 1\n"; $_ = 1; -print $ARG == $_ ? "ok 2\n" : "not ok 2\n"; +print $ARG == $_ || $threads ? "ok 2\n" : "not ok 2\n"; sub foo { print $ARG[0] == $_[0] || $threads ? "ok 3\n" : "not ok 3\n"; } &foo(1); -$ARG = "ok 4\nok 5\nok 6\n"; +if ($threads) { + $_ = "ok 4\nok 5\nok 6\n"; +} else { + $ARG = "ok 4\nok 5\nok 6\n"; +} /ok 5\n/; print $PREMATCH, $MATCH, $POSTMATCH; @@ -218,7 +218,7 @@ struct thread { HV * cvcache; perl_thread self; /* Underlying thread object */ U32 flags; - AV * magicals; /* Per-thread magicals */ + AV * threadsv; /* Per-thread SVs ($_, $@ etc.) */ AV * specific; /* Thread-specific user data */ SV * errsv; /* Backing SV for $@ */ HV * errhv; /* HV for what was %@ in pp_ctl.c */ @@ -1226,10 +1226,10 @@ yylex(void) if (!strchr(tokenbuf,':')) { #ifdef USE_THREADS - /* Check for single character per-thread magicals */ + /* Check for single character per-thread SVs */ if (tokenbuf[0] == '$' && tokenbuf[2] == '\0' - && !isALPHA(tokenbuf[1]) /* Rule out obvious non-magicals */ - && (tmp = find_thread_magical(&tokenbuf[1])) != NOT_IN_PAD) + && !isALPHA(tokenbuf[1]) /* Rule out obvious non-threadsvs */ + && (tmp = find_threadsv(&tokenbuf[1])) != NOT_IN_PAD) { yylval.opval = newOP(OP_THREADSV, 0); yylval.opval->op_targ = tmp; @@ -1373,7 +1373,7 @@ yylex(void) force_next(','); #ifdef USE_THREADS nextval[nexttoke].opval = newOP(OP_THREADSV, 0); - nextval[nexttoke].opval->op_targ = find_thread_magical("\""); + nextval[nexttoke].opval->op_targ = find_threadsv("\""); force_next(PRIVATEREF); #else force_ident("\"", '$'); @@ -2465,7 +2465,7 @@ new_struct_thread(struct thread *t) curcop = &compiling; thr->cvcache = newHV(); - thr->magicals = newAV(); + thr->threadsv = newAV(); thr->specific = newAV(); thr->errsv = newSVpv("", 0); thr->errhv = newHV(); @@ -2506,16 +2506,15 @@ new_struct_thread(struct thread *t) bodytarget = newSVsv(t->Tbodytarget); toptarget = newSVsv(t->Ttoptarget); - /* Initialise all per-thread magicals that the template thread used */ - svp = AvARRAY(t->magicals); - for (i = 0; i <= AvFILL(t->magicals); i++, svp++) { + /* Initialise all per-thread SVs that the template thread used */ + svp = AvARRAY(t->threadsv); + for (i = 0; i <= AvFILL(t->threadsv); i++, svp++) { if (*svp && *svp != &sv_undef) { SV *sv = newSVsv(*svp); - av_store(thr->magicals, i, sv); - sv_magic(sv, 0, 0, &per_thread_magicals[i], 1); + av_store(thr->threadsv, i, sv); + sv_magic(sv, 0, 0, &threadsv_names[i], 1); DEBUG_L(PerlIO_printf(PerlIO_stderr(), - "new_struct_thread: copied magical %d %p->%p\n",i, - t, thr)); + "new_struct_thread: copied threadsv %d %p->%p\n",i, t, thr)); } } |