/**
 * vec - a tiny SIMD vector library in C99
 * 
 * Copyright (c) 2024-2025 Paper
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
**/

/* Use this file to generate include/vec/impl/generic.h !!
 *
 * `gcc -o gengeneric gengeneric.c` */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))

/* ------------------------------------------------------------------------ */

enum op {
	/* return vector, take in a integer */
	OP_SPLAT = 0,

	/* return vector, take in an array */
	OP_LOAD_ALIGNED,
	OP_LOAD,

	/* void, take in vector and array */
	OP_STORE_ALIGNED,
	OP_STORE,

	/* return vector, takes in two vectors */
	OP_ADD,
	OP_SUB,
	OP_MUL,
	OP_AND,
	OP_OR,
	OP_XOR,
	OP_CMPLT,
	OP_CMPEQ,
	OP_CMPGT,
	OP_CMPLE,
	OP_CMPGE,
	OP_MIN,
	OP_MAX,
	OP_AVG,

	/* return vector, takes in a vector and an explicitly unsigned vector */
	OP_LSHIFT,
	OP_RSHIFT,
	OP_LRSHIFT,

	/* return vector, takes in a vector */
	OP_NOT,

	OP_FINAL_,

	/* operations that have some sort of "caveat" should go here, until
	 * they are fixed or removed */

	OP_DIV, /* this one causes a floating point exception on my machine.
	         * possibly we could change the behavior of divide-by-zero
	         * with some gcc pragma ?  --paper */
	OP_MOD, /* ditto with the above */
};

/* convert op -> string */
static struct {
	const char *u;
	const char *l;
} op_names[] = {
	[OP_SPLAT] = {"SPLAT", "splat"},
	[OP_LOAD_ALIGNED] = {"LOAD_ALIGNED", "load_aligned"},
	[OP_LOAD] = {"LOAD", "load"},
	[OP_STORE_ALIGNED] = {"STORE_ALIGNED", "store_aligned"},
	[OP_STORE] = {"STORE", "store"},
	[OP_ADD] = {"ADD", "add"},
	[OP_SUB] = {"SUB", "sub"},
	[OP_MUL] = {"MUL", "mul"},
	/*[OP_DIV] = {"DIV", "div"},*/
	[OP_AVG] = {"AVG", "avg"},
	[OP_AND] = {"AND", "and"},
	[OP_OR] = {"OR", "or"},
	[OP_XOR] = {"XOR", "xor"},
	[OP_NOT] = {"NOT", "not"},
	[OP_CMPLT] = {"CMPLT", "cmplt"},
	[OP_CMPEQ] = {"CMPEQ", "cmpeq"},
	[OP_CMPGT] = {"CMPGT", "cmpgt"},
	[OP_CMPLE] = {"CMPLE", "cmple"},
	[OP_CMPGE] = {"CMPGE", "cmpge"},
	[OP_MIN] = {"MIN", "min"},
	[OP_MAX] = {"MAX", "max"},
	[OP_RSHIFT] = {"RSHIFT", "rshift"},
	[OP_LRSHIFT] = {"LRSHIFT", "lrshift"},
	[OP_LSHIFT] = {"LSHIFT", "lshift"},
};

#define UPSIGN(x) ((x) ? "" : "U")
#define LOSIGN(x) ((x) ? "" : "u")

static void print_gcc_op(enum op op, int is_signed, int bits, int size)
{
	int i;

	printf("#ifndef V%sINT%dx%d_%s_DEFINED\n", UPSIGN(is_signed), bits, size, op_names[op].u);

	printf("VEC_FUNC_IMPL ");

	/* first; the return value */
	switch (op) {
	case OP_SPLAT:
	case OP_LOAD_ALIGNED:
	case OP_LOAD:
	case OP_ADD:
	case OP_SUB:
	case OP_MUL:
	case OP_DIV:
	case OP_AND:
	case OP_OR:
	case OP_XOR:
	case OP_CMPLT:
	case OP_CMPEQ:
	case OP_CMPGT:
	case OP_CMPLE:
	case OP_CMPGE:
	case OP_MIN:
	case OP_MAX:
	case OP_AVG:
	case OP_RSHIFT:
	case OP_LRSHIFT:
	case OP_LSHIFT:
	case OP_NOT:
		printf("v%sint%dx%d", LOSIGN(is_signed), bits, size);
		break;
	case OP_STORE_ALIGNED:
	case OP_STORE:
		printf("void");
		break;
	}

	/* whitespace and function name */
	printf(" v%sint%dx%d_%s(", LOSIGN(is_signed), bits, size, op_names[op].l);

	/* parameters */
	switch (op) {
	case OP_SPLAT:
		printf("vec_%sint%d x", LOSIGN(is_signed), bits);
		break;
	case OP_LOAD_ALIGNED:
	case OP_LOAD:
		printf("const vec_%sint%d x[%d]", LOSIGN(is_signed), bits, size);
		break;
	case OP_STORE_ALIGNED:
	case OP_STORE:
		printf("v%sint%dx%d vec, vec_%sint%d arr[%d]", LOSIGN(is_signed), bits, size, LOSIGN(is_signed), bits, size);
		break;
	case OP_ADD:
	case OP_SUB:
	case OP_MUL:
	case OP_DIV:
	case OP_AND:
	case OP_OR:
	case OP_XOR:
	case OP_CMPLT:
	case OP_CMPEQ:
	case OP_CMPGT:
	case OP_CMPLE:
	case OP_CMPGE:
	case OP_MIN:
	case OP_MAX:
	case OP_AVG:
		printf("v%sint%dx%d vec1, v%sint%dx%d vec2", LOSIGN(is_signed), bits, size, LOSIGN(is_signed), bits, size);
		break;
	case OP_RSHIFT:
	case OP_LRSHIFT:
	case OP_LSHIFT:
		printf("v%sint%dx%d vec1, vuint%dx%d vec2", LOSIGN(is_signed), bits, size, bits, size);
		break;
	case OP_NOT:
		printf("v%sint%dx%d vec", LOSIGN(is_signed), bits, size);
		break;
	}

	puts(")\n{");

	switch (op) {
	case OP_SPLAT:
		printf("\tv%sint%dx%d vec;\n", LOSIGN(is_signed), bits, size);
		printf("\tvec.gcc = (__typeof__(vec.gcc)){");
		for (i = 0; i < size; i++)
			printf("x,");
		printf("};\n");
		printf("\treturn vec;\n");
		break;
	case OP_LOAD_ALIGNED:
		printf("\tv%sint%dx%d vec;\n", LOSIGN(is_signed), bits, size);
		puts("\tvec.gcc = *(__typeof__(vec.gcc) *)x;");
		printf("\treturn vec;\n");
		break;
	case OP_LOAD:
		printf("\tv%sint%dx%d vec;\n", LOSIGN(is_signed), bits, size);
		puts("\tmemcpy(&vec, x, sizeof(vec));");
		printf("\treturn vec;\n");
		break;
	case OP_STORE_ALIGNED:
		puts("\t*(__typeof__(vec.gcc) *)arr = vec.gcc;");
		break;
	case OP_STORE:
		puts("\tmemcpy(arr, &vec, sizeof(vec));");
		break;
	case OP_ADD:
	case OP_SUB:
	case OP_MUL:
	case OP_DIV:
	case OP_AND:
	case OP_OR:
	case OP_XOR:
	case OP_CMPLT:
	case OP_CMPEQ:
	case OP_CMPGT:
	case OP_CMPLE:
	case OP_CMPGE: {
		const char *op_builtins[OP_CMPGE - OP_ADD + 1] = {"+", "-", "*", /*"/", */"&", "|", "^", "<", "==", ">", "<=", ">="};

		printf("\tvec1.gcc = (vec1.gcc %s vec2.gcc);\n", op_builtins[op - OP_ADD]);
		printf("\treturn vec1;\n");
		break;
	}

	case OP_LSHIFT:
	case OP_RSHIFT: {
		const char *op_builtins[OP_RSHIFT - OP_LSHIFT + 1] = {"<<", ">>"};

		printf("\tvec1.gcc = (vec1.gcc %s vec2.gcc);\n", op_builtins[op - OP_LSHIFT]);
		printf("\treturn vec1;\n");
		break;
	}

	case OP_LRSHIFT: {
		/* sigh */
		printf("\tvec1.gcc = (__typeof__(vec1.gcc))((vec_uint%d __attribute__((__vector_size__(%d))))vec1.gcc >> vec2.gcc);\n", bits, bits * size / 8);
		printf("\treturn vec1;\n");
		break;
	}
	case OP_MIN:
	case OP_MAX: {
		const char *op_builtins[OP_MAX - OP_MIN + 1] = {"<", ">"};

		printf("\tv%sint%dx%d mask;\n", LOSIGN(is_signed), bits, size);
		printf("\tmask.gcc = (vec1.gcc %s vec2.gcc);\n", op_builtins[op - OP_MIN]);
		printf("\tvec1.gcc = (vec1.gcc & mask.gcc) | (vec2.gcc & ~mask.gcc);\n");
		printf("\treturn vec1;\n");
		break;
	}
	case OP_AVG:
		if (is_signed) {
			printf("\tvint%dx%d ones = vint%dx%d_splat(1);\n", bits, size, bits, size);
			printf("\t__typeof__(vec1.gcc) x_d_rem = (vec1.gcc % 2);\n");
			printf("\t__typeof__(vec1.gcc) y_d_rem = (vec2.gcc % 2);\n");
			printf("\t__typeof__(vec1.gcc) rem_d_quot = ((x_d_rem + y_d_rem) / 2);\n");
			printf("\t__typeof__(vec1.gcc) rem_d_rem = ((x_d_rem + y_d_rem) % 2);\n");
			puts("");
			printf("\tvec1.gcc = ((vec1.gcc / 2) + (vec2.gcc / 2)) + (rem_d_quot) + ((rem_d_rem == 1) & ones.gcc);\n");
		} else {
			printf("\tvec1.gcc = (vec1.gcc >> 1) + (vec2.gcc >> 1) + ((vec1.gcc | vec2.gcc) & 1);\n");
		}

		printf("\treturn vec1;\n");
		break;
	case OP_NOT:
		printf("\tvec.gcc = ~vec.gcc;\n");
		printf("\treturn vec;\n");
		break;
	default:
		printf("#error implement this operation");
		break;
	}

	/* end function definition */
	puts("}");

	printf("# define V%sINT%dx%d_%s_DEFINED\n", UPSIGN(is_signed), bits, size, op_names[op].u);
	puts("#endif");
}

static inline void print_ops(int is_signed, int bits, int size)
{
	int i;

	printf("\n\n/* v%sint%dx%d */\n\n", (is_signed ? "u" : ""), bits, size);

	for (i = 0; i < OP_FINAL_; i++)
		print_gcc_op(i, is_signed, bits, size);
}

static const char *header =
	"/**\n"
	" * vec - a tiny SIMD vector library in C99\n"
	" * \n"
	" * Copyright (c) 2024-2025 Paper\n"
	" * \n"
	" * Permission is hereby granted, free of charge, to any person obtaining a copy\n"
	" * of this software and associated documentation files (the \"Software\"), to deal\n"
	" * in the Software without restriction, including without limitation the rights\n"
	" * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
	" * copies of the Software, and to permit persons to whom the Software is\n"
	" * furnished to do so, subject to the following conditions:\n"
	" * \n"
	" * The above copyright notice and this permission notice shall be included in all\n"
	" * copies or substantial portions of the Software.\n"
	" * \n"
	" * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
	" * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
	" * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
	" * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
	" * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
	" * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
	" * SOFTWARE.\n"
	"**/\n"
	"\n"
	"/* This file is automatically generated! Do not edit it directly!\n"
	" * Edit the code that generates it in utils/gengcc.c  --paper */\n"
	"\n"
	"#ifndef VEC_IMPL_GCC_H_\n"
	"#define VEC_IMPL_GCC_H_\n"
	"\n";

static const char *footer = 
	"#endif /* VEC_IMPL_GCC_H_ */\n";

int main(void)
{
	static struct {
		int bits, size;
	} defs[] = {
		/* -- 8-bit */
		{8, 2},
		{8, 4},
		{8, 8},
		{8, 16},
		{8, 32},
		{8, 64},

		/* -- 16-bit */
		{16, 2},
		{16, 4},
		{16, 8},
		{16, 16},
		{16, 32},

		/* -- 32-bit */
		{32, 2},
		{32, 4},
		{32, 8},
		{32, 16},

		/* -- 64-bit */
		{64, 2},
		{64, 4},
		{64, 8},
	};
	int i;

	puts(header);

	for (i = 0; i < ARRAY_SIZE(defs); i++) {
		print_ops(1, defs[i].bits, defs[i].size);
		print_ops(0, defs[i].bits, defs[i].size);
	}

	puts(footer);
}
