Cores/Nds/ARMInterpreter_Multiply.cs
namespace Emulotl.Nds;

public static partial class ARMInterpreter
{
	private static void BuildMultiplyTable()
	{
		ARMInstrTable[0x009] = A_MUL; ARMInstrTable[0x019] = A_MUL;
		ARMInstrTable[0x029] = A_MLA; ARMInstrTable[0x039] = A_MLA;
		ARMInstrTable[0x089] = A_UMULL; ARMInstrTable[0x099] = A_UMULL;
		ARMInstrTable[0x0A9] = A_UMLAL; ARMInstrTable[0x0B9] = A_UMLAL;
		ARMInstrTable[0x0C9] = A_SMULL; ARMInstrTable[0x0D9] = A_SMULL;
		ARMInstrTable[0x0E9] = A_SMLAL; ARMInstrTable[0x0F9] = A_SMLAL;

		ARMInstrTable[0x100] = A_MRS;
		ARMInstrTable[0x105] = A_QADD;
		ARMInstrTable[0x108] = A_SMLAxy; ARMInstrTable[0x10A] = A_SMLAxy; ARMInstrTable[0x10C] = A_SMLAxy; ARMInstrTable[0x10E] = A_SMLAxy;

		ARMInstrTable[0x120] = A_MSR_REG;
		ARMInstrTable[0x125] = A_QSUB;
		ARMInstrTable[0x128] = A_SMLAWy; ARMInstrTable[0x12C] = A_SMLAWy;
		ARMInstrTable[0x12A] = A_SMULWy; ARMInstrTable[0x12E] = A_SMULWy;

		ARMInstrTable[0x140] = A_MRS;
		ARMInstrTable[0x145] = A_QDADD;
		ARMInstrTable[0x148] = A_SMLALxy; ARMInstrTable[0x14A] = A_SMLALxy; ARMInstrTable[0x14C] = A_SMLALxy; ARMInstrTable[0x14E] = A_SMLALxy;

		ARMInstrTable[0x160] = A_MSR_REG;
		ARMInstrTable[0x161] = A_CLZ;
		ARMInstrTable[0x165] = A_QDSUB;
		ARMInstrTable[0x168] = A_SMULxy; ARMInstrTable[0x16A] = A_SMULxy; ARMInstrTable[0x16C] = A_SMULxy; ARMInstrTable[0x16E] = A_SMULxy;
	}

	private static int MulCycles( ARM cpu, uint rs, int m0, int m1, int m2, int m3 )
	{
		if ( (rs & 0xFFFFFF00) == 0x00000000 || (rs & 0xFFFFFF00) == 0xFFFFFF00 ) return m0;
		if ( (rs & 0xFFFF0000) == 0x00000000 || (rs & 0xFFFF0000) == 0xFFFF0000 ) return m1;
		if ( (rs & 0xFF000000) == 0x00000000 || (rs & 0xFF000000) == 0xFF000000 ) return m2;
		return m3;
	}

	private static int MulCyclesU( uint rs, int m0, int m1, int m2, int m3 )
	{
		if ( (rs & 0xFFFFFF00) == 0x00000000 ) return m0;
		if ( (rs & 0xFFFF0000) == 0x00000000 ) return m1;
		if ( (rs & 0xFF000000) == 0x00000000 ) return m2;
		return m3;
	}

	public static void A_MUL( ARM cpu )
	{
		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];

		uint res = rm * rs;

		cpu.R[(cpu.CurInstr >> 16) & 0xF] = res;
		if ( (cpu.CurInstr & (1 << 20)) != 0 )
		{
			cpu.SetNZ( (res & 0x80000000) != 0, res == 0 );
			if ( cpu.Num == 1 ) cpu.SetC( false );
		}

		int cycles = cpu.Num == 0
			? ((cpu.CurInstr & (1 << 20)) != 0 ? 3 : 1)
			: MulCycles( cpu, rs, 1, 2, 3, 4 );

		cpu.AddCycles_CI( cycles );
	}

	public static void A_MLA( ARM cpu )
	{
		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];
		uint rn = cpu.R[(cpu.CurInstr >> 12) & 0xF];

		uint res = (rm * rs) + rn;

		cpu.R[(cpu.CurInstr >> 16) & 0xF] = res;
		if ( (cpu.CurInstr & (1 << 20)) != 0 )
		{
			cpu.SetNZ( (res & 0x80000000) != 0, res == 0 );
			if ( cpu.Num == 1 ) cpu.SetC( false );
		}

		int cycles = cpu.Num == 0
			? ((cpu.CurInstr & (1 << 20)) != 0 ? 3 : 1)
			: MulCycles( cpu, rs, 2, 3, 4, 5 );

		cpu.AddCycles_CI( cycles );
	}

	public static void A_UMULL( ARM cpu )
	{
		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];

		ulong res = (ulong)rm * rs;

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = (uint)res;
		cpu.R[(cpu.CurInstr >> 16) & 0xF] = (uint)(res >> 32);
		if ( (cpu.CurInstr & (1 << 20)) != 0 )
		{
			cpu.SetNZ( (res >> 63) != 0, res == 0 );
			if ( cpu.Num == 1 ) cpu.SetC( false );
		}

		int cycles = cpu.Num == 0
			? ((cpu.CurInstr & (1 << 20)) != 0 ? 3 : 1)
			: MulCyclesU( rs, 2, 3, 4, 5 );

		cpu.AddCycles_CI( cycles );
	}

	public static void A_UMLAL( ARM cpu )
	{
		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];

		ulong res = (ulong)rm * rs;

		ulong rd = cpu.R[(cpu.CurInstr >> 12) & 0xF] | ((ulong)cpu.R[(cpu.CurInstr >> 16) & 0xF] << 32);
		res += rd;

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = (uint)res;
		cpu.R[(cpu.CurInstr >> 16) & 0xF] = (uint)(res >> 32);
		if ( (cpu.CurInstr & (1 << 20)) != 0 )
		{
			cpu.SetNZ( (res >> 63) != 0, res == 0 );
			if ( cpu.Num == 1 ) cpu.SetC( false );
		}

		int cycles = cpu.Num == 0
			? ((cpu.CurInstr & (1 << 20)) != 0 ? 3 : 1)
			: MulCyclesU( rs, 2, 3, 4, 5 );

		cpu.AddCycles_CI( cycles );
	}

	public static void A_SMULL( ARM cpu )
	{
		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];

		long res = (long)(int)rm * (int)rs;

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = (uint)res;
		cpu.R[(cpu.CurInstr >> 16) & 0xF] = (uint)((ulong)res >> 32);
		if ( (cpu.CurInstr & (1 << 20)) != 0 )
		{
			cpu.SetNZ( ((ulong)res >> 63) != 0, res == 0 );
			if ( cpu.Num == 1 ) cpu.SetC( false );
		}

		int cycles = cpu.Num == 0
			? ((cpu.CurInstr & (1 << 20)) != 0 ? 3 : 1)
			: MulCycles( cpu, rs, 2, 3, 4, 5 );

		cpu.AddCycles_CI( cycles );
	}

	public static void A_SMLAL( ARM cpu )
	{
		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];

		long res = (long)(int)rm * (int)rs;

		long rd = (long)(cpu.R[(cpu.CurInstr >> 12) & 0xF] | ((ulong)cpu.R[(cpu.CurInstr >> 16) & 0xF] << 32));
		res += rd;

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = (uint)res;
		cpu.R[(cpu.CurInstr >> 16) & 0xF] = (uint)((ulong)res >> 32);
		if ( (cpu.CurInstr & (1 << 20)) != 0 )
		{
			cpu.SetNZ( ((ulong)res >> 63) != 0, res == 0 );
			if ( cpu.Num == 1 ) cpu.SetC( false );
		}

		int cycles = cpu.Num == 0
			? ((cpu.CurInstr & (1 << 20)) != 0 ? 3 : 1)
			: MulCycles( cpu, rs, 2, 3, 4, 5 );

		cpu.AddCycles_CI( cycles );
	}

	public static void A_SMLAxy( ARM cpu )
	{
		if ( cpu.Num != 0 ) return;

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];
		uint rn = cpu.R[(cpu.CurInstr >> 12) & 0xF];

		if ( (cpu.CurInstr & (1 << 5)) != 0 ) rm >>= 16;
		else rm &= 0xFFFF;
		if ( (cpu.CurInstr & (1 << 6)) != 0 ) rs >>= 16;
		else rs &= 0xFFFF;

		uint resMul = (uint)((short)rm * (short)rs);
		uint res = resMul + rn;

		cpu.R[(cpu.CurInstr >> 16) & 0xF] = res;
		if ( OverflowAdd( resMul, rn ) )
			cpu.CPSR |= 0x08000000;

		cpu.AddCycles_C();
	}

	public static void A_SMLAWy( ARM cpu )
	{
		if ( cpu.Num != 0 ) return;

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];
		uint rn = cpu.R[(cpu.CurInstr >> 12) & 0xF];

		if ( (cpu.CurInstr & (1 << 6)) != 0 ) rs >>= 16;
		else rs &= 0xFFFF;

		uint resMul = (uint)(((long)(int)rm * (short)rs) >> 16);
		uint res = resMul + rn;

		cpu.R[(cpu.CurInstr >> 16) & 0xF] = res;
		if ( OverflowAdd( resMul, rn ) )
			cpu.CPSR |= 0x08000000;

		cpu.AddCycles_C();
	}

	public static void A_SMULxy( ARM cpu )
	{
		if ( cpu.Num != 0 ) return;

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];

		if ( (cpu.CurInstr & (1 << 5)) != 0 ) rm >>= 16;
		else rm &= 0xFFFF;
		if ( (cpu.CurInstr & (1 << 6)) != 0 ) rs >>= 16;
		else rs &= 0xFFFF;

		uint res = (uint)((short)rm * (short)rs);

		cpu.R[(cpu.CurInstr >> 16) & 0xF] = res;
		cpu.AddCycles_C();
	}

	public static void A_SMULWy( ARM cpu )
	{
		if ( cpu.Num != 0 ) return;

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];

		if ( (cpu.CurInstr & (1 << 6)) != 0 ) rs >>= 16;
		else rs &= 0xFFFF;

		uint res = (uint)(((long)(int)rm * (short)rs) >> 16);

		cpu.R[(cpu.CurInstr >> 16) & 0xF] = res;
		cpu.AddCycles_C();
	}

	public static void A_SMLALxy( ARM cpu )
	{
		if ( cpu.Num != 0 ) return;

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rs = cpu.R[(cpu.CurInstr >> 8) & 0xF];

		if ( (cpu.CurInstr & (1 << 5)) != 0 ) rm >>= 16;
		else rm &= 0xFFFF;
		if ( (cpu.CurInstr & (1 << 6)) != 0 ) rs >>= 16;
		else rs &= 0xFFFF;

		long res = (long)(short)rm * (short)rs;

		long rd = (long)(cpu.R[(cpu.CurInstr >> 12) & 0xF] | ((ulong)cpu.R[(cpu.CurInstr >> 16) & 0xF] << 32));
		res += rd;

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = (uint)res;
		cpu.R[(cpu.CurInstr >> 16) & 0xF] = (uint)((ulong)res >> 32);

		cpu.AddCycles_CI( 1 );
	}

	public static void A_CLZ( ARM cpu )
	{
		if ( cpu.Num != 0 ) { A_UNK( cpu ); return; }

		uint val = cpu.R[cpu.CurInstr & 0xF];

		uint res = 0;
		while ( (val & 0xFF000000) == 0 )
		{
			res += 8;
			val <<= 8;
			val |= 0xFF;
		}
		while ( (val & 0x80000000) == 0 )
		{
			res++;
			val <<= 1;
			val |= 0x1;
		}

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = res;
		cpu.AddCycles_C();
	}

	public static void A_QADD( ARM cpu )
	{
		if ( cpu.Num != 0 ) { A_UNK( cpu ); return; }

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rn = cpu.R[(cpu.CurInstr >> 16) & 0xF];

		uint res = rm + rn;
		if ( OverflowAdd( rm, rn ) )
		{
			res = (res & 0x80000000) != 0 ? 0x7FFFFFFF : 0x80000000;
			cpu.CPSR |= 0x08000000;
		}

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = res;
		cpu.AddCycles_C();
	}

	public static void A_QSUB( ARM cpu )
	{
		if ( cpu.Num != 0 ) { A_UNK( cpu ); return; }

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rn = cpu.R[(cpu.CurInstr >> 16) & 0xF];

		uint res = rm - rn;
		if ( OverflowSub( rm, rn ) )
		{
			res = (res & 0x80000000) != 0 ? 0x7FFFFFFF : 0x80000000;
			cpu.CPSR |= 0x08000000;
		}

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = res;
		cpu.AddCycles_C();
	}

	public static void A_QDADD( ARM cpu )
	{
		if ( cpu.Num != 0 ) { A_UNK( cpu ); return; }

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rn = cpu.R[(cpu.CurInstr >> 16) & 0xF];

		if ( OverflowAdd( rn, rn ) )
		{
			rn = (rn & 0x80000000) != 0 ? 0x80000000 : 0x7FFFFFFF;
			cpu.CPSR |= 0x08000000;
		}
		else rn <<= 1;

		uint res = rm + rn;
		if ( OverflowAdd( rm, rn ) )
		{
			res = (res & 0x80000000) != 0 ? 0x7FFFFFFF : 0x80000000;
			cpu.CPSR |= 0x08000000;
		}

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = res;
		cpu.AddCycles_C();
	}

	public static void A_QDSUB( ARM cpu )
	{
		if ( cpu.Num != 0 ) { A_UNK( cpu ); return; }

		uint rm = cpu.R[cpu.CurInstr & 0xF];
		uint rn = cpu.R[(cpu.CurInstr >> 16) & 0xF];

		if ( OverflowAdd( rn, rn ) )
		{
			rn = (rn & 0x80000000) != 0 ? 0x80000000 : 0x7FFFFFFF;
			cpu.CPSR |= 0x08000000;
		}
		else rn <<= 1;

		uint res = rm - rn;
		if ( OverflowSub( rm, rn ) )
		{
			res = (res & 0x80000000) != 0 ? 0x7FFFFFFF : 0x80000000;
			cpu.CPSR |= 0x08000000;
		}

		cpu.R[(cpu.CurInstr >> 12) & 0xF] = res;
		cpu.AddCycles_C();
	}
}