From: Ulya Trofimovich Date: Fri, 14 Apr 2017 17:47:11 +0000 (+0100) Subject: Forbid dependency cycles in tag commands. X-Git-Tag: 1.0~39^2~57 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6d15ecc5ef807c646edf9b51ab36e7e128994b62;p=re2c Forbid dependency cycles in tag commands. The previous attempt was to forbid 2-cycles; clearly it's not enough and cycles of length greater than 2 also pose a problem. We could allow cycles and deal with them by introducind a temporary variable. However, this would create variables local to basic blocks, which would complicate liveness analysis and dead code elimination. --- diff --git a/re2c/src/dfa/find_state.cc b/re2c/src/dfa/find_state.cc index 7ba1f235..f2d9d0ba 100644 --- a/re2c/src/dfa/find_state.cc +++ b/re2c/src/dfa/find_state.cc @@ -87,6 +87,29 @@ struct kernel_eq_t * identical lookahead tags. */ +/* note [save(X), copy(Y,X) optimization] + * + * save(X) command followed by a copy(Y,X) command can be optimized to + * save(Y). This helps reduce the number commands and versions (new version + * X is gone), but what is more important, it allows to put copy commands + * in front of save commands. This order is necessary when it comes to + * fallback commands. + * + * Note that in case of injective mapping there may be more than one copy + * command matching the same save command: save(X), copy(Y,X), copy(Z,X). + * In this case save command must be replicated for each copy command: + * save(Y), save(Z). + * + * For each save(X) command there must be at least one copy(Y,X) command + * (exactly one case of bijective mapping). This is because X version in + * save(X) command must be a new version which cannot occur in the older + * DFA state. Thus all save commands are transformed (maybe replicated) by + * copy commands, and some copy commands are erased by save commands. + * + * This optimization is applied after checking priority violation, so it + * cannot affect the check. +*/ + bool kernels_t::operator()(const kernel_t *k1, const kernel_t *k2) { // check that kernel sizes, NFA states and orders coincide @@ -125,13 +148,57 @@ bool kernels_t::operator()(const kernel_t *k1, const kernel_t *k2) } } - // forbid 2-cycles 'x = y; y = x;': to avoid temporary variables + // we have bijective mapping; now try to create list of commands + tcmd_t *a, **pa, *copy = NULL; + + // backup 'save' commands: if topsort finds cycles, this mapping + // will be rejected and we'll have to revert all changes + size_t nact = 0; + for (a = *pacts; a; a = a->next) { + actnext[nact] = a; + actlhs[nact] = a->lhs; + ++nact; + } + + // fix LHS of 'save' commands to reuse old version + // see note [save(X), copy(Y,X) optimization] + for (a = *pacts; a; a = a->next) { + const tagver_t + y = a->lhs * (a->history[0] == TAGVER_BOTTOM ? -1 : 1), + x = y2x[y]; + a->lhs = abs(x); + y2x[y] = x2y[x] = TAGVER_ZERO; + } + + // create 'copy' commands for (tagver_t x = -max; x < max; ++x) { - const tagver_t y = x2y[x]; - if (x != y && x2y[y] == x) return false; + const tagver_t y = x2y[x], ax = abs(x), ay = abs(y); + if (y != TAGVER_ZERO && x != y && !fixed(tags[x2t[x]])) { + assert(ax != ay); + copy = tcpool.make_copy(copy, ax, ay); + } } - return true; + // join 'copy' and 'save' commands + for (pa = © (a = *pa); pa = &a->next); + *pa = *pacts; + *pacts = copy; + + // see note [topological ordering of copy commands] + const bool acyclic = tcmd_t::topsort(pacts, indeg); + + // in case of cycles restore 'save' commands and fail + if (!acyclic) { + pa = pacts; + for (size_t i = 0; i < nact; ++i) { + *pa = a = actnext[i]; + a->lhs = actlhs[i]; + pa = &a->next; + } + *pa = NULL; + } + + return acyclic; } kernels_t::kernels_t(Tagpool &tagp, tcpool_t &tcp, const std::vector &ts) @@ -150,6 +217,10 @@ kernels_t::kernels_t(Tagpool &tagp, tcpool_t &tcp, const std::vector &ts) , y2x(NULL) , x2t(NULL) , indeg(NULL) + + , pacts(NULL) + , actnext(NULL) + , actlhs(NULL) {} kernels_t::~kernels_t() @@ -181,19 +252,20 @@ void kernels_t::init(tagver_t v, size_t nkern) m = 2 * n + 1, sz_x2y = 2 * m * sizeof(tagver_t), sz_x2t = m * sizeof(size_t), + sz_actnext = n * sizeof(tcmd_t*), + sz_actlhs = n * sizeof(tagver_t), sz_indeg = n * sizeof(uint32_t); delete[] mem; - mem = new char[sz_x2y + sz_x2t + sz_indeg]; + mem = new char[sz_x2y + sz_x2t + sz_actnext + sz_actlhs + sz_indeg]; // point to the center (zero index) of each buffer // indexes in range [-N .. N] must be valid, where N is capacity - x2y = reinterpret_cast(mem) + cap; - y2x = x2y + m; - x2t = reinterpret_cast(mem + sz_x2y) + cap; - indeg = reinterpret_cast(mem + sz_x2y + sz_x2t); - - // see note [topological ordering of copy commands] - memset(indeg, 0, sz_indeg); + x2y = reinterpret_cast(mem) + cap; + y2x = x2y + m; + x2t = reinterpret_cast(mem + sz_x2y) + cap; + actnext = reinterpret_cast(mem + sz_x2y + sz_x2t); + actlhs = reinterpret_cast(mem + sz_x2y + sz_x2t + sz_actnext); + indeg = reinterpret_cast(mem + sz_x2y + sz_x2t + sz_actnext + sz_actlhs); } } @@ -261,69 +333,15 @@ kernels_t::result_t kernels_t::insert(const closure_t &clos, // else try to find mappable kernel // see note [bijective mappings] + this->pacts = &acts; x = lookup.find_with(hash, buffer, *this); - if (x != index_t::NIL) return result_t(x, actions(acts), false); + if (x != index_t::NIL) return result_t(x, acts, false); // otherwise add new kernel x = lookup.push(hash, kernel_t::copy(*buffer)); return result_t(x, acts, true); } -/* note [save(X), copy(Y,X) optimization] - * - * save(X) command followed by a copy(Y,X) command can be optimized to - * save(Y). This helps reduce the number commands and versions (new version - * X is gone), but what is more important, it allows to put copy commands - * in front of save commands. This order is necessary when it comes to - * fallback commands. - * - * Note that in case of injective mapping there may be more than one copy - * command matching the same save command: save(X), copy(Y,X), copy(Z,X). - * In this case save command must be replicated for each copy command: - * save(Y), save(Z). - * - * For each save(X) command there must be at least one copy(Y,X) command - * (exactly one case of bijective mapping). This is because X version in - * save(X) command must be a new version which cannot occur in the older - * DFA state. Thus all save commands are transformed (maybe replicated) by - * copy commands, and some copy commands are erased by save commands. - * - * This optimization is applied after checking priority violation, so it - * cannot affect the check. -*/ - -tcmd_t *kernels_t::actions(tcmd_t *acts) -{ - tcmd_t *copy = NULL, *a, **pa; - - // fix LHS of 'save' commands to reuse old version - // see note [save(X), copy(Y,X) optimization] - for (a = acts; a; a = a->next) { - const tagver_t - y = a->lhs * (a->history[0] == TAGVER_BOTTOM ? -1 : 1), - x = y2x[y]; - a->lhs = abs(x); - y2x[y] = x2y[x] = TAGVER_ZERO; - } - - for (tagver_t x = -max; x < max; ++x) { - const tagver_t y = x2y[x], ax = abs(x), ay = abs(y); - if (y != TAGVER_ZERO && x != y && !fixed(tags[x2t[x]])) { - assert(ax != ay); - copy = tcpool.make_copy(copy, ax, ay); - } - } - - // join 'copy' and 'save' commands - for (pa = © *pa; pa = &(*pa)->next); - *pa = acts; - - // see note [topological ordering of copy commands] - tcmd_t::topsort(©, indeg); - - return copy; -} - static tcmd_t *finalizer(const clos_t &clos, size_t ridx, dfa_t &dfa, const Tagpool &tagpool, const std::vector &tags) { diff --git a/re2c/src/dfa/find_state.h b/re2c/src/dfa/find_state.h index ecb6f347..17be9a0f 100644 --- a/re2c/src/dfa/find_state.h +++ b/re2c/src/dfa/find_state.h @@ -59,6 +59,10 @@ private: size_t *x2t; uint32_t *indeg; + tcmd_t **pacts; + tcmd_t **actnext; + tagver_t *actlhs; + public: kernels_t(Tagpool &tagpool, tcpool_t &tcpool, const std::vector &tags); ~kernels_t(); @@ -67,7 +71,6 @@ public: const kernel_t* operator[](size_t idx) const; result_t insert(const closure_t &clos, tcmd_t *acts, tagver_t maxver); bool operator()(const kernel_t *k1, const kernel_t *k2); - tcmd_t *actions(tcmd_t *save); FORBID_COPY(kernels_t); }; diff --git a/re2c/src/dfa/tcmd.cc b/re2c/src/dfa/tcmd.cc index c8d5a14d..5f117490 100644 --- a/re2c/src/dfa/tcmd.cc +++ b/re2c/src/dfa/tcmd.cc @@ -67,11 +67,12 @@ bool tcmd_t::isadd(const tcmd_t *x) return x->rhs != TAGVER_ZERO && x->history[0] != TAGVER_ZERO; } -void tcmd_t::topsort(tcmd_t **phead, uint32_t *indeg) +bool tcmd_t::topsort(tcmd_t **phead, uint32_t *indeg) { tcmd_t *x0 = *phead, **px, *x, *y0 = NULL, **py, **py1; + bool acyclic = true; // initialize in-degree for (x = x0; x; x = x->next) { @@ -100,11 +101,15 @@ void tcmd_t::topsort(tcmd_t **phead, uint32_t *indeg) *px = NULL; // only cycles left - if (py == py1) break; + if (py == py1) { + acyclic = false; + break; + } } *py = x0; *phead = y0; + return acyclic; } tcpool_t::tcpool_t() diff --git a/re2c/src/dfa/tcmd.h b/re2c/src/dfa/tcmd.h index 9791a617..4dde594f 100644 --- a/re2c/src/dfa/tcmd.h +++ b/re2c/src/dfa/tcmd.h @@ -20,7 +20,7 @@ struct tcmd_t static bool equal(const tcmd_t &x, const tcmd_t &y); static bool equal_history(const tagver_t *h, const tagver_t *g); - static void topsort(tcmd_t **phead, uint32_t *indeg); + static bool topsort(tcmd_t **phead, uint32_t *indeg); static bool iscopy(const tcmd_t *cmd); static bool isset(const tcmd_t *cmd); static bool isadd(const tcmd_t *cmd);