#pragma once

#include "util/BitField.h"

template <typename T, u32 I, u32 N>
using ppu_bf_t = bf_t<T, sizeof(T) * 8 - N - I, N>;

union ppu_opcode_t
{
	u32 opcode;

	ppu_bf_t<u32, 0, 6> main;                               // 0..5
	cf_t<ppu_bf_t<u32, 30, 1>, ppu_bf_t<u32, 16, 5>> sh64;  // 30 + 16..20
	cf_t<ppu_bf_t<u32, 26, 1>, ppu_bf_t<u32, 21, 5>> mbe64; // 26 + 21..25
	ppu_bf_t<u32, 11, 5> vuimm;                             // 11..15
	ppu_bf_t<u32, 6, 5> vs;                                 // 6..10
	ppu_bf_t<u32, 22, 4> vsh;                               // 22..25
	ppu_bf_t<u32, 21, 1> oe;                                // 21
	ppu_bf_t<u32, 11, 10> spr;                              // 11..20
	ppu_bf_t<u32, 21, 5> vc;                                // 21..25
	ppu_bf_t<u32, 16, 5> vb;                                // 16..20
	ppu_bf_t<u32, 11, 5> va;                                // 11..15
	ppu_bf_t<u32, 6, 5> vd;                                 // 6..10
	ppu_bf_t<u32, 31, 1> lk;                                // 31
	ppu_bf_t<u32, 30, 1> aa;                                // 30
	ppu_bf_t<u32, 16, 5> rb;                                // 16..20
	ppu_bf_t<u32, 11, 5> ra;                                // 11..15
	ppu_bf_t<u32, 6, 5> rd;                                 // 6..10
	ppu_bf_t<u32, 16, 16> uimm16;                           // 16..31
	ppu_bf_t<u32, 11, 1> l11;                               // 11
	ppu_bf_t<u32, 6, 5> rs;                                 // 6..10
	ppu_bf_t<s32, 16, 16> simm16;                           // 16..31, signed
	ppu_bf_t<s32, 16, 14> ds;                               // 16..29, signed
	ppu_bf_t<s32, 11, 5> vsimm;                             // 11..15, signed
	ppu_bf_t<s32, 6, 26> ll;                                // 6..31, signed
	ppu_bf_t<s32, 6, 24> li;                                // 6..29, signed
	ppu_bf_t<u32, 20, 7> lev;                               // 20..26
	ppu_bf_t<u32, 16, 4> i;                                 // 16..19
	ppu_bf_t<u32, 11, 3> crfs;                              // 11..13
	ppu_bf_t<u32, 10, 1> l10;                               // 10
	ppu_bf_t<u32, 6, 3> crfd;                               // 6..8
	ppu_bf_t<u32, 16, 5> crbb;                              // 16..20
	ppu_bf_t<u32, 11, 5> crba;                              // 11..15
	ppu_bf_t<u32, 6, 5> crbd;                               // 6..10
	ppu_bf_t<u32, 31, 1> rc;                                // 31
	ppu_bf_t<u32, 26, 5> me32;                              // 26..30
	ppu_bf_t<u32, 21, 5> mb32;                              // 21..25
	ppu_bf_t<u32, 16, 5> sh32;                              // 16..20
	ppu_bf_t<u32, 11, 5> bi;                                // 11..15
	ppu_bf_t<u32, 6, 5> bo;                                 // 6..10
	ppu_bf_t<u32, 19, 2> bh;                                // 19..20
	ppu_bf_t<u32, 21, 5> frc;                               // 21..25
	ppu_bf_t<u32, 16, 5> frb;                               // 16..20
	ppu_bf_t<u32, 11, 5> fra;                               // 11..15
	ppu_bf_t<u32, 6, 5> frd;                                // 6..10
	ppu_bf_t<u32, 12, 8> crm;                               // 12..19
	ppu_bf_t<u32, 6, 5> frs;                                // 6..10
	ppu_bf_t<u32, 7, 8> flm;                                // 7..14
	ppu_bf_t<u32, 6, 1> l6;                                 // 6
	ppu_bf_t<u32, 15, 1> l15;                               // 15
	cf_t<ppu_bf_t<s32, 16, 14>, ff_t<u32, 0, 2>> bt14;
	cf_t<ppu_bf_t<s32, 6, 24>, ff_t<u32, 0, 2>> bt24;
};

constexpr u64 ppu_rotate_mask(u32 mb, u32 me)
{
	const u64 mask = ~0ull << (~(me - mb) & 63);
	return (mask >> (mb & 63)) | (mask << ((64 - mb) & 63));
}

constexpr u32 ppu_decode(u32 inst)
{
	return ((inst >> 26) | (inst << 6)) & 0x1ffff; // Rotate + mask
}

std::array<u32, 2> op_branch_targets(u32 pc, ppu_opcode_t op);

// PPU decoder object. D provides functions. T is function pointer type returned.
template <typename D, typename T = decltype(&D::UNK)>
class ppu_decoder
{
	// Fast lookup table
	std::array<T, 0x20000> m_table{};

	struct instruction_info
	{
		u32 value;
		T ptr0;
		T ptr_rc;
		u32 magn; // Non-zero for "columns" (effectively, number of most significant bits "eaten")

		constexpr instruction_info(u32 v, T p, T p_rc, u32 m = 0)
			: value(v), ptr0(p), ptr_rc(p_rc), magn(m)
		{
		}

		constexpr instruction_info(u32 v, const T* p, const T* p_rc, u32 m = 0)
			: value(v), ptr0(*p), ptr_rc(*p_rc), magn(m)
		{
		}
	};

	// Fill lookup table
	void fill_table(u32 main_op, u32 count, u32 sh, std::initializer_list<instruction_info> entries) noexcept
	{
		if (sh < 11)
		{
			for (const auto& v : entries)
			{
				for (u32 i = 0; i < 1u << (v.magn + (11 - sh - count)); i++)
				{
					for (u32 j = 0; j < 1u << sh; j++)
					{
						const u32 k = (((i << (count - v.magn)) | v.value) << sh) | j;
						::at32(m_table, (k << 6) | main_op) = k & 1 ? v.ptr_rc : v.ptr0;
					}
				}
			}
		}
		else
		{
			// Main table (special case)
			for (const auto& v : entries)
			{
				for (u32 i = 0; i < 1u << 11; i++)
				{
					::at32(m_table, i << 6 | v.value) = i & 1 ? v.ptr_rc : v.ptr0;
				}
			}
		}
	}

	// Helper
	static const D& _first(const D& arg)
	{
		return arg;
	}

public:
	template <typename... Args>
	ppu_decoder(const Args&... args) noexcept
	{
		// If an object is passed to the constructor, assign values from that object
#define GET_(name) [&] {               \
	if constexpr (sizeof...(Args) > 0) \
		return _first(args...).name;   \
	else                               \
		return &D::name;               \
}()
#define GET(name) GET_(name), GET_(name)
#define GETRC(name) GET_(name), GET_(name##_)

		static_assert(sizeof...(Args) <= 1);

		for (auto& x : m_table)
		{
			x = GET(UNK);
		}

		// Main opcodes (field 0..5)
		fill_table(0x00, 6, -1,
			{
				{0x02, GET(TDI)},
				{0x03, GET(TWI)},
				{0x07, GET(MULLI)},
				{0x08, GET(SUBFIC)},
				{0x0a, GET(CMPLI)},
				{0x0b, GET(CMPI)},
				{0x0c, GET(ADDIC)},
				{0x0d, GET(ADDIC)},
				{0x0e, GET(ADDI)},
				{0x0f, GET(ADDIS)},
				{0x10, GET(BC)},
				{0x11, GET(SC)},
				{0x12, GET(B)},
				{0x14, GETRC(RLWIMI)},
				{0x15, GETRC(RLWINM)},
				{0x17, GETRC(RLWNM)},
				{0x18, GET(ORI)},
				{0x19, GET(ORIS)},
				{0x1a, GET(XORI)},
				{0x1b, GET(XORIS)},
				{0x1c, GET(ANDI)},
				{0x1d, GET(ANDIS)},
				{0x20, GET(LWZ)},
				{0x21, GET(LWZU)},
				{0x22, GET(LBZ)},
				{0x23, GET(LBZU)},
				{0x24, GET(STW)},
				{0x25, GET(STWU)},
				{0x26, GET(STB)},
				{0x27, GET(STBU)},
				{0x28, GET(LHZ)},
				{0x29, GET(LHZU)},
				{0x2a, GET(LHA)},
				{0x2b, GET(LHAU)},
				{0x2c, GET(STH)},
				{0x2d, GET(STHU)},
				{0x2e, GET(LMW)},
				{0x2f, GET(STMW)},
				{0x30, GET(LFS)},
				{0x31, GET(LFSU)},
				{0x32, GET(LFD)},
				{0x33, GET(LFDU)},
				{0x34, GET(STFS)},
				{0x35, GET(STFSU)},
				{0x36, GET(STFD)},
				{0x37, GET(STFDU)},
			});

		// Group 0x04 opcodes (field 21..31)
		fill_table(0x04, 11, 0,
			{
				{0x0, GET(VADDUBM)},
				{0x2, GET(VMAXUB)},
				{0x4, GET(VRLB)},
				{0x006, GET(VCMPEQUB)},
				{0x406, GET(VCMPEQUB_)},
				{0x8, GET(VMULOUB)},
				{0xa, GET(VADDFP)},
				{0xc, GET(VMRGHB)},
				{0xe, GET(VPKUHUM)},

				{0x20, GET(VMHADDSHS), 5},
				{0x21, GET(VMHRADDSHS), 5},
				{0x22, GET(VMLADDUHM), 5},
				{0x24, GET(VMSUMUBM), 5},
				{0x25, GET(VMSUMMBM), 5},
				{0x26, GET(VMSUMUHM), 5},
				{0x27, GET(VMSUMUHS), 5},
				{0x28, GET(VMSUMSHM), 5},
				{0x29, GET(VMSUMSHS), 5},
				{0x2a, GET(VSEL), 5},
				{0x2b, GET(VPERM), 5},
				{0x2c, GET(VSLDOI), 5},
				{0x2e, GET(VMADDFP), 5},
				{0x2f, GET(VNMSUBFP), 5},

				{0x40, GET(VADDUHM)},
				{0x42, GET(VMAXUH)},
				{0x44, GET(VRLH)},
				{0x046, GET(VCMPEQUH)},
				{0x446, GET(VCMPEQUH_)},
				{0x48, GET(VMULOUH)},
				{0x4a, GET(VSUBFP)},
				{0x4c, GET(VMRGHH)},
				{0x4e, GET(VPKUWUM)},
				{0x80, GET(VADDUWM)},
				{0x82, GET(VMAXUW)},
				{0x84, GET(VRLW)},
				{0x086, GET(VCMPEQUW)},
				{0x486, GET(VCMPEQUW_)},
				{0x8c, GET(VMRGHW)},
				{0x8e, GET(VPKUHUS)},
				{0x0c6, GET(VCMPEQFP)},
				{0x4c6, GET(VCMPEQFP_)},
				{0xce, GET(VPKUWUS)},

				{0x102, GET(VMAXSB)},
				{0x104, GET(VSLB)},
				{0x108, GET(VMULOSB)},
				{0x10a, GET(VREFP)},
				{0x10c, GET(VMRGLB)},
				{0x10e, GET(VPKSHUS)},
				{0x142, GET(VMAXSH)},
				{0x144, GET(VSLH)},
				{0x148, GET(VMULOSH)},
				{0x14a, GET(VRSQRTEFP)},
				{0x14c, GET(VMRGLH)},
				{0x14e, GET(VPKSWUS)},
				{0x180, GET(VADDCUW)},
				{0x182, GET(VMAXSW)},
				{0x184, GET(VSLW)},
				{0x18a, GET(VEXPTEFP)},
				{0x18c, GET(VMRGLW)},
				{0x18e, GET(VPKSHSS)},
				{0x1c4, GET(VSL)},
				{0x1c6, GET(VCMPGEFP)},
				{0x5c6, GET(VCMPGEFP_)},
				{0x1ca, GET(VLOGEFP)},
				{0x1ce, GET(VPKSWSS)},
				{0x200, GET(VADDUBS)},
				{0x202, GET(VMINUB)},
				{0x204, GET(VSRB)},
				{0x206, GET(VCMPGTUB)},
				{0x606, GET(VCMPGTUB_)},
				{0x208, GET(VMULEUB)},
				{0x20a, GET(VRFIN)},
				{0x20c, GET(VSPLTB)},
				{0x20e, GET(VUPKHSB)},
				{0x240, GET(VADDUHS)},
				{0x242, GET(VMINUH)},
				{0x244, GET(VSRH)},
				{0x246, GET(VCMPGTUH)},
				{0x646, GET(VCMPGTUH_)},
				{0x248, GET(VMULEUH)},
				{0x24a, GET(VRFIZ)},
				{0x24c, GET(VSPLTH)},
				{0x24e, GET(VUPKHSH)},
				{0x280, GET(VADDUWS)},
				{0x282, GET(VMINUW)},
				{0x284, GET(VSRW)},
				{0x286, GET(VCMPGTUW)},
				{0x686, GET(VCMPGTUW_)},
				{0x28a, GET(VRFIP)},
				{0x28c, GET(VSPLTW)},
				{0x28e, GET(VUPKLSB)},
				{0x2c4, GET(VSR)},
				{0x2c6, GET(VCMPGTFP)},
				{0x6c6, GET(VCMPGTFP_)},
				{0x2ca, GET(VRFIM)},
				{0x2ce, GET(VUPKLSH)},
				{0x300, GET(VADDSBS)},
				{0x302, GET(VMINSB)},
				{0x304, GET(VSRAB)},
				{0x306, GET(VCMPGTSB)},
				{0x706, GET(VCMPGTSB_)},
				{0x308, GET(VMULESB)},
				{0x30a, GET(VCFUX)},
				{0x30c, GET(VSPLTISB)},
				{0x30e, GET(VPKPX)},
				{0x340, GET(VADDSHS)},
				{0x342, GET(VMINSH)},
				{0x344, GET(VSRAH)},
				{0x346, GET(VCMPGTSH)},
				{0x746, GET(VCMPGTSH_)},
				{0x348, GET(VMULESH)},
				{0x34a, GET(VCFSX)},
				{0x34c, GET(VSPLTISH)},
				{0x34e, GET(VUPKHPX)},
				{0x380, GET(VADDSWS)},
				{0x382, GET(VMINSW)},
				{0x384, GET(VSRAW)},
				{0x386, GET(VCMPGTSW)},
				{0x786, GET(VCMPGTSW_)},
				{0x38a, GET(VCTUXS)},
				{0x38c, GET(VSPLTISW)},
				{0x3c6, GET(VCMPBFP)},
				{0x7c6, GET(VCMPBFP_)},
				{0x3ca, GET(VCTSXS)},
				{0x3ce, GET(VUPKLPX)},
				{0x400, GET(VSUBUBM)},
				{0x402, GET(VAVGUB)},
				{0x404, GET(VAND)},
				{0x40a, GET(VMAXFP)},
				{0x40c, GET(VSLO)},
				{0x440, GET(VSUBUHM)},
				{0x442, GET(VAVGUH)},
				{0x444, GET(VANDC)},
				{0x44a, GET(VMINFP)},
				{0x44c, GET(VSRO)},
				{0x480, GET(VSUBUWM)},
				{0x482, GET(VAVGUW)},
				{0x484, GET(VOR)},
				{0x4c4, GET(VXOR)},
				{0x502, GET(VAVGSB)},
				{0x504, GET(VNOR)},
				{0x542, GET(VAVGSH)},
				{0x580, GET(VSUBCUW)},
				{0x582, GET(VAVGSW)},
				{0x600, GET(VSUBUBS)},
				{0x604, GET(MFVSCR)},
				{0x608, GET(VSUM4UBS)},
				{0x640, GET(VSUBUHS)},
				{0x644, GET(MTVSCR)},
				{0x648, GET(VSUM4SHS)},
				{0x680, GET(VSUBUWS)},
				{0x688, GET(VSUM2SWS)},
				{0x700, GET(VSUBSBS)},
				{0x708, GET(VSUM4SBS)},
				{0x740, GET(VSUBSHS)},
				{0x780, GET(VSUBSWS)},
				{0x788, GET(VSUMSWS)},
			});

		// Group 0x13 opcodes (field 21..30)
		fill_table(0x13, 10, 1,
			{
				{0x000, GET(MCRF)},
				{0x010, GET(BCLR)},
				{0x021, GET(CRNOR)},
				{0x081, GET(CRANDC)},
				{0x096, GET(ISYNC)},
				{0x0c1, GET(CRXOR)},
				{0x0e1, GET(CRNAND)},
				{0x101, GET(CRAND)},
				{0x121, GET(CREQV)},
				{0x1a1, GET(CRORC)},
				{0x1c1, GET(CROR)},
				{0x210, GET(BCCTR)},
			});

		// Group 0x1e opcodes (field 27..30)
		fill_table(0x1e, 4, 1,
			{
				{0x0, GETRC(RLDICL)},
				{0x1, GETRC(RLDICL)},
				{0x2, GETRC(RLDICR)},
				{0x3, GETRC(RLDICR)},
				{0x4, GETRC(RLDIC)},
				{0x5, GETRC(RLDIC)},
				{0x6, GETRC(RLDIMI)},
				{0x7, GETRC(RLDIMI)},
				{0x8, GETRC(RLDCL)},
				{0x9, GETRC(RLDCR)},
			});

		// Group 0x1f opcodes (field 21..30)
		fill_table(0x1f, 10, 1,
			{
				{0x000, GET(CMP)},
				{0x004, GET(TW)},
				{0x006, GET(LVSL)},
				{0x007, GET(LVEBX)},
				{0x008, GETRC(SUBFC)},
				{0x208, GETRC(SUBFCO)},
				{0x009, GETRC(MULHDU)},
				{0x00a, GETRC(ADDC)},
				{0x20a, GETRC(ADDCO)},
				{0x00b, GETRC(MULHWU)},
				{0x013, GET(MFOCRF)},
				{0x014, GET(LWARX)},
				{0x015, GET(LDX)},
				{0x017, GET(LWZX)},
				{0x018, GETRC(SLW)},
				{0x01a, GETRC(CNTLZW)},
				{0x01b, GETRC(SLD)},
				{0x01c, GETRC(AND)},
				{0x020, GET(CMPL)},
				{0x026, GET(LVSR)},
				{0x027, GET(LVEHX)},
				{0x028, GETRC(SUBF)},
				{0x228, GETRC(SUBFO)},
				{0x035, GET(LDUX)},
				{0x036, GET(DCBST)},
				{0x037, GET(LWZUX)},
				{0x03a, GETRC(CNTLZD)},
				{0x03c, GETRC(ANDC)},
				{0x044, GET(TD)},
				{0x047, GET(LVEWX)},
				{0x049, GETRC(MULHD)},
				{0x04b, GETRC(MULHW)},
				{0x054, GET(LDARX)},
				{0x056, GET(DCBF)},
				{0x057, GET(LBZX)},
				{0x067, GET(LVX)},
				{0x068, GETRC(NEG)},
				{0x268, GETRC(NEGO)},
				{0x077, GET(LBZUX)},
				{0x07c, GETRC(NOR)},
				{0x087, GET(STVEBX)},
				{0x088, GETRC(SUBFE)},
				{0x288, GETRC(SUBFEO)},
				{0x08a, GETRC(ADDE)},
				{0x28a, GETRC(ADDEO)},
				{0x090, GET(MTOCRF)},
				{0x095, GET(STDX)},
				{0x096, GET(STWCX)},
				{0x097, GET(STWX)},
				{0x0a7, GET(STVEHX)},
				{0x0b5, GET(STDUX)},
				{0x0b7, GET(STWUX)},
				{0x0c7, GET(STVEWX)},
				{0x0c8, GETRC(SUBFZE)},
				{0x2c8, GETRC(SUBFZEO)},
				{0x0ca, GETRC(ADDZE)},
				{0x2ca, GETRC(ADDZEO)},
				{0x0d6, GET(STDCX)},
				{0x0d7, GET(STBX)},
				{0x0e7, GET(STVX)},
				{0x0e8, GETRC(SUBFME)},
				{0x2e8, GETRC(SUBFMEO)},
				{0x0e9, GETRC(MULLD)},
				{0x2e9, GETRC(MULLDO)},
				{0x0ea, GETRC(ADDME)},
				{0x2ea, GETRC(ADDMEO)},
				{0x0eb, GETRC(MULLW)},
				{0x2eb, GETRC(MULLWO)},
				{0x0f6, GET(DCBTST)},
				{0x0f7, GET(STBUX)},
				{0x10a, GETRC(ADD)},
				{0x30a, GETRC(ADDO)},
				{0x116, GET(DCBT)},
				{0x117, GET(LHZX)},
				{0x11c, GETRC(EQV)},
				{0x136, GET(ECIWX)},
				{0x137, GET(LHZUX)},
				{0x13c, GETRC(XOR)},
				{0x153, GET(MFSPR)},
				{0x155, GET(LWAX)},
				{0x156, GET(DST)},
				{0x157, GET(LHAX)},
				{0x167, GET(LVXL)},
				{0x173, GET(MFTB)},
				{0x175, GET(LWAUX)},
				{0x176, GET(DSTST)},
				{0x177, GET(LHAUX)},
				{0x197, GET(STHX)},
				{0x19c, GETRC(ORC)},
				{0x1b6, GET(ECOWX)},
				{0x1b7, GET(STHUX)},
				{0x1bc, GETRC(OR)},
				{0x1c9, GETRC(DIVDU)},
				{0x3c9, GETRC(DIVDUO)},
				{0x1cb, GETRC(DIVWU)},
				{0x3cb, GETRC(DIVWUO)},
				{0x1d3, GET(MTSPR)},
				{0x1d6, GET(DCBI)},
				{0x1dc, GETRC(NAND)},
				{0x1e7, GET(STVXL)},
				{0x1e9, GETRC(DIVD)},
				{0x3e9, GETRC(DIVDO)},
				{0x1eb, GETRC(DIVW)},
				{0x3eb, GETRC(DIVWO)},
				{0x207, GET(LVLX)},
				{0x214, GET(LDBRX)},
				{0x215, GET(LSWX)},
				{0x216, GET(LWBRX)},
				{0x217, GET(LFSX)},
				{0x218, GETRC(SRW)},
				{0x21b, GETRC(SRD)},
				{0x227, GET(LVRX)},
				{0x237, GET(LFSUX)},
				{0x255, GET(LSWI)},
				{0x256, GET(SYNC)},
				{0x257, GET(LFDX)},
				{0x277, GET(LFDUX)},
				{0x287, GET(STVLX)},
				{0x294, GET(STDBRX)},
				{0x295, GET(STSWX)},
				{0x296, GET(STWBRX)},
				{0x297, GET(STFSX)},
				{0x2a7, GET(STVRX)},
				{0x2b7, GET(STFSUX)},
				{0x2d5, GET(STSWI)},
				{0x2d7, GET(STFDX)},
				{0x2f7, GET(STFDUX)},
				{0x307, GET(LVLXL)},
				{0x316, GET(LHBRX)},
				{0x318, GETRC(SRAW)},
				{0x31a, GETRC(SRAD)},
				{0x327, GET(LVRXL)},
				{0x336, GET(DSS)},
				{0x338, GETRC(SRAWI)},
				{0x33a, GETRC(SRADI)},
				{0x33b, GETRC(SRADI)},
				{0x356, GET(EIEIO)},
				{0x387, GET(STVLXL)},
				{0x396, GET(STHBRX)},
				{0x39a, GETRC(EXTSH)},
				{0x3a7, GET(STVRXL)},
				{0x3ba, GETRC(EXTSB)},
				{0x3d7, GET(STFIWX)},
				{0x3da, GETRC(EXTSW)},
				{0x3d6, GET(ICBI)},
				{0x3f6, GET(DCBZ)},
			});

		// Group 0x3a opcodes (field 30..31)
		fill_table(0x3a, 2, 0,
			{
				{0x0, GET(LD)},
				{0x1, GET(LDU)},
				{0x2, GET(LWA)},
			});

		// Group 0x3b opcodes (field 21..30)
		fill_table(0x3b, 10, 1,
			{
				{0x12, GETRC(FDIVS), 5},
				{0x14, GETRC(FSUBS), 5},
				{0x15, GETRC(FADDS), 5},
				{0x16, GETRC(FSQRTS), 5},
				{0x18, GETRC(FRES), 5},
				{0x19, GETRC(FMULS), 5},
				{0x1c, GETRC(FMSUBS), 5},
				{0x1d, GETRC(FMADDS), 5},
				{0x1e, GETRC(FNMSUBS), 5},
				{0x1f, GETRC(FNMADDS), 5},
			});

		// Group 0x3e opcodes (field 30..31)
		fill_table(0x3e, 2, 0,
			{
				{0x0, GET(STD)},
				{0x1, GET(STDU)},
			});

		// Group 0x3f opcodes (field 21..30)
		fill_table(0x3f, 10, 1,
			{
				{0x026, GETRC(MTFSB1)},
				{0x040, GET(MCRFS)},
				{0x046, GETRC(MTFSB0)},
				{0x086, GETRC(MTFSFI)},
				{0x247, GETRC(MFFS)},
				{0x2c7, GETRC(MTFSF)},

				{0x000, GET(FCMPU)},
				{0x00c, GETRC(FRSP)},
				{0x00e, GETRC(FCTIW)},
				{0x00f, GETRC(FCTIWZ)},

				{0x012, GETRC(FDIV), 5},
				{0x014, GETRC(FSUB), 5},
				{0x015, GETRC(FADD), 5},
				{0x016, GETRC(FSQRT), 5},
				{0x017, GETRC(FSEL), 5},
				{0x019, GETRC(FMUL), 5},
				{0x01a, GETRC(FRSQRTE), 5},
				{0x01c, GETRC(FMSUB), 5},
				{0x01d, GETRC(FMADD), 5},
				{0x01e, GETRC(FNMSUB), 5},
				{0x01f, GETRC(FNMADD), 5},

				{0x020, GET(FCMPO)},
				{0x028, GETRC(FNEG)},
				{0x048, GETRC(FMR)},
				{0x088, GETRC(FNABS)},
				{0x108, GETRC(FABS)},
				{0x32e, GETRC(FCTID)},
				{0x32f, GETRC(FCTIDZ)},
				{0x34e, GETRC(FCFID)},
			});
	}

	const std::array<T, 0x20000>& get_table() const noexcept
	{
		return m_table;
	}

	T decode(u32 inst) const noexcept
	{
		return m_table[ppu_decode(inst)];
	}
};

#undef GET_
#undef GET
#undef GETRC

namespace ppu_instructions
{
	namespace fields
	{
		enum
		{
			r0,
			r1,
			r2,
			r3,
			r4,
			r5,
			r6,
			r7,
			r8,
			r9,
			r10,
			r11,
			r12,
			r13,
			r14,
			r15,
			r16,
			r17,
			r18,
			r19,
			r20,
			r21,
			r22,
			r23,
			r24,
			r25,
			r26,
			r27,
			r28,
			r29,
			r30,
			r31,
		};

		enum
		{
			f0,
			f1,
			f2,
			f3,
			f4,
			f5,
			f6,
			f7,
			f8,
			f9,
			f10,
			f11,
			f12,
			f13,
			f14,
			f15,
			F16,
			f17,
			f18,
			f19,
			f20,
			f21,
			f22,
			f23,
			f24,
			f25,
			f26,
			f27,
			f28,
			f29,
			f30,
			f31,
		};

		enum
		{
			v0,
			v1,
			v2,
			v3,
			v4,
			v5,
			v6,
			v7,
			v8,
			v9,
			v10,
			v11,
			v12,
			v13,
			v14,
			v15,
			v16,
			v17,
			v18,
			v19,
			v20,
			v21,
			v22,
			v23,
			v24,
			v25,
			v26,
			v27,
			v28,
			v29,
			v30,
			v31,
		};

		enum
		{
			cr0,
			cr1,
			cr2,
			cr3,
			cr4,
			cr5,
			cr6,
			cr7,
		};
	} // namespace fields

	using namespace fields;

	inline u32 ADDI(u32 rt, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x0eu << 26};
		op.rd = rt;
		op.ra = ra;
		op.simm16 = si;
		return op.opcode;
	}
	inline u32 ADDIS(u32 rt, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x0fu << 26};
		op.rd = rt;
		op.ra = ra;
		op.simm16 = si;
		return op.opcode;
	}
	inline u32 XORIS(u32 rt, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x1bu << 26};
		op.rd = rt;
		op.ra = ra;
		op.simm16 = si;
		return op.opcode;
	}
	inline u32 ORI(u32 rt, u32 ra, u32 ui)
	{
		ppu_opcode_t op{0x18u << 26};
		op.rd = rt;
		op.ra = ra;
		op.uimm16 = ui;
		return op.opcode;
	}
	inline u32 ORIS(u32 rt, u32 ra, u32 ui)
	{
		ppu_opcode_t op{0x19u << 26};
		op.rd = rt;
		op.ra = ra;
		op.uimm16 = ui;
		return op.opcode;
	}
	inline u32 OR(u32 ra, u32 rs, u32 rb, bool rc = false)
	{
		ppu_opcode_t op{0x1fu << 26 | 0x1bcu << 1};
		op.rs = rs;
		op.ra = ra;
		op.rb = rb;
		op.rc = rc;
		return op.opcode;
	}
	inline u32 SC(u32 lev)
	{
		ppu_opcode_t op{0x11u << 26 | 1 << 1};
		op.lev = lev;
		return op.opcode;
	}
	inline u32 B(s32 li, bool aa = false, bool lk = false)
	{
		ppu_opcode_t op{0x12u << 26};
		op.ll = li;
		op.aa = aa;
		op.lk = lk;
		return op.opcode;
	}
	inline u32 BC(u32 bo, u32 bi, s32 bd, bool aa = false, bool lk = false)
	{
		ppu_opcode_t op{0x10u << 26};
		op.bo = bo;
		op.bi = bi;
		op.ds = bd / 4;
		op.aa = aa;
		op.lk = lk;
		return op.opcode;
	}
	inline u32 BCLR(u32 bo, u32 bi, u32 bh, bool lk = false)
	{
		ppu_opcode_t op{0x13u << 26 | 0x10u << 1};
		op.bo = bo;
		op.bi = bi;
		op.bh = bh;
		op.lk = lk;
		return op.opcode;
	}
	inline u32 BCCTR(u32 bo, u32 bi, u32 bh, bool lk = false)
	{
		ppu_opcode_t op{0x13u << 26 | 0x210u << 1};
		op.bo = bo;
		op.bi = bi;
		op.bh = bh;
		op.lk = lk;
		return op.opcode;
	}
	inline u32 MFSPR(u32 rt, u32 spr)
	{
		ppu_opcode_t op{0x1fu << 26 | 0x153u << 1};
		op.rd = rt;
		op.spr = spr;
		return op.opcode;
	}
	inline u32 MTSPR(u32 spr, u32 rs)
	{
		ppu_opcode_t op{0x1fu << 26 | 0x1d3u << 1};
		op.rs = rs;
		op.spr = spr;
		return op.opcode;
	}
	inline u32 LWZ(u32 rt, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x20u << 26};
		op.rd = rt;
		op.ra = ra;
		op.simm16 = si;
		return op.opcode;
	}
	inline u32 STW(u32 rt, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x24u << 26};
		op.rd = rt;
		op.ra = ra;
		op.simm16 = si;
		return op.opcode;
	}
	inline u32 STD(u32 rs, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x3eu << 26};
		op.rs = rs;
		op.ra = ra;
		op.ds = si / 4;
		return op.opcode;
	}
	inline u32 STDU(u32 rs, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x3eu << 26 | 1};
		op.rs = rs;
		op.ra = ra;
		op.ds = si / 4;
		return op.opcode;
	}
	inline u32 LD(u32 rt, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x3au << 26};
		op.rd = rt;
		op.ra = ra;
		op.ds = si / 4;
		return op.opcode;
	}
	inline u32 LDU(u32 rt, u32 ra, s32 si)
	{
		ppu_opcode_t op{0x3au << 26 | 1};
		op.rd = rt;
		op.ra = ra;
		op.ds = si / 4;
		return op.opcode;
	}
	inline u32 CMPI(u32 bf, u32 l, u32 ra, u32 ui)
	{
		ppu_opcode_t op{0xbu << 26};
		op.crfd = bf;
		op.l10 = l;
		op.ra = ra;
		op.uimm16 = ui;
		return op.opcode;
	}
	inline u32 CMPLI(u32 bf, u32 l, u32 ra, u32 ui)
	{
		ppu_opcode_t op{0xau << 26};
		op.crfd = bf;
		op.l10 = l;
		op.ra = ra;
		op.uimm16 = ui;
		return op.opcode;
	}
	inline u32 RLDICL(u32 ra, u32 rs, u32 sh, u32 mb, bool rc = false)
	{
		ppu_opcode_t op{30 << 26};
		op.ra = ra;
		op.rs = rs;
		op.sh64 = sh;
		op.mbe64 = mb;
		op.rc = rc;
		return op.opcode;
	}
	inline u32 RLDICR(u32 ra, u32 rs, u32 sh, u32 mb, bool rc = false)
	{
		return RLDICL(ra, rs, sh, mb, rc) | 1 << 2;
	}
	inline u32 STFD(u32 frs, u32 ra, s32 si)
	{
		ppu_opcode_t op{54u << 26};
		op.frs = frs;
		op.ra = ra;
		op.simm16 = si;
		return op.opcode;
	}
	inline u32 STVX(u32 vs, u32 ra, u32 rb)
	{
		ppu_opcode_t op{31 << 26 | 231 << 1};
		op.vs = vs;
		op.ra = ra;
		op.rb = rb;
		return op.opcode;
	}
	inline u32 LFD(u32 frd, u32 ra, s32 si)
	{
		ppu_opcode_t op{50u << 26};
		op.frd = frd;
		op.ra = ra;
		op.simm16 = si;
		return op.opcode;
	}
	inline u32 LVX(u32 vd, u32 ra, u32 rb)
	{
		ppu_opcode_t op{31 << 26 | 103 << 1};
		op.vd = vd;
		op.ra = ra;
		op.rb = rb;
		return op.opcode;
	}
	inline constexpr u32 EIEIO()
	{
		return 0x7c0006ac;
	}

	namespace implicts
	{
		inline u32 NOP()
		{
			return ORI(r0, r0, 0);
		}
		inline u32 MR(u32 rt, u32 ra)
		{
			return OR(rt, ra, ra, false);
		}
		inline u32 LI(u32 rt, u32 imm)
		{
			return ADDI(rt, r0, imm);
		}
		inline u32 LIS(u32 rt, u32 imm)
		{
			return ADDIS(rt, r0, imm);
		}

		inline u32 BLR()
		{
			return BCLR(0x10 | 0x04, 0, 0);
		}
		inline u32 BCTR()
		{
			return BCCTR(0x10 | 0x04, 0, 0);
		}
		inline u32 BCTRL()
		{
			return BCCTR(0x10 | 0x04, 0, 0, true);
		}
		inline u32 MFCTR(u32 reg)
		{
			return MFSPR(reg, 9 << 5);
		}
		inline u32 MTCTR(u32 reg)
		{
			return MTSPR(9 << 5, reg);
		}
		inline u32 MFLR(u32 reg)
		{
			return MFSPR(reg, 8 << 5);
		}
		inline u32 MTLR(u32 reg)
		{
			return MTSPR(8 << 5, reg);
		}

		inline u32 BNE(u32 cr, s32 imm)
		{
			return BC(4, 2 | cr << 2, imm);
		}
		inline u32 BEQ(u32 cr, s32 imm)
		{
			return BC(12, 2 | cr << 2, imm);
		}
		inline u32 BGT(u32 cr, s32 imm)
		{
			return BC(12, 1 | cr << 2, imm);
		}
		inline u32 BNE(s32 imm)
		{
			return BNE(cr0, imm);
		}
		inline u32 BEQ(s32 imm)
		{
			return BEQ(cr0, imm);
		}
		inline u32 BGT(s32 imm)
		{
			return BGT(cr0, imm);
		}

		inline u32 CMPDI(u32 cr, u32 reg, u32 imm)
		{
			return CMPI(cr, 1, reg, imm);
		}
		inline u32 CMPDI(u32 reg, u32 imm)
		{
			return CMPDI(cr0, reg, imm);
		}
		inline u32 CMPWI(u32 cr, u32 reg, u32 imm)
		{
			return CMPI(cr, 0, reg, imm);
		}
		inline u32 CMPWI(u32 reg, u32 imm)
		{
			return CMPWI(cr0, reg, imm);
		}
		inline u32 CMPLDI(u32 cr, u32 reg, u32 imm)
		{
			return CMPLI(cr, 1, reg, imm);
		}
		inline u32 CMPLDI(u32 reg, u32 imm)
		{
			return CMPLDI(cr0, reg, imm);
		}
		inline u32 CMPLWI(u32 cr, u32 reg, u32 imm)
		{
			return CMPLI(cr, 0, reg, imm);
		}
		inline u32 CMPLWI(u32 reg, u32 imm)
		{
			return CMPLWI(cr0, reg, imm);
		}

		inline u32 EXTRDI(u32 x, u32 y, u32 n, u32 b)
		{
			return RLDICL(x, y, b + n, 64 - b, false);
		}
		inline u32 SRDI(u32 x, u32 y, u32 n)
		{
			return RLDICL(x, y, 64 - n, n, false);
		}
		inline u32 CLRLDI(u32 x, u32 y, u32 n)
		{
			return RLDICL(x, y, 0, n, false);
		}
		inline u32 CLRRDI(u32 x, u32 y, u32 n)
		{
			return RLDICR(x, y, 0, 63 - n, false);
		}

		inline constexpr u32 TRAP()
		{
			return 0x7FE00008;
		} // tw 31,r0,r0
	} // namespace implicts

	using namespace implicts;
} // namespace ppu_instructions
