-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathRecompilerDifferentialResult.cs
More file actions
90 lines (80 loc) · 3.84 KB
/
Copy pathRecompilerDifferentialResult.cs
File metadata and controls
90 lines (80 loc) · 3.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
using PSXRecomp.Architecture;
using PSXRecomp.Core.Cpu;
namespace PSXRecomp.Core.Recompiler;
/// <summary>
/// The result of running one fixture through both executors and comparing their
/// state snapshots.
/// </summary>
[Domain]
public sealed record RecompilerDifferentialResult(
RecompilerDifferentialFixture Fixture,
RecompilerExecutionResult Reference,
RecompilerExecutionResult Actual,
RecompilerStateDiffResult? Diff)
{
/// <summary>True when both executors completed and a state comparison exists.</summary>
public bool BothCompleted =>
Reference.Status == RecompilerExecutionStatus.Completed &&
Actual.Status == RecompilerExecutionStatus.Completed &&
Diff is not null;
/// <summary>True when both completed and the state snapshots match.</summary>
public bool IsMatch => BothCompleted && Diff!.IsMatch;
/// <summary>
/// True when both completed and the comparison was inconclusive: the executors
/// agree up to the bounded budget cut and differ only on the fields the cut
/// leaves mid-iteration, so neither a match nor a real divergence was proven
/// (Issue #304).
/// </summary>
public bool IsBudgetInconclusive => BothCompleted && Diff!.IsBudgetInconclusive;
}
/// <summary>
/// Orchestrates a single differential run: executes the fixture on the reference
/// (interpreter) executor and the actual (recompiled) executor, then compares
/// their state snapshots. Pure orchestration — it does not itself perform
/// host compiles, file I/O or process control.
/// </summary>
[Domain]
public static class RecompilerDifferentialRunner
{
public static RecompilerDifferentialResult Run(
RecompilerDifferentialFixture fixture,
IRecompilerExecutor reference,
IRecompilerExecutor actual)
{
ArgumentNullException.ThrowIfNull(fixture);
ArgumentNullException.ThrowIfNull(reference);
ArgumentNullException.ThrowIfNull(actual);
var referenceResult = reference.Execute(fixture);
var actualResult = actual.Execute(fixture);
// The runner is the one caller that can supply both facts a pair of
// snapshots alone cannot prove (CodeRabbit findings on #305): it reads the
// fixture's own author-asserted BudgetsAreShared fact — StepBudget (host
// blocks) and ReferenceStepBudget (guest instructions) count different
// units, so equal numbers alone never prove the same work counter — and it
// rebuilds the lowered program's authoritative static block-entry PCs.
RecompilerStateDiffResult? diff = referenceResult.Snapshot is not null && actualResult.Snapshot is not null
? RecompilerStateDiff.Compare(
referenceResult.Snapshot,
actualResult.Snapshot,
budgetsAreShared: fixture.BudgetsAreShared,
staticBlockEntryPcs: StaticBlockEntryPcs(fixture))
: null;
return new RecompilerDifferentialResult(fixture, referenceResult, actualResult, diff);
}
/// <summary>
/// The lowered program's static block-entry PCs for the fixture's instructions —
/// the authoritative projection target for <see cref="RecompilerStateDiff"/>'s
/// budget-tail check, independent of whichever PCs a given host run happened to
/// observe.
/// </summary>
private static IReadOnlySet<uint> StaticBlockEntryPcs(RecompilerDifferentialFixture fixture)
{
var instructions = new List<(R3000aInstruction Instruction, uint EntryPc)>(fixture.Instructions.Count);
for (var i = 0; i < fixture.Instructions.Count; i++)
{
instructions.Add((R3000aDecoder.Decode(fixture.Instructions[i]), fixture.PcOfInstruction(i)));
}
var program = MipsToIrLowerer.LowerProgram(instructions);
return program.Blocks.Select(block => block.EntryPc).ToHashSet();
}
}