601 lines
21 KiB
C#
601 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Iris.CPU
|
|
{
|
|
public sealed class CPU_Core
|
|
{
|
|
public enum Model
|
|
{
|
|
ARM7TDMI,
|
|
ARM946ES
|
|
}
|
|
|
|
public delegate Byte Read8_Delegate(UInt32 address);
|
|
public delegate UInt16 Read16_Delegate(UInt32 address);
|
|
public delegate UInt32 Read32_Delegate(UInt32 address);
|
|
public delegate void Write8_Delegate(UInt32 address, Byte value);
|
|
public delegate void Write16_Delegate(UInt32 address, UInt16 value);
|
|
public delegate void Write32_Delegate(UInt32 address, UInt32 value);
|
|
public delegate UInt64 HandleSWI_Delegate();
|
|
public delegate UInt64 HandleIRQ_Delegate();
|
|
|
|
// could have used function pointers (delegate*) for performance instead of delegates but it's less flexible (cannot use non-static function for instance)
|
|
//public readonly struct CallbackInterface
|
|
//(
|
|
// Read8_Delegate read8,
|
|
// Read16_Delegate read16,
|
|
// Read32_Delegate read32,
|
|
// Write8_Delegate write8,
|
|
// Write16_Delegate write16,
|
|
// Write32_Delegate write32,
|
|
// HandleSWI_Delegate handleSWI,
|
|
// HandleIRQ_Delegate handleIRQ
|
|
//)
|
|
//{
|
|
// internal readonly Read8_Delegate _read8 = read8;
|
|
// internal readonly Read16_Delegate _read16 = read16;
|
|
// internal readonly Read32_Delegate _read32 = read32;
|
|
// internal readonly Write8_Delegate _write8 = write8;
|
|
// internal readonly Write16_Delegate _write16 = write16;
|
|
// internal readonly Write32_Delegate _write32 = write32;
|
|
// internal readonly HandleSWI_Delegate _handleSWI = handleSWI;
|
|
// internal readonly HandleIRQ_Delegate _handleIRQ = handleIRQ;
|
|
//}
|
|
|
|
public readonly struct CallbackInterface
|
|
{
|
|
public CallbackInterface
|
|
(
|
|
Read8_Delegate read8,
|
|
Read16_Delegate read16,
|
|
Read32_Delegate read32,
|
|
Write8_Delegate write8,
|
|
Write16_Delegate write16,
|
|
Write32_Delegate write32,
|
|
HandleSWI_Delegate handleSWI,
|
|
HandleIRQ_Delegate handleIRQ
|
|
)
|
|
{
|
|
_read8 = read8;
|
|
_read16 = read16;
|
|
_read32 = read32;
|
|
_write8 = write8;
|
|
_write16 = write16;
|
|
_write32 = write32;
|
|
_handleSWI = handleSWI;
|
|
_handleIRQ = handleIRQ;
|
|
}
|
|
|
|
internal readonly Read8_Delegate _read8;
|
|
internal readonly Read16_Delegate _read16;
|
|
internal readonly Read32_Delegate _read32;
|
|
internal readonly Write8_Delegate _write8;
|
|
internal readonly Write16_Delegate _write16;
|
|
internal readonly Write32_Delegate _write32;
|
|
internal readonly HandleSWI_Delegate _handleSWI;
|
|
internal readonly HandleIRQ_Delegate _handleIRQ;
|
|
}
|
|
|
|
public enum Signal
|
|
{
|
|
High,
|
|
Low
|
|
}
|
|
|
|
internal enum Flag
|
|
{
|
|
V = 28,
|
|
C = 29,
|
|
Z = 30,
|
|
N = 31
|
|
}
|
|
|
|
//internal unsafe readonly struct InstructionListEntry1<T>(T mask, T expected, delegate*<CPU_Core, T, UInt64> handler, List<Model> modelList)
|
|
//{
|
|
// internal readonly T _mask = mask;
|
|
// internal readonly T _expected = expected;
|
|
// internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler = handler;
|
|
// internal readonly List<Model> _modelList = modelList;
|
|
//}
|
|
|
|
internal unsafe readonly struct InstructionListEntry<T>
|
|
{
|
|
internal InstructionListEntry(T mask, T expected, delegate*<CPU_Core, T, UInt64> handler, List<Model> modelList)
|
|
{
|
|
_mask = mask;
|
|
_expected = expected;
|
|
_handler = handler;
|
|
_modelList = modelList;
|
|
}
|
|
|
|
internal readonly T _mask;
|
|
internal readonly T _expected;
|
|
internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler;
|
|
internal readonly List<Model> _modelList;
|
|
}
|
|
|
|
//internal unsafe readonly struct InstructionLUTEntry<T>(delegate*<CPU_Core, T, UInt64> handler)
|
|
//{
|
|
// internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler = handler;
|
|
//}
|
|
|
|
|
|
internal unsafe readonly struct InstructionLUTEntry<T>
|
|
{
|
|
internal InstructionLUTEntry(delegate*<CPU_Core, T, UInt64> handler)
|
|
{
|
|
_handler = handler;
|
|
}
|
|
internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler;
|
|
}
|
|
|
|
internal const UInt32 ModeMask = 0b1_1111;
|
|
internal const UInt32 UserMode = 0b1_0000;
|
|
internal const UInt32 SystemMode = 0b1_1111;
|
|
internal const UInt32 SupervisorMode = 0b1_0011;
|
|
internal const UInt32 AbortMode = 0b1_0111;
|
|
internal const UInt32 UndefinedMode = 0b1_1011;
|
|
internal const UInt32 InterruptMode = 0b1_0010;
|
|
internal const UInt32 FastInterruptMode = 0b1_0001;
|
|
|
|
public const UInt32 SP = 13;
|
|
public const UInt32 LR = 14;
|
|
public const UInt32 PC = 15;
|
|
|
|
public readonly UInt32[] Reg = new UInt32[16];
|
|
public UInt32 CPSR;
|
|
public UInt32 SPSR;
|
|
|
|
public UInt32 Reg8_usr, Reg9_usr, Reg10_usr, Reg11_usr, Reg12_usr, Reg13_usr, Reg14_usr;
|
|
public UInt32 Reg13_svc, Reg14_svc;
|
|
public UInt32 Reg13_abt, Reg14_abt;
|
|
public UInt32 Reg13_und, Reg14_und;
|
|
public UInt32 Reg13_irq, Reg14_irq;
|
|
public UInt32 Reg8_fiq, Reg9_fiq, Reg10_fiq, Reg11_fiq, Reg12_fiq, Reg13_fiq, Reg14_fiq;
|
|
public UInt32 SPSR_svc, SPSR_abt, SPSR_und, SPSR_irq, SPSR_fiq;
|
|
|
|
internal readonly Model _model;
|
|
internal readonly CallbackInterface _callbackInterface;
|
|
|
|
private readonly ARM_Interpreter _armInterpreter;
|
|
private readonly THUMB_Interpreter _thumbInterpreter;
|
|
|
|
public UInt32 NextInstructionAddress;
|
|
public Signal NIRQ;
|
|
|
|
public CPU_Core(Model model, CallbackInterface callbackInterface)
|
|
{
|
|
_model = model;
|
|
_callbackInterface = callbackInterface;
|
|
_armInterpreter = new(this);
|
|
_thumbInterpreter = new(this);
|
|
}
|
|
|
|
public void ResetState()
|
|
{
|
|
Array.Clear(Reg,0,Reg.Length);
|
|
|
|
CPSR = 0b1_0000;
|
|
SPSR = 0;
|
|
|
|
Reg8_usr = 0;
|
|
Reg9_usr = 0;
|
|
Reg10_usr = 0;
|
|
Reg11_usr = 0;
|
|
Reg12_usr = 0;
|
|
Reg13_usr = 0;
|
|
Reg14_usr = 0;
|
|
|
|
Reg13_svc = 0;
|
|
Reg14_svc = 0;
|
|
|
|
Reg13_abt = 0;
|
|
Reg14_abt = 0;
|
|
|
|
Reg13_und = 0;
|
|
Reg14_und = 0;
|
|
|
|
Reg13_irq = 0;
|
|
Reg14_irq = 0;
|
|
|
|
Reg8_fiq = 0;
|
|
Reg9_fiq = 0;
|
|
Reg10_fiq = 0;
|
|
Reg11_fiq = 0;
|
|
Reg12_fiq = 0;
|
|
Reg13_fiq = 0;
|
|
Reg14_fiq = 0;
|
|
|
|
SPSR_svc = 0;
|
|
SPSR_abt = 0;
|
|
SPSR_und = 0;
|
|
SPSR_irq = 0;
|
|
SPSR_fiq = 0;
|
|
|
|
NextInstructionAddress = 0;
|
|
NIRQ = Signal.High;
|
|
}
|
|
|
|
public void LoadState(BinaryReader reader)
|
|
{
|
|
foreach (ref UInt32 reg in Reg.AsSpan())
|
|
reg = reader.ReadUInt32();
|
|
|
|
CPSR = reader.ReadUInt32();
|
|
SPSR = reader.ReadUInt32();
|
|
|
|
Reg8_usr = reader.ReadUInt32();
|
|
Reg9_usr = reader.ReadUInt32();
|
|
Reg10_usr = reader.ReadUInt32();
|
|
Reg11_usr = reader.ReadUInt32();
|
|
Reg12_usr = reader.ReadUInt32();
|
|
Reg13_usr = reader.ReadUInt32();
|
|
Reg14_usr = reader.ReadUInt32();
|
|
|
|
Reg13_svc = reader.ReadUInt32();
|
|
Reg14_svc = reader.ReadUInt32();
|
|
|
|
Reg13_abt = reader.ReadUInt32();
|
|
Reg14_abt = reader.ReadUInt32();
|
|
|
|
Reg13_und = reader.ReadUInt32();
|
|
Reg14_und = reader.ReadUInt32();
|
|
|
|
Reg13_irq = reader.ReadUInt32();
|
|
Reg14_irq = reader.ReadUInt32();
|
|
|
|
Reg8_fiq = reader.ReadUInt32();
|
|
Reg9_fiq = reader.ReadUInt32();
|
|
Reg10_fiq = reader.ReadUInt32();
|
|
Reg11_fiq = reader.ReadUInt32();
|
|
Reg12_fiq = reader.ReadUInt32();
|
|
Reg13_fiq = reader.ReadUInt32();
|
|
Reg14_fiq = reader.ReadUInt32();
|
|
|
|
SPSR_svc = reader.ReadUInt32();
|
|
SPSR_abt = reader.ReadUInt32();
|
|
SPSR_und = reader.ReadUInt32();
|
|
SPSR_irq = reader.ReadUInt32();
|
|
SPSR_fiq = reader.ReadUInt32();
|
|
|
|
NextInstructionAddress = reader.ReadUInt32();
|
|
NIRQ = (Signal)reader.ReadInt32();
|
|
}
|
|
|
|
public void SaveState(BinaryWriter writer)
|
|
{
|
|
foreach (UInt32 reg in Reg)
|
|
writer.Write(reg);
|
|
|
|
writer.Write(CPSR);
|
|
writer.Write(SPSR);
|
|
|
|
writer.Write(Reg8_usr);
|
|
writer.Write(Reg9_usr);
|
|
writer.Write(Reg10_usr);
|
|
writer.Write(Reg11_usr);
|
|
writer.Write(Reg12_usr);
|
|
writer.Write(Reg13_usr);
|
|
writer.Write(Reg14_usr);
|
|
|
|
writer.Write(Reg13_svc);
|
|
writer.Write(Reg14_svc);
|
|
|
|
writer.Write(Reg13_abt);
|
|
writer.Write(Reg14_abt);
|
|
|
|
writer.Write(Reg13_und);
|
|
writer.Write(Reg14_und);
|
|
|
|
writer.Write(Reg13_irq);
|
|
writer.Write(Reg14_irq);
|
|
|
|
writer.Write(Reg8_fiq);
|
|
writer.Write(Reg9_fiq);
|
|
writer.Write(Reg10_fiq);
|
|
writer.Write(Reg11_fiq);
|
|
writer.Write(Reg12_fiq);
|
|
writer.Write(Reg13_fiq);
|
|
writer.Write(Reg14_fiq);
|
|
|
|
writer.Write(SPSR_svc);
|
|
writer.Write(SPSR_abt);
|
|
writer.Write(SPSR_und);
|
|
writer.Write(SPSR_irq);
|
|
writer.Write(SPSR_fiq);
|
|
|
|
writer.Write(NextInstructionAddress);
|
|
writer.Write((int)NIRQ);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public UInt64 Step()
|
|
{
|
|
UInt32 i = (CPSR >> 7) & 1;
|
|
|
|
if ((i == 0) && (NIRQ == Signal.Low))
|
|
return _callbackInterface._handleIRQ();
|
|
|
|
UInt32 t = (CPSR >> 5) & 1;
|
|
|
|
if (t == 0)
|
|
return _armInterpreter.Step();
|
|
else
|
|
return _thumbInterpreter.Step();
|
|
}
|
|
|
|
public void SetCPSR(UInt32 value)
|
|
{
|
|
UInt32 previousMode = CPSR & ModeMask;
|
|
UInt32 newMode = value & ModeMask;
|
|
|
|
CPSR = value | 0b1_0000;
|
|
if (previousMode != newMode)
|
|
{
|
|
ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(Reg);
|
|
ref UInt32 reg8 = ref Unsafe.Add(ref regDataRef, 8);
|
|
ref UInt32 reg9 = ref Unsafe.Add(ref regDataRef, 9);
|
|
ref UInt32 reg10 = ref Unsafe.Add(ref regDataRef, 10);
|
|
ref UInt32 reg11 = ref Unsafe.Add(ref regDataRef, 11);
|
|
ref UInt32 reg12 = ref Unsafe.Add(ref regDataRef, 12);
|
|
ref UInt32 reg13 = ref Unsafe.Add(ref regDataRef, 13);
|
|
ref UInt32 reg14 = ref Unsafe.Add(ref regDataRef, 14);
|
|
|
|
// save previous mode registers
|
|
switch (previousMode)
|
|
{
|
|
case UserMode:
|
|
case SystemMode:
|
|
Reg8_usr = reg8;
|
|
Reg9_usr = reg9;
|
|
Reg10_usr = reg10;
|
|
Reg11_usr = reg11;
|
|
Reg12_usr = reg12;
|
|
Reg13_usr = reg13;
|
|
Reg14_usr = reg14;
|
|
break;
|
|
case SupervisorMode:
|
|
Reg8_usr = reg8;
|
|
Reg9_usr = reg9;
|
|
Reg10_usr = reg10;
|
|
Reg11_usr = reg11;
|
|
Reg12_usr = reg12;
|
|
Reg13_svc = reg13;
|
|
Reg14_svc = reg14;
|
|
SPSR_svc = SPSR;
|
|
break;
|
|
case AbortMode:
|
|
Reg8_usr = reg8;
|
|
Reg9_usr = reg9;
|
|
Reg10_usr = reg10;
|
|
Reg11_usr = reg11;
|
|
Reg12_usr = reg12;
|
|
Reg13_abt = reg13;
|
|
Reg14_abt = reg14;
|
|
SPSR_abt = SPSR;
|
|
break;
|
|
case UndefinedMode:
|
|
Reg8_usr = reg8;
|
|
Reg9_usr = reg9;
|
|
Reg10_usr = reg10;
|
|
Reg11_usr = reg11;
|
|
Reg12_usr = reg12;
|
|
Reg13_und = reg13;
|
|
Reg14_und = reg14;
|
|
SPSR_und = SPSR;
|
|
break;
|
|
case InterruptMode:
|
|
Reg8_usr = reg8;
|
|
Reg9_usr = reg9;
|
|
Reg10_usr = reg10;
|
|
Reg11_usr = reg11;
|
|
Reg12_usr = reg12;
|
|
Reg13_irq = reg13;
|
|
Reg14_irq = reg14;
|
|
SPSR_irq = SPSR;
|
|
break;
|
|
case FastInterruptMode:
|
|
Reg8_fiq = reg8;
|
|
Reg9_fiq = reg9;
|
|
Reg10_fiq = reg10;
|
|
Reg11_fiq = reg11;
|
|
Reg12_fiq = reg12;
|
|
Reg13_fiq = reg13;
|
|
Reg14_fiq = reg14;
|
|
SPSR_fiq = SPSR;
|
|
break;
|
|
}
|
|
|
|
// load new mode registers
|
|
switch (newMode)
|
|
{
|
|
case UserMode:
|
|
case SystemMode:
|
|
reg8 = Reg8_usr;
|
|
reg9 = Reg9_usr;
|
|
reg10 = Reg10_usr;
|
|
reg11 = Reg11_usr;
|
|
reg12 = Reg12_usr;
|
|
reg13 = Reg13_usr;
|
|
reg14 = Reg14_usr;
|
|
break;
|
|
case SupervisorMode:
|
|
reg8 = Reg8_usr;
|
|
reg9 = Reg9_usr;
|
|
reg10 = Reg10_usr;
|
|
reg11 = Reg11_usr;
|
|
reg12 = Reg12_usr;
|
|
reg13 = Reg13_svc;
|
|
reg14 = Reg14_svc;
|
|
SPSR = SPSR_svc;
|
|
break;
|
|
case AbortMode:
|
|
reg8 = Reg8_usr;
|
|
reg9 = Reg9_usr;
|
|
reg10 = Reg10_usr;
|
|
reg11 = Reg11_usr;
|
|
reg12 = Reg12_usr;
|
|
reg13 = Reg13_abt;
|
|
reg14 = Reg14_abt;
|
|
SPSR = SPSR_abt;
|
|
break;
|
|
case UndefinedMode:
|
|
reg8 = Reg8_usr;
|
|
reg9 = Reg9_usr;
|
|
reg10 = Reg10_usr;
|
|
reg11 = Reg11_usr;
|
|
reg12 = Reg12_usr;
|
|
reg13 = Reg13_und;
|
|
reg14 = Reg14_und;
|
|
SPSR = SPSR_und;
|
|
break;
|
|
case InterruptMode:
|
|
reg8 = Reg8_usr;
|
|
reg9 = Reg9_usr;
|
|
reg10 = Reg10_usr;
|
|
reg11 = Reg11_usr;
|
|
reg12 = Reg12_usr;
|
|
reg13 = Reg13_irq;
|
|
reg14 = Reg14_irq;
|
|
SPSR = SPSR_irq;
|
|
break;
|
|
case FastInterruptMode:
|
|
reg8 = Reg8_fiq;
|
|
reg9 = Reg9_fiq;
|
|
reg10 = Reg10_fiq;
|
|
reg11 = Reg11_fiq;
|
|
reg12 = Reg12_fiq;
|
|
reg13 = Reg13_fiq;
|
|
reg14 = Reg14_fiq;
|
|
SPSR = SPSR_fiq;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal UInt32 GetFlag(Flag flag)
|
|
{
|
|
return (CPSR >> (int)flag) & 1;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void SetFlag(Flag flag, UInt32 value)
|
|
{
|
|
CPSR = (CPSR & ~(1u << (int)flag)) | (value << (int)flag);
|
|
}
|
|
|
|
internal bool ConditionPassed(UInt32 cond)
|
|
{
|
|
return cond switch
|
|
{
|
|
// EQ
|
|
0b0000 => GetFlag(Flag.Z) == 1,
|
|
// NE
|
|
0b0001 => GetFlag(Flag.Z) == 0,
|
|
// CS/HS
|
|
0b0010 => GetFlag(Flag.C) == 1,
|
|
// CC/LO
|
|
0b0011 => GetFlag(Flag.C) == 0,
|
|
// MI
|
|
0b0100 => GetFlag(Flag.N) == 1,
|
|
// PL
|
|
0b0101 => GetFlag(Flag.N) == 0,
|
|
// VS
|
|
0b0110 => GetFlag(Flag.V) == 1,
|
|
// VC
|
|
0b0111 => GetFlag(Flag.V) == 0,
|
|
// HI
|
|
0b1000 => (GetFlag(Flag.C) == 1) && (GetFlag(Flag.Z) == 0),
|
|
// LS
|
|
0b1001 => (GetFlag(Flag.C) == 0) || (GetFlag(Flag.Z) == 1),
|
|
// GE
|
|
0b1010 => GetFlag(Flag.N) == GetFlag(Flag.V),
|
|
// LT
|
|
0b1011 => GetFlag(Flag.N) != GetFlag(Flag.V),
|
|
// GT
|
|
0b1100 => (GetFlag(Flag.Z) == 0) && (GetFlag(Flag.N) == GetFlag(Flag.V)),
|
|
// LE
|
|
0b1101 => (GetFlag(Flag.Z) == 1) || (GetFlag(Flag.N) != GetFlag(Flag.V)),
|
|
// AL
|
|
0b1110 => true,
|
|
// NV
|
|
0b1111 => false,
|
|
// should never happen
|
|
_ => throw new Exception($"Iris.CPU.CPU_Core: Wrong condition code {cond}"),
|
|
};
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static UInt32 Not(UInt32 flag)
|
|
{
|
|
return flag ^ 1;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static UInt32 CarryFrom(UInt64 result)
|
|
{
|
|
return (result > 0xffff_ffff) ? 1u : 0u;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static UInt32 BorrowFrom(UInt64 result)
|
|
{
|
|
return (result >= 0x8000_0000_0000_0000) ? 1u : 0u;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static UInt32 OverflowFrom_Addition(UInt32 leftOperand, UInt32 rightOperand, UInt32 result)
|
|
{
|
|
return (((leftOperand >> 31) == (rightOperand >> 31))
|
|
&& ((leftOperand >> 31) != (result >> 31))) ? 1u : 0u;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static UInt32 OverflowFrom_Subtraction(UInt32 leftOperand, UInt32 rightOperand, UInt32 result)
|
|
{
|
|
return (((leftOperand >> 31) != (rightOperand >> 31))
|
|
&& ((leftOperand >> 31) != (result >> 31))) ? 1u : 0u;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static UInt32 ArithmeticShiftRight(UInt32 value, int shiftAmount)
|
|
{
|
|
return (UInt32)((Int32)value >> shiftAmount);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal static UInt32 SignExtend(UInt32 value, int size)
|
|
{
|
|
return value | ~((value & (1u << (size - 1))) - 1);
|
|
}
|
|
|
|
internal static UInt64 ComputeMultiplicationCycleCount(UInt32 leftMultiplier, UInt32 rightMultiplier)
|
|
{
|
|
static UInt64 ComputeMultiplierCycleCount(UInt32 multiplier)
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
static bool CheckMultiplierAgainstMask(UInt32 multiplier, UInt32 mask)
|
|
{
|
|
UInt32 masked = multiplier & mask;
|
|
return (masked == 0) || (masked == mask);
|
|
}
|
|
|
|
if (CheckMultiplierAgainstMask(multiplier, 0xffff_ff00))
|
|
return 1;
|
|
else if (CheckMultiplierAgainstMask(multiplier, 0xffff_0000))
|
|
return 2;
|
|
else if (CheckMultiplierAgainstMask(multiplier, 0xff00_0000))
|
|
return 3;
|
|
else
|
|
return 4;
|
|
}
|
|
|
|
return Math.Max(ComputeMultiplierCycleCount(leftMultiplier), ComputeMultiplierCycleCount(rightMultiplier));
|
|
}
|
|
}
|
|
}
|