https://github.com/mikehaertel/minrx/pull/68 https://cgit.git.savannah.gnu.org/cgit/gawk.git/commit/?h=gawk-5.4-stable&id=1a61f93cd203dca8b0bc94f04a3eff80effae152 From 1a61f93cd203dca8b0bc94f04a3eff80effae152 Mon Sep 17 00:00:00 2001 From: "Arnold D. Robbins" Date: Sat, 8 Aug 2026 22:24:22 +0300 Subject: Update minrx.c from upstream: memory management improvements. --- support/minrx.c | 201 ++++++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 174 insertions(+), 42 deletions(-) diff --git a/support/minrx.c b/support/minrx.c index 1e03fb99..5e268213 100644 --- a/support/minrx.c +++ b/support/minrx.c @@ -185,15 +185,6 @@ cowvec_allocator_construct(COWVec_Allocator *a, size_t length) a->length = length; } -static void -cowvec_allocator_destruct(COWVec_Allocator *a) -{ - for (COWVec_Storage *s = a->freelist, *sfreelink = (COWVec_Storage *) NULL; s != (COWVec_Storage *) NULL; s = sfreelink) { - sfreelink = s->u.freelink; - free(s); - } -} - static size_t cowvec_storage_get(const COWVec_Storage *cvs, size_t i) { @@ -1272,6 +1263,28 @@ nodelist_empty(void) return r; } +// Scratch space for an execution, kept with the compiled Regexp so that +// the next execution can have it again. Everything in it is sized from +// the Regexp alone, so it need only be built once; building it afresh +// for every execution otherwise dominates the cost of matching a short +// subject. +// +// Only one execution can have it at a time, so an execution that finds +// it taken -- a concurrent or a nested one -- falls back to its own. +typedef struct Scratch Scratch; +struct Scratch { + bool ready; +#ifdef HAVE_PTHREADS + pthread_mutex_t inuse; +#else + bool inuse; +#endif + COWVec_Storage *freelist; + QSet epsq; + QVec epsv; + QVec mcsv[2]; +}; + typedef struct Regexp Regexp; struct Regexp { WConv_Encoding enc; @@ -1287,8 +1300,89 @@ struct Regexp { FirstBytes firstbytes; int32_t firstunique; bool anchored; + Scratch scratch; }; +static bool +scratch_construct(Scratch *sc, size_t nnode) +{ + if (sc->ready) + return true; + if (!qset_construct(&sc->epsq, nnode) + || !qvec_construct(&sc->epsv, nnode) + || !qvec_construct(&sc->mcsv[0], nnode) + || !qvec_construct(&sc->mcsv[1], nnode)) { + qvec_destruct(&sc->mcsv[1]); + qvec_destruct(&sc->mcsv[0]); + qvec_destruct(&sc->epsv); + qset_destruct(&sc->epsq); + return false; + } + sc->ready = true; + return true; +} + +static void +scratch_destruct(Scratch *sc) +{ + if (sc->ready) { + qvec_destruct(&sc->mcsv[1]); + qvec_destruct(&sc->mcsv[0]); + qvec_destruct(&sc->epsv); + qset_destruct(&sc->epsq); + sc->ready = false; + } + for (COWVec_Storage *s = sc->freelist, *next = (COWVec_Storage *) NULL; s != (COWVec_Storage *) NULL; s = next) { + next = s->u.freelink; + free(s); + } + sc->freelist = (COWVec_Storage *) NULL; +} + +// Claim the scratch space for an execution, or report it already taken. +static bool +scratch_acquire(Scratch *sc) +{ +#ifdef HAVE_PTHREADS + return pthread_mutex_trylock(&sc->inuse) == 0; +#else + if (sc->inuse) + return false; + sc->inuse = true; + return true; +#endif +} + +static void +scratch_release(Scratch *sc) +{ +#ifdef HAVE_PTHREADS + pthread_mutex_unlock(&sc->inuse); +#else + sc->inuse = false; +#endif +} + +// For the Regexp's own scratch space, as opposed to the one an execution +// falls back to, which is private to it and needs neither of these. +static void +scratch_init(Scratch *sc) +{ + memset(sc, 0, sizeof *sc); +#ifdef HAVE_PTHREADS + pthread_mutex_init(&sc->inuse, (const pthread_mutexattr_t *) NULL); +#endif +} + +static void +scratch_fini(Scratch *sc) +{ + scratch_destruct(sc); +#ifdef HAVE_PTHREADS + pthread_mutex_destroy(&sc->inuse); +#endif +} + static NInt satmul(NInt x, NInt y) { @@ -1879,6 +1973,7 @@ compile(Compile *c) memset(&r->firstbytes, 0, sizeof r->firstbytes); r->firstunique = -1; r->anchored = false; + scratch_init(&r->scratch); int err; if ((err = setjmp(c->errjmp)) != 0) { c = vc, r = vr; @@ -1944,13 +2039,16 @@ struct Execute { COWVec_Allocator allocator; COWVec best; NInt bestmincount; // note mincounts are negated so this means +infinity - QSet epsq; - QVec epsv; + Scratch *scratch; // the scratch space this execution is using + QSet *epsq; // aliases of the members of *scratch, cached + QVec *epsv; // here to save an indirection on every use + QVec *mcsv; + Scratch own; // used where the Regexp's is already taken bool exiting; }; static bool -execute_construct(Execute *e, const Regexp *r, minrx_regexec_flags_t flags, const char *bp, const char *ep) +execute_construct(Execute *e, Regexp *r, minrx_regexec_flags_t flags, const char *bp, const char *ep) { e->r = r; e->flags = flags; @@ -1965,21 +2063,47 @@ execute_construct(Execute *e, const Regexp *r, minrx_regexec_flags_t flags, cons cowvec_allocator_construct(&e->allocator, e->nestoff + r->nmin); cowvec_construct(&e->best, (COWVec_Allocator *) NULL); e->bestmincount = 0; - if (!qset_construct(&e->epsq, r->nnode) || !qvec_construct(&e->epsv, r->nnode)) { - qset_destruct(&e->epsq); + if (scratch_acquire(&r->scratch)) { + e->scratch = &r->scratch; + } else { + // Private to this execution, so only what scratch_construct() + // reads needs setting here; it fills in the rest. + e->own.ready = false; + e->own.freelist = (COWVec_Storage *) NULL; + e->scratch = &e->own; + } + if (!scratch_construct(e->scratch, r->nnode)) { + if (e->scratch != &e->own) + scratch_release(e->scratch); return false; } + e->epsq = &e->scratch->epsq; + e->epsv = &e->scratch->epsv; + e->mcsv = e->scratch->mcsv; + e->allocator.freelist = e->scratch->freelist; e->exiting = r->anchored; return true; } +// 'reusable' is false where the execution has failed part way through and +// may have left the scratch space in a state the next one, which would +// otherwise inherit it, should not build upon. static void -execute_destruct(Execute *e) +execute_destruct(Execute *e, bool reusable) { - qvec_destruct(&e->epsv); - qset_destruct(&e->epsq); + Scratch *sc = e->scratch; cowvec_destruct(&e->best); - cowvec_allocator_destruct(&e->allocator); + // Drain, but keep the storage for the next execution. + qvec_clear(&e->mcsv[1]); + qvec_clear(&e->mcsv[0]); + qvec_clear(e->epsv); + while (!qset_empty(e->epsq)) + (void) qset_remove(e->epsq); + sc->freelist = e->allocator.freelist; + if (!reusable || sc == &e->own) + scratch_destruct(sc); + if (sc != &e->own) + scratch_release(sc); } inline static void @@ -2001,7 +2125,7 @@ execute_add(Execute *e, QVec *ncsv, NInt k, NInt nstk, const NState *nsp, WChar qvi.newnsp->gen = e->gen; } } else { - QVecInsert qvi = qvec_insert(&e->epsv, k, nsp); + QVecInsert qvi = qvec_insert(e->epsv, k, nsp); if (qvi.newly) { nstate_construct_copy(qvi.newnsp, nsp); } else { @@ -2012,7 +2136,7 @@ execute_add(Execute *e, QVec *ncsv, NInt k, NInt nstk, const NState *nsp, WChar return; } qvi.newnsp->gen = e->gen; - qset_insert(&e->epsq, k); + qset_insert(e->epsq, k); } } @@ -2036,7 +2160,7 @@ execute_add_1(Execute *e, QVec *ncsv, NInt k, NInt nstk, const NState *nsp, WCha cowvec_put(&qvi.newnsp->substack, nstk - 1, arg1); } } else { - QVecInsert qvi = qvec_insert(&e->epsv, k, nsp); + QVecInsert qvi = qvec_insert(e->epsv, k, nsp); if (qvi.newly) { nstate_construct_copy(qvi.newnsp, nsp); } else { @@ -2048,7 +2172,7 @@ execute_add_1(Execute *e, QVec *ncsv, NInt k, NInt nstk, const NState *nsp, WCha } qvi.newnsp->gen = e->gen; cowvec_put(&qvi.newnsp->substack, nstk - 1, arg1); - qset_insert(&e->epsq, k); + qset_insert(e->epsq, k); } } @@ -2073,7 +2197,7 @@ execute_add_2(Execute *e, QVec *ncsv, NInt k, NInt nstk, const NState *nsp, WCha cowvec_put(&qvi.newnsp->substack, nstk - 1, arg2); } } else { - QVecInsert qvi = qvec_insert(&e->epsv, k, nsp); + QVecInsert qvi = qvec_insert(e->epsv, k, nsp); if (qvi.newly) { nstate_construct_copy(qvi.newnsp, nsp); } else { @@ -2086,7 +2210,7 @@ execute_add_2(Execute *e, QVec *ncsv, NInt k, NInt nstk, const NState *nsp, WCha qvi.newnsp->gen = e->gen; cowvec_put(&qvi.newnsp->substack, nstk - 2, arg1); cowvec_put(&qvi.newnsp->substack, nstk - 1, arg2); - qset_insert(&e->epsq, k); + qset_insert(e->epsq, k); } } @@ -2112,7 +2236,7 @@ execute_add_3(Execute *e, QVec *ncsv, NInt k, NInt nstk, const NState *nsp, WCha cowvec_put(&qvi.newnsp->substack, nstk - 1, arg3); } } else { - QVecInsert qvi = qvec_insert(&e->epsv, k, nsp); + QVecInsert qvi = qvec_insert(e->epsv, k, nsp); if (qvi.newly) { nstate_construct_copy(qvi.newnsp, nsp); } else { @@ -2126,7 +2250,7 @@ execute_add_3(Execute *e, QVec *ncsv, NInt k, NInt nstk, const NState *nsp, WCha cowvec_put(&qvi.newnsp->substack, nstk - 3, arg1); cowvec_put(&qvi.newnsp->substack, nstk - 2, arg2); cowvec_put(&qvi.newnsp->substack, nstk - 1, arg3); - qset_insert(&e->epsq, k); + qset_insert(e->epsq, k); } } @@ -2148,8 +2272,8 @@ execute_epsclosure(Execute *e, QVec *ncsv, WChar wcnext) const Node *nodes = e->nodes; bool (*is_word)(WChar) = e->r->enc == Byte ? is_word_byte : is_word_wide; do { - NInt k = qset_remove(&e->epsq); - NState *nsp = qvec_lookup(&e->epsv, k); + NInt k = qset_remove(e->epsq); + NState *nsp = qvec_lookup(e->epsv, k); if (cowvec_valid(&e->best) && nsp->boff > cowvec_get(&e->best, e->suboff + 0)) continue; const Node *np = &e->nodes[k]; @@ -2305,7 +2429,7 @@ execute_epsclosure(Execute *e, QVec *ncsv, WChar wcnext) abort(); break; } - } while (!qset_empty(&e->epsq)); + } while (!qset_empty(e->epsq)); } #define WCNEXT(E, WCN) ((E)->wcprev = (WCN), (E)->off = wconv_off(&(E)->wconv), (WCN) = wconv_nextchr(&(E)->wconv)) @@ -2313,18 +2437,12 @@ execute_epsclosure(Execute *e, QVec *ncsv, WChar wcnext) static int execute(Execute *e, size_t nm, minrx_regmatch_t *rm) { - QVec mcsvs[2]; - if (!qvec_construct(&mcsvs[0], e->r->nnode) || !qvec_construct(&mcsvs[1], e->r->nnode)) { - qvec_destruct(&mcsvs[0]); - return MINRX_REG_ESPACE; - } + QVec *mcsvs = e->mcsv; NState nsinit; nstate_construct(&nsinit, (COWVec_Allocator *) NULL); // fake construction so "exception handler" will be safe int err; if ((err = setjmp(e->allocator.errjmp)) != 0) { nstate_destruct(&nsinit); - qvec_destruct(&mcsvs[1]); - qvec_destruct(&mcsvs[0]); return err; } nstate_construct(&nsinit, &e->allocator); // real construction @@ -2381,7 +2499,7 @@ execute(Execute *e, size_t nm, minrx_regmatch_t *rm) for (size_t i = 0; i < e->r->nmin; ++i) cowvec_put(&nsinit.substack, e->nestoff + i, 0); execute_add(e, &mcsvs[0], 0, 0, &nsinit, wcnext); - if (!qset_empty(&e->epsq)) + if (!qset_empty(e->epsq)) execute_epsclosure(e, &mcsvs[0], wcnext); for (;;) { // unrolled to ping-pong roles of mcsvs[0]/[1] if (wcnext == End) @@ -2396,7 +2514,7 @@ execute(Execute *e, size_t nm, minrx_regmatch_t *rm) nsinit.boff = e->off; execute_add(e, &mcsvs[1], 0, 0, &nsinit, wcnext); } - if (!qset_empty(&e->epsq)) + if (!qset_empty(e->epsq)) execute_epsclosure(e, &mcsvs[1], wcnext); if (qvec_empty(&mcsvs[1])) { if (e->exiting) @@ -2416,7 +2534,7 @@ execute(Execute *e, size_t nm, minrx_regmatch_t *rm) nsinit.boff = e->off; execute_add(e, &mcsvs[0], 0, 0, &nsinit, wcnext); } - if (!qset_empty(&e->epsq)) + if (!qset_empty(e->epsq)) execute_epsclosure(e, &mcsvs[0], wcnext); if (qvec_empty(&mcsvs[0])) { if (e->exiting) @@ -2427,8 +2545,6 @@ execute(Execute *e, size_t nm, minrx_regmatch_t *rm) } exit: nstate_destruct(&nsinit); - qvec_destruct(&mcsvs[1]); - qvec_destruct(&mcsvs[0]); if (cowvec_valid(&e->best)) { if (rm) { size_t nsub = MIN(nm, e->r->nsub); @@ -2504,7 +2620,7 @@ minrx_regnexec(minrx_regex_t *rx, size_t ns, const char *s, size_t nm, minrx_reg if (!execute_construct(&e, r, (minrx_regexec_flags_t) flags, s, s + ns)) return MINRX_REG_ESPACE; int ret = execute(&e, nm, rm); - execute_destruct(&e); + execute_destruct(&e, ret == MINRX_REG_SUCCESS || ret == MINRX_REG_NOMATCH); return ret; } @@ -2513,6 +2629,7 @@ minrx_regfree(minrx_regex_t *rx) { Regexp *r = (Regexp *) rx->re_regexp; if (r) { + scratch_fini(&r->scratch); if (r->firstcset) { cset_destruct(r->firstcset); free((void *) r->firstcset); -- cgit v1.3