bool kernel_map_t<ctx_t, regless>::operator()(const kernel_t *x, const kernel_t *y)
{
    // check that kernel sizes, NFA states lookahead tags
    // and precedence table coincide (versions might differ)
    const size_t n = x->size;
    const bool compatible = n == y->size
        && memcmp(x->state, y->state, n * sizeof(void*)) == 0
        && (!x->prectbl || memcmp(x->prectbl, y->prectbl, n * n * sizeof(prectable_t)) == 0)
        && equal_lookahead_tags(ctx, x, y);
    if (!compatible) return false;

    const std::vector<Tag> &tags = ctx.dfa.tags;
    const size_t ntag = tags.size();
    kernel_buffers_t &bufs = ctx.dc_buffers;
    tagver_t *x2y = bufs.x2y, *y2x = bufs.y2x, max = bufs.max;
    size_t *x2t = bufs.x2t;

    // map tag versions of one kernel to that of another
    // and check that lookahead versions (if any) coincide
    std::fill(x2y, x2y + max, TAGVER_ZERO);
    std::fill(y2x, y2x + max, TAGVER_ZERO);
    for (size_t i = 0; i < n; ++i) {
        const tagver_t
            *xvs = ctx.dc_tagvertbl[x->tvers[i]],
            *yvs = ctx.dc_tagvertbl[y->tvers[i]];
        const hidx_t xl = x->thist[i];

        for (size_t t = 0; t < ntag; ++t) {
            // see note [mapping ignores items with lookahead tags]
            if (!history(tags[t])
                && last(ctx.history, xl, t) != TAGVER_ZERO) continue;

            const tagver_t xv = xvs[t], yv = yvs[t];
            tagver_t &xv0 = y2x[yv], &yv0 = x2y[xv];

            if (yv0 == TAGVER_ZERO && xv0 == TAGVER_ZERO) {
                xv0 = xv;
                yv0 = yv;
                x2t[xv] = t;
            } else if (yv != yv0 || xv != xv0) {
                return false;
            }
        }
    }

    // Registerless automaton does not need register operations (actions).
    if (regless) return true;

    // we have bijective mapping; now try to create list of commands
    tcmd_t **pacts = &ctx.dc_actions, *a, **pa, *copy = NULL;
    tcmd_t *b1 = bufs.backup_actions, *b2 = b1;

    // backup 'save' commands: if topsort finds cycles, this mapping
    // will be rejected and we'll have to revert all changes
    for (b2->next = a = *pacts; a; a = a->next) {
        *++b2 = *a;
    }

    // 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 yv = a->lhs, xv = y2x[yv];
        a->lhs = abs(xv);
        y2x[yv] = x2y[xv] = TAGVER_ZERO;
    }

    // create 'copy' commands
    for (tagver_t xv = 0; xv < max; ++xv) {
        const tagver_t yv = x2y[xv], axv = abs(xv), ayv = abs(yv);
        if (yv != TAGVER_ZERO && xv != yv && !fixed(tags[x2t[xv]])) {
            DASSERT(axv != ayv);
            copy = ctx.dfa.tcpool.make_copy(copy, axv, ayv);
        }
    }

    // join 'copy' and 'save' commands
    for (pa = &copy; (a = *pa); pa = &a->next);
    *pa = *pacts;
    *pacts = copy;

    // see note [topological ordering of copy commands]
    tcmd_t **cycle = tcmd_t::topsort(pacts, NULL, bufs.indegree);

    // in case of cycles restore 'save' commands and fail
    if (cycle) {
        for (*pacts = a = b1->next; a; a = a->next) {
            *a = *++b1;
        }
    }

    return cycle == NULL;
}