/* SPDX-License-Identifier: GPL-3.0-or-later
 *
 * Microcode programmable logic	matrix analyzer.
 * Copyright 2015-2020 Viacheslav Ovsiienko <1801BM1@gmail.com>
 *
 * This	program	is free	software: you can redistribute it and/or modify
 * it under the	terms of the GNU General Public	License	version	3
 * as published	by the Free Software Foundation.
 *
 * This	program	is distributed in the hope that	it will	be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see	<http://www.gnu.org/licenses/>.
 */
#include "mcode.h"

enum arr_index {
	ARR_VALUE = 0,		/* original values */
	ARR_REDUX,		/* reduced after essentials */
	ARR_CHECK,		/* gathering for check */
	ARR_PRIME_V,		/* prime implicant values */
	ARR_PRIME_M,		/* prime implicant masks */
	ARR_ESSEN_V,		/* essential implicant values */
	ARR_ESSEN_M,		/* essential implicant mask */
	ARR_MAX
};

#define QMC_NBIT   16			/* width of input field in bits */
#define QMC_NMAX   (1 << QMC_NBIT)	/* array sizes for full range */
#define QMC_ALIGN  16			/* buffer alignments in qmc_t */
#define QMC_EXT	   0x4000		/* number of elements to extend */
#define QMC_INI	   64			/* initial number of elements */

typedef uint16_t   qmc_t;		/* basic data element */
typedef uint32_t   len_t;		/* data element array length */

struct arr {
	len_t	size;			/* array size in elements */
	len_t	len;			/* number of stored elements */
					/* alignment for AVX2 vectors */
	uint8_t align[QMC_ALIGN * sizeof(qmc_t) - 2 * sizeof(len_t)];
	qmc_t	data[0];		/* data array */
};

struct grp {
	len_t	    ncnt;		/* number of nodes */
	struct arr  *xmsk;		/* array of X-masks */
	struct arr  *node[0];		/* array of data for masks */
};

/* pointers to lowest level loop routines, can be vectorized */
static int (*qmc_check)(const struct arr *a, qmc_t value);
static void (*qmc_scan)(qmc_t term, qmc_t xmask,
			const struct arr *array,
			struct grp *prime);
static void (*qmc_term)(qmc_t term, qmc_t xmask,
			const struct arr *anext,
			const struct grp *gprev,
			struct grp *merge,
			struct grp *prime);
#ifdef __GNUC__
static inline void *_aligned_malloc(size_t size, size_t align)
{
	return aligned_alloc(align, size);
}

static inline void _aligned_free(void *p)
{
	free(p);
}
#endif

static void qmc_print(qmc_t term, qmc_t xmsk, char *line)
{
	uint16_t i = QMC_NBIT;

	do {
		line[i-1] = (xmsk & 1) ? 'x' : (term & 1) + '0';
		term >>= 1;
		xmsk >>= 1;
	} while (--i);
	line[QMC_NBIT] = 0;
}

static void arr_print(struct arr *av, char *text)
{
	len_t i;

	if (!av || !av->len) {
		printf("%s (empty)\n", text);
		return;
	}
	printf("%s:\n", text);
	for (i = 0; i < av->len; i++) {
		char line[QMC_NBIT + 1];

		qmc_print(av->data[i], 0, line);
		printf("  %s\n", line);
	}
}

static void arr_print2(struct arr *av, struct arr *am, char *text)
{
	len_t i;

	if (!av || !am || !av->len || !am->len) {
		printf("%s (empty)\n", text);
		return;
	}
	printf("%s: %u\n", text, av->len);
	for (i = 0; i < av->len; i++) {
		char line[QMC_NBIT + 1];

		qmc_print(av->data[i], am->data[i], line);
		printf("  %s\n", line);
	}
}

static void grp_print(struct grp *group, char *text)
{
	char line[QMC_NBIT + 1];
	struct arr *x = group->xmsk;
	len_t i, j;

	if (!x || !x->len) {
		printf("%s (empty)\n", text);
		return;
	}
	printf("%s:\n", text);
	for (i = 0; i < x->len; i++) {
		qmc_t xmsk = x->data[i];
		struct arr *d = group->node[xmsk];

		if (!d || !d->len)
			continue;
		for (j = 0; j < d->len; j++) {
			qmc_print(d->data[j], xmsk, line);
			printf("  %s\n", line);
		}
	}
}

/* Frees the allocated arr structure */
static inline void arr_free(struct arr *a)
{
	_aligned_free(a);
}

/*
 * Reallocates the arr structure to accomodate the specified amount of
 * elements, if the first parameter is not NULL the elements from this
 * are copied to the new allocated structure.
 */
static struct arr *arr_alloc(struct arr *a, len_t size)
{
	struct arr *p;

	if (a && a->size >= size)
		return a;

	size = (size + QMC_ALIGN - 1) & ~(QMC_ALIGN - 1);
	p = _aligned_malloc(size * sizeof(qmc_t) + sizeof(struct arr),
			    QMC_ALIGN * sizeof(qmc_t));
	if (!p) {
		printf("mcode: memory allocation failure (%u)\n", size);
		exit(-1);
	}
	if (a) {
		p->size = size;
		p->len = a->len;
		assert(p->size >= a->size);
		memcpy(p->data, a->data, a->len * sizeof(qmc_t));
		arr_free(a);
	} else {
		memset(p, 0, sizeof(struct arr));
		p->size = size;
	}
	return p;
}

/*
 * Extends the arr structure to accomodate the specified amount of
 * new elements is going to be appended to existing ones.
 */
static inline void arr_extend(struct arr **pa, len_t size)
{
	struct arr *a = *pa;
	len_t asiz = a->size;

	size += a->len;
	if (size <= asiz)
		return;
	do {
		asiz = (asiz > QMC_EXT) ?
		       (size + QMC_EXT - 1) & ~(QMC_EXT - 1) : asiz * 2;
	} while (asiz < size);
	*pa = arr_alloc(a, asiz);
}

/* Appends single element to the and of data array */
static inline void arr_append(struct arr **pa, qmc_t value)
{
	arr_extend(pa, 1);
	pa[0]->data[pa[0]->len++] = value;

	assert(pa[0]->len <= QMC_NMAX);
}

static len_t grp_len(const struct grp *g)
{
	len_t len = 0;

	if (g && g->xmsk && g->xmsk->len) {
		struct arr *x = g->xmsk;
		len_t i;

		for (i = 0; i < x->len; i++)
			len += g->node[x->data[i]]->len;
	}
	return len;
}

static void grp_prime(struct grp *group, struct grp *store)
{
	struct arr *x = group->xmsk;
	len_t len = grp_len(group);
	len_t i, j;

	if (x == NULL || x->len == 0 || len == 0)
		return;
	arr_free(store->node[ARR_PRIME_V]);
	arr_free(store->node[ARR_PRIME_M]);
	store->node[ARR_PRIME_V] = arr_alloc(NULL, len);
	store->node[ARR_PRIME_M] = arr_alloc(NULL, len);
	for (i = 0; i < x->len; i++) {
		qmc_t xmsk = x->data[i];
		struct arr *d = group->node[xmsk];

		if (!d || !d->len)
			continue;
		for (j = 0; j < d->len; j++) {
			arr_append(&store->node[ARR_PRIME_V], d->data[j]);
			arr_append(&store->node[ARR_PRIME_M], xmsk);
		}
	}
}

/* Frees the allocated grp structure */
static inline void grp_free(struct grp *g)
{
	if (!g)
		return;
	if (g->xmsk && g->xmsk->len) {
		struct arr *x = g->xmsk;
		len_t i;

		for (i = 0; i < x->len; i++)
			arr_free(g->node[x->data[i]]);
	}
	_aligned_free(g);
}

static struct grp *grp_alloc(len_t ncnt)
{
	struct grp *p;

	p = _aligned_malloc(sizeof(struct grp) + ncnt * sizeof(struct arr *),
			    QMC_ALIGN * sizeof(qmc_t));
	if (!p) {
		printf("mcode: memory allocation failure (GRP)\n");
		exit(-1);
	}
	memset(p, 0, sizeof(struct grp) + ncnt * sizeof(struct arr *));
	p->ncnt = ncnt;
	p->xmsk = arr_alloc(NULL, QMC_INI);
	return p;
}

static int qmc_check_u32(const struct arr *a, qmc_t value)
{
	const qmc_t *p;
	len_t n;

	if (!a || !a->len)
		return 0;
	n = a->len;
	p = a->data;

	do {
		if (*p++ == value)
			return 1;
	} while (--n);
	return 0;
}

static int qmc_check_128(const struct arr *a, qmc_t value)
{
	const qmc_t *p;
	len_t n, vn;

	if (!a || !a->len)
		return 0;
	n = a->len;
	p = a->data;
	vn = n >> 3;
	if (vn) {
		const register __m128i vc = _mm_set1_epi16(value);
		register __m128i vx;
		const __m128i *vp = (const __m128i *)p;

		p += (uintptr_t)vn << 3;
		/* SSE2/128-bit code, handles eight elements per iteration */
		do {
			vx = _mm_load_si128(vp);
			vx = _mm_cmpeq_epi16(vx, vc);
			if (_mm_movemask_epi8(vx))
				return 1;
			++vp;
		} while (--vn);
		n &= (8 - 1);
	}
	while (n--) {
		if (*p++ == value)
			return 1;
	}
	return 0;
}

static int qmc_check_256(const struct arr *a, qmc_t value)
{
	const qmc_t *p;
	len_t n, vn;

	if (!a || !a->len)
		return 0;
	n = a->len;
	p = a->data;
	vn = n >> 4;
	if (vn) {
		const register __m256i vc = _mm256_set1_epi16(value);
		register __m256i vx;
		const __m256i *vp = (const __m256i *)p;

		p += (uintptr_t)vn << 4;
		/* AVX2/256-bit code, handles sixteen elements per iteration */
		do {
			vx = _mm256_load_si256(vp);
			vx = _mm256_cmpeq_epi16(vx, vc);
			if (_mm256_movemask_epi8(vx))
				return 1;
			++vp;
		} while (--vn);
		n &= (16 - 1);
	}
	while (n--) {
		if (*p++ == value)
			return 1;
	}
	return 0;
}

static void grp_append(struct grp *g, qmc_t term, qmc_t xmsk)
{
	if (!g->node[xmsk]) {
		g->node[xmsk] = arr_alloc(NULL, QMC_INI);
		arr_append(&g->xmsk, xmsk);
		arr_append(&g->node[xmsk], term);
		return;
	}
	if (qmc_check(g->node[xmsk], term))
		return;
	arr_append(&g->node[xmsk], term);
}

static inline void grp_cat(struct grp *prime, const struct grp *group)
{
	if (group && group->xmsk && group->xmsk->len) {
		struct arr *x = group->xmsk;
		len_t i, j;

		for (i = 0; i < x->len; i++) {
			const qmc_t xm = x->data[i];
			struct arr *a = group->node[xm];
			const len_t len = a->len;

			if (len)
				for (j = 0; j < len; j++)
					grp_append(prime, a->data[j], xm);
		}
	}
}

static void qmc_scan_u32(qmc_t term, qmc_t xmask,
			 const struct arr *array,
			 struct grp *prime)
{
	const qmc_t *p = array->data;
	len_t n = array->len;

	while (n--) {
		qmc_t v = term ^ *p++;

		assert(v);
		if ((v & (v - 1)) == 0)
			return;
	}
	grp_append(prime, term, xmask);
}

static void qmc_scan_128(qmc_t term, qmc_t xmask,
			 const struct arr *array,
			 struct grp *prime)
{
	const qmc_t *p = array->data;
	len_t n = array->len;
	len_t vn = n >> 3;

	if (vn) {
		const register __m128i vc = _mm_set1_epi16(term);
		const register __m128i vo = _mm_set1_epi16(1);
		const register __m128i vz = _mm_setzero_si128();
		register __m128i vx, vt;
		const __m128i *vp = (const __m128i *)p;

		p += (uintptr_t)vn << 3;
		/* SSE2/128-bit code, handles eight elements per iteration */
		do {
			vx = _mm_load_si128(vp);
			vx = _mm_xor_si128(vx, vc);
			vt = _mm_sub_epi16(vx, vo);
			vx = _mm_and_si128(vx, vt);
			vx = _mm_cmpeq_epi16(vx, vz);
			if (_mm_movemask_epi8(vx))
				return;
			++vp;
		} while (--vn);
		n &= (8 - 1);
	}
	while (n--) {
		qmc_t v = term ^ *p++;

		if ((v & (v - 1)) == 0)
			return;
	}
	grp_append(prime, term, xmask);
}

static void qmc_scan_256(qmc_t term, qmc_t xmask,
	const struct arr *array,
	struct grp *prime)
{
	const qmc_t *p = array->data;
	len_t n = array->len;
	len_t vn = n >> 4;

	if (vn) {
		const register __m256i vc = _mm256_set1_epi16(term);
		const register __m256i vo = _mm256_set1_epi16(1);
		const register __m256i vz = _mm256_setzero_si256();
		register __m256i vx, vt;
		const __m256i *vp = (const __m256i *)p;

		p += (uintptr_t)vn << 4;
		/* AVX2/256-bit code, handles sixteen elements per iteration */
		do {
			vx = _mm256_load_si256(vp);
			vx = _mm256_xor_si256(vx, vc);
			vt = _mm256_sub_epi16(vx, vo);
			vx = _mm256_and_si256(vx, vt);
			vx = _mm256_cmpeq_epi16(vx, vz);
			if (_mm256_movemask_epi8(vx))
				return;
			++vp;
		} while (--vn);
		n &= (16 - 1);
	}
	while (n--) {
		qmc_t v = term ^ *p++;

		if ((v & (v - 1)) == 0)
			return;
	}
	grp_append(prime, term, xmask);
}

/* Scans array of terms in the specified group, not found is added to primes */
static inline void arr_scan(const struct arr *array,
			    qmc_t xmask,
			    const struct grp *group,
			    struct grp *prime)
{
	const struct arr *ga = group->node[xmask];
	const qmc_t *p = array->data;
	len_t n = array->len;

	assert(n);
	if (ga)
		do {
			qmc_scan(*p++, xmask, ga, prime);
		} while (--n);
	else
		do {
			grp_append(prime, *p++, xmask);
		} while (--n);
}

static void qmc_term_u32(qmc_t term, qmc_t xmask,
			 const struct arr *anext,
			 const struct grp *gprev,
			 struct grp *merge,
			 struct grp *prime)
{
	if (anext) {
		const qmc_t *p = anext->data;
		len_t n = anext->len;
		len_t m = 0;

		assert(n);
		while (n--) {
			qmc_t v = term ^ *p++;

			assert(v);
			if ((v & (v - 1)) == 0) {
				grp_append(merge, term & ~v, xmask | v);
				m++;
			}
		}
		if (m)
			return;
	}
	if (gprev && gprev->node[xmask])
		qmc_scan(term, xmask, gprev->node[xmask], prime);
	else
		grp_append(prime, term, xmask);
}

static void qmc_term_128(qmc_t term, qmc_t xmask,
	const struct arr *anext,
	const struct grp *gprev,
	struct grp *merge,
	struct grp *prime)
{
	if (anext) {
		const qmc_t *p = anext->data;
		len_t n = anext->len;
		len_t m = 0;
		len_t vn = n >> 3;

		if (vn) {
			const register __m128i vc = _mm_set1_epi16(term);
			const register __m128i vs = _mm_set1_epi16(xmask);
			const register __m128i vo = _mm_set1_epi16(1);
			const register __m128i vz = _mm_setzero_si128();
			register __m128i vx, vt;
			const __m128i *vp = (const __m128i *)p;
			uint32_t vm;

			p += (uintptr_t)vn << 3;
			/* SSE2/128-bit code, handles eight elements */
			do {
				vx = _mm_load_si128(vp);
				vx = _mm_xor_si128(vx, vc);	/* ^ term */
				vt = _mm_sub_epi16(vx, vo);	/* v - 1 */
				vt = _mm_and_si128(vx, vt);	/* v & (v-1) */
				vt = _mm_cmpeq_epi16(vt, vz);
				vm = _mm_movemask_epi8(vt);
				++vp;
				if (!vm)
					continue;
				m++;
				vt = _mm_xor_si128(vt, vx);	/* ~v */
				vt = _mm_and_si128(vt, vc);	/* & term */
				vx = _mm_or_si128(vx, vs);	/* | mask */

				if (vm & (3ul << 0))
					grp_append(merge,
						_mm_extract_epi16(vt, 0),
						_mm_extract_epi16(vx, 0));
				if (vm & (3ul << 2))
					grp_append(merge,
						_mm_extract_epi16(vt, 1),
						_mm_extract_epi16(vx, 1));
				if (vm & (3ul << 4))
					grp_append(merge,
						_mm_extract_epi16(vt, 2),
						_mm_extract_epi16(vx, 2));
				if (vm & (3ul << 6))
					grp_append(merge,
						_mm_extract_epi16(vt, 3),
						_mm_extract_epi16(vx, 3));
				vm &= ~0x000000FF;
				if (!vm)
					continue;

				if (vm & (3ul << 8))
					grp_append(merge,
						_mm_extract_epi16(vt, 4),
						_mm_extract_epi16(vx, 4));
				if (vm & (3ul << 10))
					grp_append(merge,
						_mm_extract_epi16(vt, 5),
						_mm_extract_epi16(vx, 5));
				if (vm & (3ul << 12))
					grp_append(merge,
						_mm_extract_epi16(vt, 6),
						_mm_extract_epi16(vx, 6));
				if (vm & (3ul << 14))
					grp_append(merge,
						_mm_extract_epi16(vt, 7),
						_mm_extract_epi16(vx, 7));
			} while (--vn);
			n &= (8 - 1);
		}
		while (n--) {
			qmc_t v = term ^ *p++;

			assert(v);
			if ((v & (v - 1)) == 0) {
				grp_append(merge, term & ~v, xmask | v);
				m++;
			}
		}
		if (m)
			return;
	}
	if (gprev && gprev->node[xmask])
		qmc_scan(term, xmask, gprev->node[xmask], prime);
	else
		grp_append(prime, term, xmask);
}

static void qmc_term_256(qmc_t term, qmc_t xmask,
	const struct arr *anext,
	const struct grp *gprev,
	struct grp *merge,
	struct grp *prime)
{
	if (anext) {
		const qmc_t *p = anext->data;
		len_t n = anext->len;
		len_t m = 0;
		len_t vn = n >> 4;

		if (vn) {
			const register __m256i vc = _mm256_set1_epi16(term);
			const register __m256i vs = _mm256_set1_epi16(xmask);
			const register __m256i vo = _mm256_set1_epi16(1);
			const register __m256i vz = _mm256_setzero_si256();
			register __m256i vx, vt;
			const __m256i *vp = (const __m256i *)p;
			uint32_t vm;

			p += (uintptr_t)vn << 4;
			/* AVX2/256-bit code, handles sixteen elements */
			do {
				vx = _mm256_load_si256(vp);
				vx = _mm256_xor_si256(vx, vc);	/* ^ term */
				vt = _mm256_sub_epi16(vx, vo);  /* v - 1 */
				vt = _mm256_and_si256(vx, vt);  /* v & (v-1) */
				vt = _mm256_cmpeq_epi16(vt, vz);
				vm = _mm256_movemask_epi8(vt);
				++vp;
				if (!vm)
					continue;
				m++;
				vt = _mm256_xor_si256(vt, vx);	/* ~v */
				vt = _mm256_and_si256(vt, vc);	/* & term */
				vx = _mm256_or_si256(vx, vs);	/* | mask */

				if (vm & (3ul << 0))
					grp_append(merge,
						_mm256_extract_epi16(vt, 0),
						_mm256_extract_epi16(vx, 0));
				if (vm & (3ul << 2))
					grp_append(merge,
						_mm256_extract_epi16(vt, 1),
						_mm256_extract_epi16(vx, 1));
				if (vm & (3ul << 4))
					grp_append(merge,
						_mm256_extract_epi16(vt, 2),
						_mm256_extract_epi16(vx, 2));
				if (vm & (3ul << 6))
					grp_append(merge,
						_mm256_extract_epi16(vt, 3),
						_mm256_extract_epi16(vx, 3));
				vm &= ~0x000000FF;
				if (!vm)
					continue;

				if (vm & (3ul << 8))
					grp_append(merge,
						_mm256_extract_epi16(vt, 4),
						_mm256_extract_epi16(vx, 4));
				if (vm & (3ul << 10))
					grp_append(merge,
						_mm256_extract_epi16(vt, 5),
						_mm256_extract_epi16(vx, 5));
				if (vm & (3ul << 12))
					grp_append(merge,
						_mm256_extract_epi16(vt, 6),
						_mm256_extract_epi16(vx, 6));
				if (vm & (3ul << 14))
					grp_append(merge,
						_mm256_extract_epi16(vt, 7),
						_mm256_extract_epi16(vx, 7));
				vm &= ~0x0000FF00;
				if (!vm)
					continue;

				if (vm & (3ul << 16))
					grp_append(merge,
						_mm256_extract_epi16(vt, 8),
						_mm256_extract_epi16(vx, 8));
				if (vm & (3ul << 18))
					grp_append(merge,
						_mm256_extract_epi16(vt, 9),
						_mm256_extract_epi16(vx, 9));
				if (vm & (3ul << 20))
					grp_append(merge,
						_mm256_extract_epi16(vt, 10),
						_mm256_extract_epi16(vx, 10));
				if (vm & (3ul << 22))
					grp_append(merge,
						_mm256_extract_epi16(vt, 11),
						_mm256_extract_epi16(vx, 11));
				vm &= ~0x00FF0000;
				if (!vm)
					continue;

				if (vm & (3ul << 24))
					grp_append(merge,
						_mm256_extract_epi16(vt, 12),
						_mm256_extract_epi16(vx, 12));
				if (vm & (3ul << 26))
					grp_append(merge,
						_mm256_extract_epi16(vt, 13),
						_mm256_extract_epi16(vx, 13));
				if (vm & (3ul << 28))
					grp_append(merge,
						_mm256_extract_epi16(vt, 14),
						_mm256_extract_epi16(vx, 14));
				if (vm & (3ul << 30))
					grp_append(merge,
						_mm256_extract_epi16(vt, 15),
						_mm256_extract_epi16(vx, 15));
			} while (--vn);
			n &= (16 - 1);
		}
		while (n--) {
			qmc_t v = term ^ *p++;

			assert(v);
			if ((v & (v - 1)) == 0) {
				grp_append(merge, term & ~v, xmask | v);
				m++;
			}
		}
		if (m)
			return;
	}
	if (gprev && gprev->node[xmask])
		qmc_scan(term, xmask, gprev->node[xmask], prime);
	else
		grp_append(prime, term, xmask);
}

/*
 * Scans terms from array in the next group, appends next order terms
 * to merge on found matches, if nothing found, scans the prev group,
 * if found neither it is appended to primes.
 */
static inline void arr_term(const struct arr *array,
			    qmc_t xmask,
			    const struct grp *gprev,
			    const struct grp *gnext,
			    struct grp *merge,
			    struct grp *prime)
{
	const qmc_t *p = array->data;
	len_t n = array->len;

	assert(n);
	while (n--)
		qmc_term(*p++, xmask, gnext->node[xmask], gprev, merge, prime);
}

/* Builds the next order terms for the group with the next neighbour only */
static inline struct grp *grp_merge_first(const struct grp *group,
					  const struct grp *gnext,
					  struct grp *prime)
{
	struct grp *merge = grp_alloc(QMC_NMAX);
	struct arr *x = group->xmsk;
	qmc_t *p;
	len_t n;

	if (!x || !x->len)
		return merge;
	if (!gnext->xmsk || !gnext->xmsk->len) {
		grp_cat(prime, group);
		return merge;
	}
	p = x->data;
	n = x->len;
	do {
		qmc_t xmask = *p++;

		arr_term(group->node[xmask], xmask, NULL, gnext, merge, prime);
	} while (--n);
	return merge;
}

/* Builds the next order terms for the group with the prev neighbour only */
static inline void grp_merge_last(const struct grp *group,
				  const struct grp *gprev,
				  struct grp *prime)
{
	struct arr *x = group->xmsk;
	qmc_t *p;
	len_t n;

	if (!x || !x->len)
		return;
	if (!gprev->xmsk || !gprev->xmsk->len) {
		grp_cat(prime, group);
		return;
	}
	p = x->data;
	n = x->len;
	do {
		qmc_t xmask = *p++;

		arr_scan(group->node[xmask], xmask, gprev, prime);
	} while (--n);
}

/* Builds the next order terms for the group with both neighbours */
static inline struct grp *grp_merge_next(const struct grp *group,
					 const struct grp *gprev,
					 const struct grp *gnext,
					 struct grp *prime)
{
	struct grp *merge = grp_alloc(QMC_NMAX);
	struct arr *x = group->xmsk;
	qmc_t *p;
	len_t n;

	if (!x || !x->len)
		return merge;
	if (!gnext->xmsk || !gnext->xmsk->len) {
		grp_merge_last(group, gprev, prime);
		return merge;
	}
	p = x->data;
	n = x->len;
	do {
		qmc_t xmask = *p++;

		arr_term(group->node[xmask], xmask,
			 gprev, gnext, merge, prime);
	} while (--n);
	return merge;
}

static void qmc16_order(struct grp **group, struct grp *prime, uint32_t n)
{
	struct grp *gprev = NULL;
	uint32_t i;

	for (i = 0; i <= QMC_NBIT - n; i++) {
		struct grp *gnext;
		uint64_t start;
		len_t len;

		 /* grp_print(group[i], "  group data"); */
		start = mc_query_ms();
		if (!i) {
			gnext = grp_merge_first(group[0], group[1], prime);
		} else if (i == QMC_NBIT - n) {
			grp_merge_last(group[i], gprev, prime);
			grp_free(gprev);
			gnext = NULL;
		} else {
			gnext = grp_merge_next(group[i], gprev,
					       group[i + 1], prime);
			grp_free(gprev);
		}
		gprev = group[i];
		group[i] = gnext;

		len = grp_len(gnext);
		if (len) {
			start = mc_query_ms() - start;
			printf("  round [%u,%u]", n, i);
			printf(" %u/%u (%" PRIu64 ".%03" PRIu64 " ms)\n",
			       len, grp_len(prime),
			       start / 1000, start % 1000);
		}
	}
}

/* Finding the prime minterms */
static void qmc16_essen(struct grp *store)
{
	len_t i, nv = store->node[ARR_VALUE]->len;
	uint64_t start;

	store->node[ARR_ESSEN_V] =
			arr_alloc(NULL, store->node[ARR_PRIME_V]->size);
	store->node[ARR_ESSEN_M] =
			arr_alloc(NULL, store->node[ARR_PRIME_V]->size);
	store->node[ARR_REDUX] = arr_alloc(NULL, QMC_INI);
	start = mc_query_ms();

	for (i = 0; i < nv; i++) {
		qmc_t v = store->node[ARR_VALUE]->data[i];
		qmc_t *pv = store->node[ARR_PRIME_V]->data;
		qmc_t *pm = store->node[ARR_PRIME_M]->data;
		len_t j, np = store->node[ARR_PRIME_V]->len;
		int ec = -1;

		for (j = 0; j < np; j++) {
			if ((v & ~*pm++) == *pv++) {
				if (ec >= 0)
					break;
				ec = j;
			}
		}
		assert(ec >= 0);
		if (j >= np) {
			if (!qmc_check(store->node[ARR_ESSEN_V], ec))
				arr_append(&store->node[ARR_ESSEN_V], ec);
		}
	}
	/* Convert pprime indices into values/masks. */
	nv = store->node[ARR_ESSEN_V]->len;
	for (i = 0; i < nv; i++) {
		len_t x = store->node[ARR_ESSEN_V]->data[i];

		store->node[ARR_ESSEN_V]->data[i] =
					store->node[ARR_PRIME_V]->data[x];
		store->node[ARR_ESSEN_M]->data[i] =
					store->node[ARR_PRIME_M]->data[x];
	}
	store->node[ARR_ESSEN_M]->len = store->node[ARR_ESSEN_V]->len;

	start = mc_query_ms() - start;
	arr_print2(store->node[ARR_ESSEN_V],
		   store->node[ARR_ESSEN_M],
		   "Found essential implicants");
	printf("Essentials done in: %" PRIu64 ".%03" PRIu64 "\n", start / 1000, start % 1000);

	/* Exclude values covered with essentials. */
	start = mc_query_ms();
	nv = store->node[ARR_VALUE]->len;
	for (i = 0; i < nv; i++) {
		qmc_t v = store->node[ARR_VALUE]->data[i];
		qmc_t *ev = store->node[ARR_ESSEN_V]->data;
		qmc_t *em = store->node[ARR_ESSEN_M]->data;
		len_t j, ne = store->node[ARR_ESSEN_V]->len;

		for (j = 0; j < ne; j++)
			if ((v & ~*em++) == *ev++)
				break;
		if (j >= ne)
			arr_append(&store->node[ARR_REDUX], v);
	}
	start = mc_query_ms() - start;
	printf("Not covered by essen %u/%u in: %" PRIu64 ".%03" PRIu64 "\n",
		store->node[ARR_REDUX]->len,
		store->node[ARR_VALUE]->len,
		start / 1000, start % 1000);
	if (cl_qmc > 2)
		arr_print(store->node[ARR_REDUX], "Redux");
}

static void qmc_gather(const struct plm *tpl,
		       struct arr *av, struct arr *ap, struct arr *am)
{
	len_t val, max;

	max = 1ul << tpl->in_nb;
	for (val = 0; val < max; val++) {
		qmc_t *pv = ap->data;
		qmc_t *pm = am->data;
		len_t n = ap->len;

		do {
			if ((val & ~*pm++) == *pv++) {
				arr_append(&av, val);
				break;
			}
		} while (--n);
	}
}

/* Check the value coverage */
static void qmc16_verify(const struct plm *tpl, struct grp *store)
{
	uint64_t start;
	len_t vlen;

	if (!store->node[ARR_VALUE] ||
	    !store->node[ARR_PRIME_V] ||
	    !store->node[ARR_PRIME_M] ||
	    !store->node[ARR_VALUE]->len ||
	    !store->node[ARR_PRIME_V]->len ||
	    !store->node[ARR_PRIME_M]->len)
		return;
	start = mc_query_ms();
	vlen = store->node[ARR_VALUE]->len;
	arr_free(store->node[ARR_CHECK]);
	store->node[ARR_CHECK] = arr_alloc(NULL, vlen);

	qmc_gather(tpl,
		   store->node[ARR_CHECK],
		   store->node[ARR_PRIME_V],
		   store->node[ARR_PRIME_M]);
	if (store->node[ARR_CHECK]->len != vlen ||
	    memcmp(store->node[ARR_CHECK]->data,
		   store->node[ARR_VALUE]->data,
		   sizeof(qmc_t) * vlen)) {
		start = mc_query_ms() - start;
		printf("Verification failed");
		/* arr_print(store->node[ARR_VALUE], "Value"); */
		/* arr_print(store->node[ARR_CHECK], "Check"); */
	} else {
		start = mc_query_ms() - start;
		printf("Verification succeeded");
	}
	printf(" in: %" PRIu64 ".%03" PRIu64 "\n", start / 1000, start % 1000);
}

/* Finding the prime minterms */
static void qmc16_prime(const struct plm *tpl,
			struct grp **group,
			struct grp *store)
{
	struct grp *prime = grp_alloc(QMC_NMAX);
	uint32_t i;

	printf("Finding prime implicants, wait...\n");
	for (i = 0; i < QMC_NBIT; i++)
		qmc16_order(group, prime, i);

	grp_prime(prime, store);
	grp_free(prime);
	arr_print2(store->node[ARR_PRIME_V],
		   store->node[ARR_PRIME_M],
		   "Found prime implicants");
	if (cl_qmc > 1)
		qmc16_essen(store);
	qmc16_verify(tpl, store);
}

static int qmc16_opt(enum opt_type opt)
{
	switch (opt) {
	case PLM_OPT_32:
	case PLM_OPT_32_TREE:
	case PLM_OPT_64:
	case PLM_OPT_64_TREE:
		qmc_check = qmc_check_u32;
		qmc_scan  = qmc_scan_u32;
		qmc_term  = qmc_term_u32;
		return 0;
	case PLM_OPT_128:
	case PLM_OPT_128_TREE:
		if (sizeof(qmc_t) == sizeof(uint16_t) &&
			(mc_query_simd() & SIMD_SSE2)) {
			qmc_check = qmc_check_128;
			qmc_scan  = qmc_scan_128;
			qmc_term  = qmc_term_128;
			return 0;
		}
		printf("mcode: AVX2 is required\n");
		return -1;
	case PLM_OPT_256:
	case PLM_OPT_256_TREE:
		if (sizeof(qmc_t) == sizeof(uint16_t) &&
		    (mc_query_simd() & SIMD_AVX2)) {
			qmc_check = qmc_check_256;
			qmc_scan  = qmc_scan_256;
			qmc_term  = qmc_term_256;
			return 0;
		}
		printf("mcode: AVX2 is required\n");
		return -1;
	default:
		printf("mcode: unrecognized opt type %d\n", opt);
		assert(0);
		return -1;
	}
}

static void mc_test_qmc16_vm2(const struct plm *tpl)
{
	struct grp *store;
	struct grp *group[QMC_NBIT + 1];
	uint32_t amx, adr, ab, ae;
	uint32_t val, max, i, n, op, om;
	uint64_t start;

	if (qmc16_opt(tpl->opt))
		return;
	max = 1ul << tpl->in_nb;	/* input value limit */
	amx = 1ul << tpl->na_nb;	/* microaddress limit */
	ab = cl_ab < 0 ? 0 : MIN(cl_ab, (int)amx);
	ae = cl_ae < 0 ? amx : MIN(cl_ae, (int)amx);
	op = cl_op < 0 ? 0 : (uint32_t)cl_op;
	om = cl_om < 0 ? 0 : (uint32_t)cl_om;

	start = mc_query_ms();
	for (adr = ab; adr < ae; adr++) {
		store = grp_alloc(ARR_MAX);
		for (i = 0; i <= QMC_NBIT; i++)
			group[i] = grp_alloc(QMC_NMAX);
		n = 0;
		/* fill the groups depending on the number of bits */
		if (om) {
			for (val = 0; val < max; val++) {
				uint64_t sop;

				if ((val & om) != op)
					continue;
				sop = plm_get(tpl, val) >> PLM_S_MAX;
				if (sop == adr) {
					i = _mm_popcnt_u32(val);
					assert(i <= QMC_NBIT);
					grp_append(group[i], val, 0);
					grp_append(store, val, ARR_VALUE);
					n++;
				}
			}
		} else {
			for (val = 0; val < max; val++) {
				uint64_t sop = plm_get(tpl, val) >> PLM_S_MAX;

				if (sop == adr) {
					i = _mm_popcnt_u32(val);
					assert(i <= QMC_NBIT);
					grp_append(group[i], val, 0);
					grp_append(store, val, ARR_VALUE);
					n++;
				}
			}
		}
		if (n) {
			printf("Adress %02X: %u\n", adr, n);
			if (cl_qmc <= 0) {
				for (i = 0; i <= QMC_NBIT; i++)
					if (group[i]->node[0] &&
					    group[i]->node[0]->len)
						printf("  [%02u] %u\n", i,
						       group[i]->node[0]->len);
			} else {
				/* execute the next stages of algorithm */
				qmc16_prime(tpl, group, store);
			}
		}
		for (i = 0; i <= QMC_NBIT; i++)
			grp_free(group[i]);
		grp_free(store);
	}
	start = mc_query_ms() - start;
	printf("Elapsed: %" PRIu64 ".%03" PRIu64 "\n",
		start / 1000, start % 1000);
}

static void mc_test_qmc16_vm3(const struct plm *tpl)
{
	struct grp *store;
	struct grp *group[QMC_NBIT + 1];
	uint32_t val, max, i, n, op, om;
	uint64_t qm, qv;
	uint64_t start;

	if (qmc16_opt(tpl->opt))
		return;
	max = 1ul << tpl->in_nb;	/* input value limit */
	qv = cl_qv < 0 ? 0 : (uint64_t)cl_qv;
	qm = cl_qm < 0 ? (1ull << tpl->out_nb) - 1 : (uint64_t)cl_qm;
	op = cl_op < 0 ? 0x20000 : (uint32_t)cl_op;
	om = cl_om < 0 ? 0x30000 : (uint32_t)cl_om;

	start = mc_query_ms();
	store = grp_alloc(ARR_MAX);
	for (i = 0; i <= QMC_NBIT; i++)
		group[i] = grp_alloc(QMC_NMAX);
	n = 0;
	/* fill the groups depending on the number of bits */
	for (val = 0; val < max; val++) {
		uint64_t sop;

		if ((val & om) != op)
			continue;
		sop = plm_get(tpl, val);
		if ((sop & qm) == qv) {
			uint32_t v = val & (QMC_NMAX - 1);

			i = _mm_popcnt_u32(v);
			assert(i <= QMC_NBIT);
			grp_append(group[i], v, 0);
			grp_append(store, v, ARR_VALUE);
			n++;
		}
	}
	if (n) {
		printf("Value %08llo:%08llo: %u\n", qv, qm, n);
		if (cl_qmc <= 0) {
			for (i = 0; i <= QMC_NBIT; i++)
				if (group[i]->node[0] &&
					group[i]->node[0]->len)
					printf("  [%02u] %u\n", i,
						group[i]->node[0]->len);
		} else {
			/* execute the next stages of algorithm */
			qmc16_prime(tpl, group, store);
		}
	}
	for (i = 0; i <= QMC_NBIT; i++)
		grp_free(group[i]);
	grp_free(store);
	start = mc_query_ms() - start;
	printf("Elapsed: %" PRIu64 ".%03" PRIu64 "\n",
		start / 1000, start % 1000);
}

void mc_test_qmc16(const struct plm *tpl)
{
	switch (tpl->type) {
	case PLM_TYPE_VM2_DEC:
		mc_test_qmc16_vm2(tpl);
		break;
	case PLM_TYPE_VM3_DEC:
		mc_test_qmc16_vm3(tpl);
		break;
	default:
		printf("mcode: Quine-McCluskey not supported\n");
		assert(0);
		break;
	}
}

