Skip to content

Commit 077123d

Browse files
refactor: Optimized & improved DosExecResult and DosFileOperationResult. (#2181)
* refactor: Optimized & improved DosExecResult and DosFileOperationResult. - DosExecResult implements IEquatable<T> interface. - DosExecResult added static cache for storing a subset of common error results. - DosFileOperationResult implements IEquatable<T> interface. - DosFileOperationResult added static cache for storing a subset of common error results and NoValue() result. - DosFileOperationResult class is now sealed (was not possible to inherit before anyways). * refactor: Optimized DosFileOperationResult.Value16 method. - Cache the last 16-bit result for repeated results (useful for chained block read results). - Also fixed potential thread safety issue in DosExecResult.Fail and DosFileOperationResult.Error methods introduced by previous commit.
1 parent 8e3b06e commit 077123d

2 files changed

Lines changed: 103 additions & 14 deletions

File tree

src/Spice86.Core/Emulator/OperatingSystem/Structures/DosExecResult.cs

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
namespace Spice86.Core.Emulator.OperatingSystem.Structures;
22

33
using Spice86.Core.Emulator.OperatingSystem.Enums;
4-
using Spice86.Shared.Emulator.Errors;
54

65
/// <summary>
76
/// Result of an INT 21h EXEC operation.
87
/// </summary>
9-
public sealed class DosExecResult {
8+
public sealed class DosExecResult : IEquatable<DosExecResult?> {
9+
// Cache the most common error codes into a fast lookup array.
10+
private const int ErrorCacheLength = 20;
11+
private static readonly DosExecResult?[] s_errorCache = new DosExecResult[ErrorCacheLength];
12+
1013
private DosExecResult(bool success, DosErrorCode errorCode,
1114
ushort cs, ushort ip, ushort ss, ushort sp, ushort? ax = null, ushort? dx = null) {
1215
Success = success;
@@ -34,14 +37,49 @@ private DosExecResult(bool success, DosErrorCode errorCode,
3437
/// </summary>
3538
public ushort? DX { get; }
3639

37-
public static DosExecResult Fail(DosErrorCode code) => new(false, code, 0, 0, 0, 0);
40+
public static DosExecResult Fail(DosErrorCode code) {
41+
// Use cache for common error codes.
42+
if ((int)code is >= 0 and < ErrorCacheLength) {
43+
DosExecResult? errorCacheEntry = s_errorCache[(int)code];
44+
if (errorCacheEntry is null) {
45+
errorCacheEntry = NewErrorResult(code);
46+
s_errorCache[(int)code] = errorCacheEntry;
47+
}
48+
return errorCacheEntry;
49+
}
50+
51+
return NewErrorResult(code);
52+
53+
static DosExecResult NewErrorResult(DosErrorCode errorCode)
54+
=> new(success: false, errorCode: errorCode, cs: 0, ip: 0, ss: 0, sp: 0);
55+
}
3856

3957
public static DosExecResult SuccessExecute(ushort cs, ushort ip, ushort ss, ushort sp)
40-
=> new(true, DosErrorCode.NoError, cs, ip, ss, sp);
58+
=> new(success: true, DosErrorCode.NoError, cs, ip, ss, sp);
4159

4260
public static DosExecResult SuccessLoadOnly(ushort cs, ushort ip, ushort ss, ushort sp)
43-
=> new(true, DosErrorCode.NoError, cs, ip, ss, sp);
61+
=> new(success: true, DosErrorCode.NoError, cs, ip, ss, sp);
4462

4563
public static DosExecResult SuccessLoadOverlay()
46-
=> new(true, DosErrorCode.NoError, 0, 0, 0, 0, ax: 0, dx: 0);
64+
=> new(success: true, DosErrorCode.NoError, cs: 0, ip: 0, ss: 0, sp: 0, ax: 0, dx: 0);
65+
66+
public override bool Equals(object? obj) {
67+
return Equals(obj as DosExecResult);
68+
}
69+
70+
public bool Equals(DosExecResult? other) {
71+
return other is not null &&
72+
Success == other.Success &&
73+
ErrorCode == other.ErrorCode &&
74+
InitialCS == other.InitialCS &&
75+
InitialIP == other.InitialIP &&
76+
InitialSS == other.InitialSS &&
77+
InitialSP == other.InitialSP &&
78+
AX == other.AX &&
79+
DX == other.DX;
80+
}
81+
82+
public override int GetHashCode() {
83+
return HashCode.Combine(Success, ErrorCode, InitialCS, InitialIP, InitialSS, InitialSP, AX, DX);
84+
}
4785
}

src/Spice86.Core/Emulator/OperatingSystem/Structures/DosFileOperationResult.cs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@
22

33
using Spice86.Core.Emulator.OperatingSystem.Enums;
44

5+
using System.Diagnostics;
6+
57
/// <summary>
68
/// Represents the result of a DOS file operation, which could be an error or a value.
79
/// </summary>
8-
public class DosFileOperationResult {
9-
private readonly bool _error;
10+
public sealed class DosFileOperationResult : IEquatable<DosFileOperationResult?> {
11+
// Cache the most common error codes into a fast lookup array.
12+
private const int ErrorCacheLength = 20;
13+
private static readonly DosFileOperationResult?[] s_errorCache = new DosFileOperationResult[ErrorCacheLength];
14+
15+
// Cache the common "no value" result to avoid extra allocations.
16+
private static readonly DosFileOperationResult s_noValue = new(error: false, valueIsUint32: false, value: null);
17+
18+
// Cache the last UInt16 result (no error, reference count zero).
19+
private static DosFileOperationResult? s_lastResult16;
20+
1021
private readonly uint? _value;
22+
private readonly bool _error;
1123
private readonly bool _valueIsUint32;
1224
private readonly byte _refCount;
1325

@@ -33,15 +45,28 @@ private DosFileOperationResult(bool error, bool valueIsUint32, uint? value, byte
3345
/// <param name="errorCode">The error code.</param>
3446
/// <returns>A new instance of the class indicating an error.</returns>
3547
public static DosFileOperationResult Error(DosErrorCode errorCode) {
36-
return new DosFileOperationResult(true, false, (uint?)errorCode);
48+
// Use cache for common error codes.
49+
if ((int)errorCode is >= 0 and < ErrorCacheLength) {
50+
DosFileOperationResult? errorCacheEntry = s_errorCache[(int)errorCode];
51+
if (errorCacheEntry is null) {
52+
errorCacheEntry = NewErrorResult(errorCode);
53+
s_errorCache[(int)errorCode] = errorCacheEntry;
54+
}
55+
return errorCacheEntry;
56+
}
57+
58+
return NewErrorResult(errorCode);
59+
60+
static DosFileOperationResult NewErrorResult(DosErrorCode errorCode)
61+
=> new(error: true, valueIsUint32: false, value: (uint?)errorCode);
3762
}
3863

3964
/// <summary>
4065
/// Returns a new instance of the class indicating no value.
4166
/// </summary>
4267
/// <returns>A new instance of the class indicating no value.</returns>
4368
public static DosFileOperationResult NoValue() {
44-
return new DosFileOperationResult(false, false, null);
69+
return s_noValue;
4570
}
4671

4772
/// <summary>
@@ -50,7 +75,17 @@ public static DosFileOperationResult NoValue() {
5075
/// <param name="value">The 16-bit value.</param>
5176
/// <returns>A new instance of the class with a 16-bit value.</returns>
5277
public static DosFileOperationResult Value16(ushort value) {
53-
return new DosFileOperationResult(false, false, value);
78+
// Use last cached 16-bit result if possible.
79+
DosFileOperationResult? lastResult = s_lastResult16;
80+
if (lastResult?.Value == value) {
81+
Debug.Assert(!lastResult.IsError);
82+
Debug.Assert(!lastResult.IsValueIsUint32);
83+
return lastResult;
84+
}
85+
86+
lastResult = new DosFileOperationResult(error: false, valueIsUint32: false, value);
87+
s_lastResult16 = lastResult;
88+
return lastResult;
5489
}
5590

5691
/// <summary>
@@ -59,7 +94,7 @@ public static DosFileOperationResult Value16(ushort value) {
5994
/// <param name="value">The 32-bit value.</param>
6095
/// <returns>A new instance of the class with a 32-bit value.</returns>
6196
public static DosFileOperationResult Value32(uint value) {
62-
return new DosFileOperationResult(false, true, value);
97+
return new DosFileOperationResult(error: false, valueIsUint32: true, value);
6398
}
6499

65100
/// <summary>
@@ -68,7 +103,7 @@ public static DosFileOperationResult Value32(uint value) {
68103
/// <param name="refCount">The reference count from the System File Table.</param>
69104
/// <returns>A new instance of the class with a reference count.</returns>
70105
public static DosFileOperationResult NoValueWithRefCount(byte refCount) {
71-
return new DosFileOperationResult(false, false, null, refCount);
106+
return new DosFileOperationResult(error: false, valueIsUint32: false, value: null, refCount);
72107
}
73108

74109
/// <summary>
@@ -90,4 +125,20 @@ public static DosFileOperationResult NoValueWithRefCount(byte refCount) {
90125
/// The number of handles associated with the file or device after a successful <see cref="DosFileManager.CloseFileOrDevice(ushort)"/> operation. Defaults to 0 for other DOS operations.
91126
/// </summary>
92127
public byte RefCount => _refCount;
93-
}
128+
129+
public override bool Equals(object? obj) {
130+
return Equals(obj as DosFileOperationResult);
131+
}
132+
133+
public bool Equals(DosFileOperationResult? other) {
134+
return other is not null &&
135+
Value == other.Value &&
136+
IsError == other.IsError &&
137+
IsValueIsUint32 == other.IsValueIsUint32 &&
138+
RefCount == other.RefCount;
139+
}
140+
141+
public override int GetHashCode() {
142+
return HashCode.Combine(Value, IsError, IsValueIsUint32, RefCount);
143+
}
144+
}

0 commit comments

Comments
 (0)