#include <xbyak/xbyak_util.h>
#include <cybozu/test.hpp>

#ifdef XBYAK32
	#error "this sample is for only 64-bit mode"
#endif

using namespace Xbyak::util;

struct Code : public Xbyak::CodeGenerator {
	void gen1()
	{
		StackFrame sf(this, 1);
		mov(rax, sf.p[0]);
	}
	void gen2()
	{
		StackFrame sf(this, 2);
		lea(rax, ptr [sf.p[0] + sf.p[1]]);
	}
	void gen3()
	{
		StackFrame sf(this, 3);
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
	}
	void gen4()
	{
		StackFrame sf(this, 4);
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
		add(rax, sf.p[3]);
	}

	void gen5()
	{
		StackFrame sf(this, 4, UseRCX);
		xor_(rcx, rcx);
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
		add(rax, sf.p[3]);
	}

	void gen6()
	{
		StackFrame sf(this, 4, UseRCX | UseRDX);
		xor_(rcx, rcx);
		xor_(rdx, rdx);
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
		add(rax, sf.p[3]);
	}

	void gen7()
	{
		StackFrame sf(this, 3, UseRCX | UseRDX);
		xor_(rcx, rcx);
		xor_(rdx, rdx);
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
	}

	void gen8()
	{
		StackFrame sf(this, 3, 3 | UseRCX | UseRDX);
		xor_(rcx, rcx);
		xor_(rdx, rdx);
		mov(sf.t[0], 1);
		mov(sf.t[1], 2);
		mov(sf.t[2], 3);
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
	}

	void gen9()
	{
		StackFrame sf(this, 3, 3 | UseRCX | UseRDX, 32);
		xor_(rcx, rcx);
		xor_(rdx, rdx);
		mov(sf.t[0], 1);
		mov(sf.t[1], 2);
		mov(sf.t[2], 3);
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
		mov(ptr [rsp + 8 * 0], rax);
		mov(ptr [rsp + 8 * 1], rax);
		mov(ptr [rsp + 8 * 2], rax);
		mov(ptr [rsp + 8 * 3], rax);
	}

	void gen10()
	{
		StackFrame sf(this, 4, 8 | UseRCX | UseRDX, 32);
		xor_(rcx, rcx);
		xor_(rdx, rdx);
		for (int i = 0; i < 8; i++) {
			mov(sf.t[i], i);
		}
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
		add(rax, sf.p[3]);
		mov(ptr [rsp + 8 * 0], rax);
		mov(ptr [rsp + 8 * 1], rax);
		mov(ptr [rsp + 8 * 2], rax);
		mov(ptr [rsp + 8 * 3], rax);
	}

	void gen11()
	{
		StackFrame sf(this, 0, UseRCX);
		xor_(rcx, rcx);
		mov(rax, 3);
	}

	void gen12()
	{
		StackFrame sf(this, 4, UseRDX);
		xor_(rdx, rdx);
		mov(rax, sf.p[0]);
		add(rax, sf.p[1]);
		add(rax, sf.p[2]);
		add(rax, sf.p[3]);
	}

	/*
		int64_t f(const int64_t a[13]) { return sum-of-a[]; }
	*/
	void gen13()
	{
		StackFrame sf(this, 1, 13);
		for (int i = 0; i < 13; i++) {
			mov(sf.t[i], ptr[sf.p[0] + i * 8]);
		}
		mov(rax, sf.t[0]);
		for (int i = 1; i < 13; i++) {
			add(rax, sf.t[i]);
		}
	}
	/*
		same as gen13
	*/
	void gen14()
	{
		StackFrame sf(this, 1, 11 | UseRCX | UseRDX);
		Pack t = sf.t;
		t.append(rcx);
		t.append(rdx);
		for (int i = 0; i < 13; i++) {
			mov(t[i], ptr[sf.p[0] + i * 8]);
		}
		mov(rax, t[0]);
		for (int i = 1; i < 13; i++) {
			add(rax, t[i]);
		}
	}
	/*
		return (1 << 15) - 1;
	*/
	void gen15()
	{
		StackFrame sf(this, 0, 14, 8);
		Pack t = sf.t;
		t.append(rax);
		for (int i = 0; i < 15; i++) {
			mov(t[i], 1 << i);
		}
		mov(qword[rsp], 0);
		for (int i = 0; i < 15; i++) {
			add(ptr[rsp], t[i]);
		}
		mov(rax, ptr[rsp]);
	}
};

struct Code2 : Xbyak::CodeGenerator {
	Code2()
		: Xbyak::CodeGenerator(4096 * 32)
	{
	}
	void gen(int pNum, int tNum, int stackSizeByte)
	{
		StackFrame sf(this, pNum, tNum, stackSizeByte);
		if (tNum & UseRCX) xor_(rcx, rcx);
		if (tNum & UseRDX) xor_(rdx, rdx);
		for (int i = 0, n = tNum & ~(UseRCX | UseRDX); i < n; i++) {
			mov(sf.t[i], 5);
		}
		for (int i = 0; i < stackSizeByte; i++) {
			mov(byte [rsp + i], 0);
		}
		mov(rax, 1);
		for (int i = 0; i < pNum; i++) {
			add(rax, sf.p[i]);
		}
	}
	void gen2(int pNum, int tNum, int stackSizeByte)
	{
		StackFrame sf(this, pNum, tNum, stackSizeByte);
		mov(rax, rsp);
	}
};


void verify(const uint8_t *_f, int pNum)
{
	uint8_t *f = const_cast<uint8_t*>(_f);
	switch (pNum) {
	case 0:
		CYBOZU_TEST_EQUAL(1, reinterpret_cast<int (*)()>(f)());
		return;
	case 1:
		CYBOZU_TEST_EQUAL(11, reinterpret_cast<int (*)(int)>(f)(10));
		return;
	case 2:
		CYBOZU_TEST_EQUAL(111, reinterpret_cast<int (*)(int, int)>(f)(10, 100));
		return;
	case 3:
		CYBOZU_TEST_EQUAL(1111, reinterpret_cast<int (*)(int, int, int)>(f)(10, 100, 1000));
		return;
	case 4:
		CYBOZU_TEST_EQUAL(11111, reinterpret_cast<int (*)(int, int, int, int)>(f)(10, 100, 1000, 10000));
		return;
	default:
		printf("ERR pNum=%d\n", pNum);
		exit(1);
	}
}

CYBOZU_TEST_AUTO(param)
{
	Code2 code;
	for (int stackSize = 0; stackSize < 32; stackSize += 7) {
		for (int pNum = 0; pNum < 4; pNum++) {
			for (int mode = 0; mode < 4; mode++) {
				int maxNum = 0;
				int opt = 0;
				if (mode == 0) {
					maxNum = 10;
				} else if (mode == 1) {
					maxNum = 9;
					opt = UseRCX;
				} else if (mode == 2) {
					maxNum = 9;
					opt = UseRDX;
				} else {
					maxNum = 8;
					opt = UseRCX | UseRDX;
				}
				for (int tNum = 0; tNum < maxNum; tNum++) {
//					printf("pNum=%d, tNum=%d, stackSize=%d\n", pNum, tNum | opt, stackSize);
					const uint8_t *f = code.getCurr();
					code.gen(pNum, tNum | opt, stackSize);
					verify(f, pNum);
					/*
						check rsp is 16-byte aligned if stackSize > 0
					*/
					if (stackSize > 0) {
						Code2 c2;
						c2.gen2(pNum, tNum | opt, stackSize);
						uint64_t addr = c2.getCode<uint64_t (*)()>()();
						CYBOZU_TEST_EQUAL(addr % 16, 0);
					}
				}
			}
		}
	}
}

CYBOZU_TEST_AUTO(args)
{
	Code code;
	int (*f1)(int) = code.getCurr<int (*)(int)>();
	code.gen1();
	CYBOZU_TEST_EQUAL(5, f1(5));

	int (*f2)(int, int) = code.getCurr<int (*)(int, int)>();
	code.gen2();
	CYBOZU_TEST_EQUAL(9, f2(3, 6));

	int (*f3)(int, int, int) = code.getCurr<int (*)(int, int, int)>();
	code.gen3();
	CYBOZU_TEST_EQUAL(14, f3(1, 4, 9));

	int (*f4)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
	code.gen4();
	CYBOZU_TEST_EQUAL(30, f4(1, 4, 9, 16));

	int (*f5)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
	code.gen5();
	CYBOZU_TEST_EQUAL(23, f5(2, 5, 7, 9));

	int (*f6)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
	code.gen6();
	CYBOZU_TEST_EQUAL(18, f6(3, 4, 5, 6));

	int (*f7)(int, int, int) = code.getCurr<int (*)(int, int, int)>();
	code.gen7();
	CYBOZU_TEST_EQUAL(12, f7(3, 4, 5));

	int (*f8)(int, int, int) = code.getCurr<int (*)(int, int, int)>();
	code.gen8();
	CYBOZU_TEST_EQUAL(23, f8(5, 8, 10));

	int (*f9)(int, int, int) = code.getCurr<int (*)(int, int, int)>();
	code.gen9();
	CYBOZU_TEST_EQUAL(60, f9(10, 20, 30));

	int (*f10)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
	code.gen10();
	CYBOZU_TEST_EQUAL(100, f10(10, 20, 30, 40));

	int (*f11)() = code.getCurr<int (*)()>();
	code.gen11();
	CYBOZU_TEST_EQUAL(3, f11());

	int (*f12)(int, int, int, int) = code.getCurr<int (*)(int, int, int, int)>();
	code.gen12();
	CYBOZU_TEST_EQUAL(24, f12(3, 5, 7, 9));

	{
		int64_t tbl[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
		int64_t (*f13)(const int64_t*) = code.getCurr<int64_t (*)(const int64_t*)>();
		code.gen13();
		CYBOZU_TEST_EQUAL(91, f13(tbl));

		int64_t (*f14)(const int64_t*) = code.getCurr<int64_t (*)(const int64_t*)>();
		code.gen14();
		CYBOZU_TEST_EQUAL(91, f14(tbl));
	}
	int (*f15)() = code.getCurr<int (*)()>();
	code.gen15();
	CYBOZU_TEST_EQUAL((1 << 15) - 1, f15());
}

void put(const Xbyak::util::Pack& p)
{
	for (size_t i = 0, n = p.size(); i < n; i++) {
		printf("%s ", p[i].toString());
	}
	printf("\n");
}

void verifyPack(const Xbyak::util::Pack& p, const int *tbl, size_t tblNum)
{
	for (size_t i = 0; i < tblNum; i++) {
		CYBOZU_TEST_EQUAL(p[i].getIdx(), tbl[i]);
	}
}

CYBOZU_TEST_AUTO(pack)
{
	const int N = 10;
	Xbyak::Reg64 regTbl[N];
	for (int i = 0; i < N; i++) {
		regTbl[i] = Xbyak::Reg64(i);
	}
	Xbyak::util::Pack p(regTbl, N);
	const struct {
		int pos;
		int num;
		int tbl[10];
	} tbl[] = {
		{ 0, 10, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } },
		{ 1, 9, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } },
		{ 2, 8, { 2, 3, 4, 5, 6, 7, 8, 9 } },
		{ 3, 7, { 3, 4, 5, 6, 7, 8, 9 } },
		{ 4, 6, { 4, 5, 6, 7, 8, 9 } },
		{ 5, 5, { 5, 6, 7, 8, 9 } },
		{ 6, 4, { 6, 7, 8, 9 } },
		{ 7, 3, { 7, 8, 9 } },
		{ 8, 2, { 8, 9 } },
		{ 9, 1, { 9 } },
		{ 3, 5, { 3, 4, 5, 6, 7 } },
	};
	for (size_t i = 0; i < sizeof(tbl) / sizeof(*tbl); i++) {
		const int pos = tbl[i].pos;
		const int num = tbl[i].num;
		verifyPack(p.sub(pos, num), tbl[i].tbl, num);
		if (pos + num == N) {
			verifyPack(p.sub(pos), tbl[i].tbl, num);
		}
	}
}

struct CloseCode : Xbyak::CodeGenerator {
	CloseCode(size_t mode)
	{
		switch (mode) {
		case 0:
			{
				StackFrame sf(this, 0);
				// close() is automatically called.
			}
			break;

		case 1:
			{
				StackFrame sf(this, 0, 0, 0, false);
				sf.close(); // Explicitly call close().
				setProtectModeRE(); // Ensure that no writes occur in destructor by setting read-exec
			}
			break;

		case 2:
			{
				StackFrame sf(this, 0, 0, 0, false);
				sf.close(); // Explicitly call close().
				sf.close(); // Explicitly call close().
				setProtectModeRE(); // Ensure that no writes occur in destructor by setting read-exec
			}
			break;
		default:
			CYBOZU_TEST_ASSERT(false);
		}
	}
};

CYBOZU_TEST_AUTO(close)
{
	const size_t expectedTbl[] = {
		1, 1, 2,
	};
	for (size_t i = 0; i < sizeof(expectedTbl)/sizeof(expectedTbl[0]); i++) {
		CloseCode c(i);
		CYBOZU_TEST_EQUAL(c.getSize(), expectedTbl[i]);
	}
}
