Skip to content

Commit

Permalink
Merge pull request #575 from mbbsemu/fistp-fixes
Browse files Browse the repository at this point in the history
FISTP Fixes + CPU Exception Handling
  • Loading branch information
paladine authored Jul 18, 2023
2 parents 8630512 + c0bcc3d commit 2d2c6f6
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 46 deletions.
93 changes: 88 additions & 5 deletions MBBSEmu.Tests/CPU/FISTP_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class FISTP_Tests : CpuTestBase
[InlineData((double)31337, 31337, 0)]
[InlineData(-0.0d, 0, 0)]
[InlineData(double.NaN, 0, 1)]
public void FISTP_Test_M16(double ST0Value, short expectedvalue, ushort expectedControlWord)
public void FISTP_Test_M16_DS(double ST0Value, short expectedvalue, ushort expectedControlWord)
{
Reset();

Expand All @@ -44,7 +44,7 @@ public void FISTP_Test_M16(double ST0Value, short expectedvalue, ushort expected
[InlineData((double)(int.MinValue), int.MinValue, 0)]
[InlineData(-0.0d, 0, 0)]
[InlineData(double.NaN, 0, 1)]
public void FISTP_Test_M32(double ST0Value, int expectedvalue, ushort expectedControlWord)
public void FISTP_Test_M32_DS(double ST0Value, int expectedvalue, ushort expectedControlWord)
{
Reset();

Expand All @@ -65,12 +65,14 @@ public void FISTP_Test_M32(double ST0Value, int expectedvalue, ushort expectedCo
Assert.Equal(expectedControlWord, mbbsEmuCpuRegisters.Fpu.ControlWord & FPU_CONTROLWORD_EXCEPTION_MASK);
}

/// <summary>
/// i386/i486 Systems did not support 64-bit integer values, so we test for overflow here
/// </summary>
[Theory]
//[InlineData((double)(long.MaxValue), long.MaxValue, 0)] //Overflows -- need to figure out why
[InlineData((double)(long.MinValue), long.MinValue, 0)]
[InlineData((double)(long.MinValue), 0, 1)]
[InlineData(-0.0d, 0, 0)]
[InlineData(double.NaN, 0, 1)]
public void FISTP_Test_M64(double ST0Value, long expectedvalue, ushort expectedControlWord)
public void FISTP_Test_M64_DS(double ST0Value, long expectedvalue, ushort expectedControlWord)
{
Reset();

Expand All @@ -90,5 +92,86 @@ public void FISTP_Test_M64(double ST0Value, long expectedvalue, ushort expectedC
Assert.Equal(0, mbbsEmuCpuRegisters.Fpu.GetStackTop());
Assert.Equal(expectedControlWord, mbbsEmuCpuRegisters.Fpu.ControlWord & FPU_CONTROLWORD_EXCEPTION_MASK);
}

[Theory]
[InlineData((double)(short.MaxValue), short.MaxValue, 0)]
[InlineData((double)(short.MaxValue + 1), 0, 1)]
[InlineData((double)(short.MinValue), short.MinValue, 0)]
[InlineData((double)(short.MinValue - 1), 0, 1)]
[InlineData((double)31337, 31337, 0)]
[InlineData(-0.0d, 0, 0)]
[InlineData(double.NaN, 0, 1)]
public void FISTP_Test_M16_SS(double ST0Value, short expectedvalue, ushort expectedControlWord)
{
Reset();

mbbsEmuCpuRegisters.SS = 0;
mbbsEmuProtectedModeMemoryCore.AddSegment(0);
mbbsEmuCpuRegisters.Fpu.SetStackTop(1);
mbbsEmuCpuRegisters.Fpu.ControlWord = 0;
mbbsEmuCpuCore.FpuStack[1] = ST0Value;

var instructions = new Assembler(16);
instructions.fistp(__word_ptr.ss[0]);
CreateCodeSegment(instructions);

mbbsEmuCpuCore.Tick();

Assert.Equal(expectedvalue, (short)mbbsEmuMemoryCore.GetWord(mbbsEmuCpuRegisters.SS, 0));
Assert.Equal(0, mbbsEmuCpuRegisters.Fpu.GetStackTop());
Assert.Equal(expectedControlWord, mbbsEmuCpuRegisters.Fpu.ControlWord & FPU_CONTROLWORD_EXCEPTION_MASK);
}

[Theory]
[InlineData((double)(int.MaxValue), int.MaxValue, 0)]
[InlineData(31337.1d, 31337, 0)]
[InlineData((double)(int.MinValue), int.MinValue, 0)]
[InlineData(-0.0d, 0, 0)]
[InlineData(double.NaN, 0, 1)]
public void FISTP_Test_M32_SS(double ST0Value, int expectedvalue, ushort expectedControlWord)
{
Reset();

mbbsEmuCpuRegisters.SS = 0;
mbbsEmuProtectedModeMemoryCore.AddSegment(0);
mbbsEmuCpuRegisters.Fpu.SetStackTop(1);
mbbsEmuCpuRegisters.Fpu.ControlWord = 0;
mbbsEmuCpuCore.FpuStack[1] = ST0Value;

var instructions = new Assembler(16);
instructions.fistp(__dword_ptr.ss[0]);
CreateCodeSegment(instructions);

mbbsEmuCpuCore.Tick();

Assert.Equal(expectedvalue, BitConverter.ToInt32(mbbsEmuMemoryCore.GetArray(mbbsEmuCpuRegisters.SS, 0, 4)));
Assert.Equal(0, mbbsEmuCpuRegisters.Fpu.GetStackTop());
Assert.Equal(expectedControlWord, mbbsEmuCpuRegisters.Fpu.ControlWord & FPU_CONTROLWORD_EXCEPTION_MASK);
}

[Theory]
[InlineData((double)(long.MinValue), 0, 1)]
[InlineData(-0.0d, 0, 0)]
[InlineData(double.NaN, 0, 1)]
public void FISTP_Test_M64_SS(double ST0Value, long expectedvalue, ushort expectedControlWord)
{
Reset();

mbbsEmuCpuRegisters.SS = 0;
mbbsEmuProtectedModeMemoryCore.AddSegment(0);
mbbsEmuCpuRegisters.Fpu.SetStackTop(1);
mbbsEmuCpuRegisters.Fpu.ControlWord = 0;
mbbsEmuCpuCore.FpuStack[1] = ST0Value;

var instructions = new Assembler(16);
instructions.fistp(__qword_ptr.ss[0]);
CreateCodeSegment(instructions);

mbbsEmuCpuCore.Tick();

Assert.Equal(expectedvalue, BitConverter.ToInt64(mbbsEmuMemoryCore.GetArray(mbbsEmuCpuRegisters.SS, 0, 8)));
Assert.Equal(0, mbbsEmuCpuRegisters.Fpu.GetStackTop());
Assert.Equal(expectedControlWord, mbbsEmuCpuRegisters.Fpu.ControlWord & FPU_CONTROLWORD_EXCEPTION_MASK);
}
}
}
97 changes: 56 additions & 41 deletions MBBSEmu/CPU/CPUCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ public void Tick()
*/
var CPUBreakpoints = new List<FarPtr>
{
new(0x1, 0xEE4)
//Below would break the CPU at the CS:IP of 0x1:0x100
//new(0x1, 0x100)
};

/* ---------------------------
Expand All @@ -335,11 +336,12 @@ public void Tick()
*/
var CPUDebugRanges = new List<List<FarPtr>>
{
new()
{
new FarPtr(0x1, 0xEE4),
new FarPtr(0x1, 0xF07)
}
//Below would log debug information for the range of CS:IP 0x1:0x100 to 0x1:0x200
//new()
//{
// new FarPtr(0x1, 0x100),
// new FarPtr(0x1, 0x200)
//}
};

//Set this value to TRUE if you want the CPU to break after each instruction
Expand All @@ -348,7 +350,7 @@ public void Tick()
//Evaluate Breakpoints
if (CPUBreakpoints.Contains(_currentInstructionPointer))
Debugger.Break();

//Evaluate Debug Ranges
if (CPUDebugRanges.Any(x => x[0] <= _currentInstructionPointer && x[1] >= _currentInstructionPointer))
{
Expand All @@ -359,9 +361,15 @@ public void Tick()
{
_showDebug = false;
}

_currentInstructionPointer.Offset = Registers.IP;
_currentInstructionPointer.Segment = Registers.CS;
#endif
_currentInstruction = Memory.GetInstruction(Registers.CS, Registers.IP);
_currentOperationSize = GetCurrentOperationSize();

InstructionCounter++;

//Jump Table
Switch:
switch (_currentInstruction.Mnemonic)
Expand Down Expand Up @@ -755,11 +763,10 @@ public void Tick()
{
_logger.InfoRegisters(this);

if(CPUDebugBreak)
if (CPUDebugBreak)
Debugger.Break();
}
#endif

}

/// <summary>
Expand Down Expand Up @@ -1096,24 +1103,24 @@ private ushort GetOperandOffset(OpKind opKind)
}

case Register.SI:
{
{
#if DEBUG
if (_currentInstruction.MemoryIndex != Register.None)
throw new Exception($"Unknown GetOperandOffset MemoryBase: {_currentInstruction}");
if (_currentInstruction.MemoryIndex != Register.None)
throw new Exception($"Unknown GetOperandOffset MemoryBase: {_currentInstruction}");
#endif
result += Registers.SI;
break;
result += Registers.SI;
break;
}

case Register.DI:
{
{
#if DEBUG
if (_currentInstruction.MemoryIndex != Register.None)
throw new Exception($"Unknown GetOperandOffset MemoryBase: {_currentInstruction}");
if (_currentInstruction.MemoryIndex != Register.None)
throw new Exception($"Unknown GetOperandOffset MemoryBase: {_currentInstruction}");
#endif
result += Registers.DI;
break;
}
result += Registers.DI;
break;
}

default:
throw new Exception($"Unknown GetOperandOffset MemoryBase: {_currentInstruction}");
Expand Down Expand Up @@ -3354,55 +3361,63 @@ private void Op_Fistp()
{
var offset = GetOperandOffset(_currentInstruction.Op0Kind);

var valueToSave = FpuStack[Registers.Fpu.GetStackTop()];
//Get Rounded Value from FPU and Pop Stack
var valueFromFpu = FpuStack[Registers.Fpu.GetStackTop()];
Registers.Fpu.PopStackTop();
if (double.IsNaN(valueFromFpu))
{
Registers.Fpu.ControlWord = Registers.Fpu.ControlWord.SetFlag((ushort)EnumFpuControlWordFlags.InvalidOperation);
return;
}

if (double.IsNaN(valueToSave))
//Round Value from FPU
valueFromFpu = Math.Round(valueFromFpu, MidpointRounding.AwayFromZero);

//Safely Cast it to 32-bit Signed Integer
int valueToSave;
try
{
valueToSave = Convert.ToInt32(valueFromFpu);
}
catch
{
_logger?.Error($"Unable to cast value from FPU to 32-Bit Signed Integer: {valueFromFpu}");

Registers.Fpu.ControlWord = Registers.Fpu.ControlWord.SetFlag((ushort)EnumFpuControlWordFlags.InvalidOperation);
return;
}

//Determine Destination Segment
var destinationSegment = Registers.GetValue(_currentInstruction.MemorySegment);

switch (_currentInstruction.MemorySize)
{
case MemorySize.Int16:
{
if (valueToSave > short.MaxValue || valueToSave < short.MinValue)
if (valueFromFpu is > short.MaxValue or < short.MinValue)
{
Registers.Fpu.ControlWord = Registers.Fpu.ControlWord.SetFlag((ushort)EnumFpuControlWordFlags.InvalidOperation);
break;
}

Memory.SetWord(Registers.DS, offset, (ushort)Convert.ToInt16(valueToSave));
Memory.SetWord(destinationSegment, offset, (ushort)Convert.ToInt16(valueToSave));
break;
}
case MemorySize.Int32:
case MemorySize.Int64: //i386/i486 only support 32-Bit Integers, disassembler might still emit an operation as 64-bit for some reason
{
if (valueToSave > uint.MaxValue || valueToSave < int.MinValue)
{
Registers.Fpu.ControlWord = Registers.Fpu.ControlWord.SetFlag((ushort)EnumFpuControlWordFlags.InvalidOperation);
break;
}

Memory.SetArray(Registers.DS, offset, BitConverter.GetBytes(Convert.ToInt32(valueToSave)));
break;
}
case MemorySize.Int64:
{
if (valueToSave > long.MaxValue || valueToSave < long.MinValue)
if (valueFromFpu is > int.MaxValue or < int.MinValue)
{
Registers.Fpu.ControlWord = Registers.Fpu.ControlWord.SetFlag((ushort)EnumFpuControlWordFlags.InvalidOperation);
break;
}

Memory.SetArray(Registers.DS, offset, BitConverter.GetBytes(Convert.ToInt64(valueToSave)));
Memory.SetArray(destinationSegment, offset, BitConverter.GetBytes(valueToSave));
break;
}
default:
throw new Exception($"Unknown Memory Size: {_currentInstruction.MemorySize}");
throw new Exception($"Unsupported Memory Size: {_currentInstruction.MemorySize}");
}


}

/// <summary>
Expand Down

0 comments on commit 2d2c6f6

Please sign in to comment.