mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-12 09:45:04 -04:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 352c4ca02e | |||
| cc286f3060 | |||
| b034f7c4cc | |||
| a51fc25daf | |||
| f41d1f1393 | |||
| 6242926af9 | |||
| 88643d6884 | |||
| 5381c4ecf2 | |||
| e5258af5ec | |||
| c57ed5f69b |
@@ -1,19 +1,6 @@
|
|||||||
<img width="4190" height="1232" alt="wiicomplogofinalfinalfinalev2MADEBY_INKWRECK_plzcredit" src="https://github.com/user-attachments/assets/df7a3f2e-5336-479a-b4c0-968dd578726d" />
|
|
||||||
|
|
||||||
# WiiCompiled
|
# WiiCompiled
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="Windows 10 / 11, x64" src="https://img.shields.io/badge/Windows-10%20%2F%2011%20%C2%B7%20x64-0078D4"></a>
|
|
||||||
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="Linux, x64 / ARM64" src="https://img.shields.io/badge/Linux-x64%20%2F%20ARM64-FCC624?logo=linux&logoColor=white"></a>
|
|
||||||
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="macOS 14+, Apple Silicon" src="https://img.shields.io/badge/macOS-14%2B%20%C2%B7%20Apple%20Silicon-0A84FF?logo=apple&logoColor=white"></a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<a href="#building-from-source"><img alt="PowerPC static recompilation" src="https://img.shields.io/badge/PowerPC-static%20recompilation-FF9F0A"></a>
|
|
||||||
<a href="#retro-rewind"><img alt="Retro Rewind supported" src="https://img.shields.io/badge/Retro%20Rewind-supported-FF375F"></a>
|
|
||||||
<a href="https://github.com/TeamWheelWizard/WheelWizard/releases"><img alt="Install with Wheel Wizard" src="https://img.shields.io/badge/install%20with-Wheel%20Wizard-8B5CF6"></a>
|
|
||||||
<a href="LICENSE"><img alt="License: GPLv3" src="https://img.shields.io/badge/license-GPLv3-2EA44F?logo=gnu&logoColor=white"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
A native PC port of Mario Kart Wii, made with static recompilation.
|
A native PC port of Mario Kart Wii, made with static recompilation.
|
||||||
|
|
||||||
There's no emulator in the loop, no interpreter, no JIT, no PowerPC
|
There's no emulator in the loop, no interpreter, no JIT, no PowerPC
|
||||||
@@ -228,7 +215,7 @@ AI coding tools were used during development of this project.
|
|||||||
All translated output is verified against real hardware behavior and most importantly, physics accuracy is proven synced across Wii, Dolphin, and WiiCompiled (see FAQ).
|
All translated output is verified against real hardware behavior and most importantly, physics accuracy is proven synced across Wii, Dolphin, and WiiCompiled (see FAQ).
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
- **inkwreck** - making the logo
|
|
||||||
- **[aurora](https://github.com/encounter/aurora)** - the GX rendering/windowing backend this
|
- **[aurora](https://github.com/encounter/aurora)** - the GX rendering/windowing backend this
|
||||||
project's whole graphics layer sits on. MIT licensed.
|
project's whole graphics layer sits on. MIT licensed.
|
||||||
- **[Dawn](https://dawn.googlesource.com/dawn)** - Google's WebGPU implementation, powering
|
- **[Dawn](https://dawn.googlesource.com/dawn)** - Google's WebGPU implementation, powering
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Due to legal requirements, no proprietary Nintendo assets or code are included i
|
|||||||
3. **Retro Rewind Distribution** *(Skip if only building WiiCompiled)*:
|
3. **Retro Rewind Distribution** *(Skip if only building WiiCompiled)*:
|
||||||
- Download the [Retro Rewind](https://wiki.tockdom.com/wiki/Retro_Rewind) release package. You will need the `RetroRewind6` folder (which contains `Binaries/Code.pul`).
|
- Download the [Retro Rewind](https://wiki.tockdom.com/wiki/Retro_Rewind) release package. You will need the `RetroRewind6` folder (which contains `Binaries/Code.pul`).
|
||||||
4. **Retro-WFC Payload** *(Skip if only building WiiCompiled or building offline)*:
|
4. **Retro-WFC Payload** *(Skip if only building WiiCompiled or building offline)*:
|
||||||
- Required for online multiplayer on Retro Rewind. Downloaded during setup from `https://rwfc.net/api/wfc/payload?g=RMCPD00`.
|
- Required for online multiplayer on Retro Rewind. Downloaded during setup from `http://nas.play.rwfc.net/payload?g=RMCPD00`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ Launcher/local-build-macos.command \
|
|||||||
```bash
|
```bash
|
||||||
# 1. Download Retro-WFC payload into a staging directory:
|
# 1. Download Retro-WFC payload into a staging directory:
|
||||||
mkdir -p build/retro-wfc/binary
|
mkdir -p build/retro-wfc/binary
|
||||||
curl -fsSL --retry 3 "https://rwfc.net/api/wfc/payload?g=RMCPD00" \
|
curl -fsSL --retry 3 "http://nas.play.rwfc.net/payload?g=RMCPD00" \
|
||||||
-o build/retro-wfc/binary/payload.RMCPD00.bin
|
-o build/retro-wfc/binary/payload.RMCPD00.bin
|
||||||
|
|
||||||
# 2. Run the automated build with the payload directory:
|
# 2. Run the automated build with the payload directory:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -1891,7 +1891,6 @@ int RunTranslateModCore(string[] argsTail, string? outputDirectoryOverride)
|
|||||||
overlayBuild,
|
overlayBuild,
|
||||||
continuationPlan,
|
continuationPlan,
|
||||||
retroWfcResolvedExecutableHooks,
|
retroWfcResolvedExecutableHooks,
|
||||||
patchPlan,
|
|
||||||
kamekFunctionStarts,
|
kamekFunctionStarts,
|
||||||
moduleLinkBase,
|
moduleLinkBase,
|
||||||
selected.CodeSize,
|
selected.CodeSize,
|
||||||
@@ -2232,7 +2231,6 @@ int EmitModCpp(
|
|||||||
OverlayBuildResult overlayBuild,
|
OverlayBuildResult overlayBuild,
|
||||||
ContinuationPlan continuationPlan,
|
ContinuationPlan continuationPlan,
|
||||||
IReadOnlyCollection<RetroWfcExecutableHookPlan>? retroWfcExecutableHooks,
|
IReadOnlyCollection<RetroWfcExecutableHookPlan>? retroWfcExecutableHooks,
|
||||||
KamekPatchPlan patchPlan,
|
|
||||||
IReadOnlyList<ModFunctionStart> kamekFunctionStarts,
|
IReadOnlyList<ModFunctionStart> kamekFunctionStarts,
|
||||||
uint moduleLinkBase,
|
uint moduleLinkBase,
|
||||||
uint moduleLinkedCodeSize,
|
uint moduleLinkedCodeSize,
|
||||||
@@ -2274,25 +2272,14 @@ int EmitModCpp(
|
|||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
var queuedContinuationAddresses = continuationPlan.Entries.Select(e => e.Address).ToHashSet();
|
var queuedContinuationAddresses = continuationPlan.Entries.Select(e => e.Address).ToHashSet();
|
||||||
var discoveredContinuationQueue = new Queue<ContinuationEntry>();
|
var discoveredContinuationQueue = new Queue<ContinuationEntry>();
|
||||||
var hookLrBases = new List<(uint TargetAddress, uint ContinuationAddress)>();
|
var linkedHookLrBasesByTarget = retroWfcExecutableHooks is null
|
||||||
if (retroWfcExecutableHooks is not null)
|
? new Dictionary<uint, uint[]>()
|
||||||
{
|
: retroWfcExecutableHooks
|
||||||
hookLrBases.AddRange(
|
.Where(h => h.TargetAddress.HasValue && RetroWfcHookSetsLinkRegister(h))
|
||||||
retroWfcExecutableHooks
|
.GroupBy(h => h.TargetAddress!.Value)
|
||||||
.Where(h => h.TargetAddress.HasValue && RetroWfcHookSetsLinkRegister(h))
|
.ToDictionary(
|
||||||
.Select(h => (h.TargetAddress!.Value, h.ContinuationAddress)));
|
g => g.Key,
|
||||||
}
|
g => g.Select(h => h.ContinuationAddress).Distinct().ToArray());
|
||||||
foreach (var patch in patchPlan.ExecutablePatches.Where(p => p.CommandId == KamekCommandId.BranchLink && p.Arguments.Count > 0))
|
|
||||||
{
|
|
||||||
var target = KamekAddress.Resolve(patch.Arguments[0], patchPlan.ModuleGuestBase);
|
|
||||||
hookLrBases.Add((target, checked(patch.CommandAddress + 4u)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var linkedHookLrBasesByTarget = hookLrBases
|
|
||||||
.GroupBy(h => h.TargetAddress)
|
|
||||||
.ToDictionary(
|
|
||||||
g => g.Key,
|
|
||||||
g => g.Select(h => h.ContinuationAddress).Distinct().ToArray());
|
|
||||||
var lrContinuationCallTargets = linkedHookLrBasesByTarget.Keys.ToHashSet();
|
var lrContinuationCallTargets = linkedHookLrBasesByTarget.Keys.ToHashSet();
|
||||||
var linkedCallFallthroughLrOverrides = retroWfcExecutableHooks is null
|
var linkedCallFallthroughLrOverrides = retroWfcExecutableHooks is null
|
||||||
? new Dictionary<uint, uint>()
|
? new Dictionary<uint, uint>()
|
||||||
@@ -2763,8 +2750,123 @@ IEnumerable<uint> DirectModuleTargets(FunctionTranslationResult result, uint mod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<int> DiscoverLrRelativeIndirectJumpOffsets(FunctionTranslationResult result) =>
|
IEnumerable<int> DiscoverLrRelativeIndirectJumpOffsets(FunctionTranslationResult result)
|
||||||
ContinuationPlanner.DiscoverLrRelativeIndirectJumpOffsets(result.Instructions);
|
{
|
||||||
|
var lrOffsets = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
int? ctrOffset = null;
|
||||||
|
|
||||||
|
foreach (var instruction in result.Instructions)
|
||||||
|
{
|
||||||
|
var mnemonic = instruction.Mnemonic.ToLowerInvariant();
|
||||||
|
if (mnemonic == "mflr" && TryGetInstructionReg(instruction, 0, out var lrDest))
|
||||||
|
{
|
||||||
|
lrOffsets[lrDest] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mnemonic == "mr" || mnemonic == "or") &&
|
||||||
|
TryGetInstructionReg(instruction, 0, out var moveDest) &&
|
||||||
|
TryGetInstructionReg(instruction, 1, out var moveSource) &&
|
||||||
|
(mnemonic == "mr" ||
|
||||||
|
(instruction.Operands.Count >= 3 &&
|
||||||
|
instruction.Operands[2] is PpcRegisterOperand moveSource2 &&
|
||||||
|
string.Equals(NormalizeInstructionReg(moveSource2.Name), moveSource, StringComparison.OrdinalIgnoreCase))))
|
||||||
|
{
|
||||||
|
if (lrOffsets.TryGetValue(moveSource, out var sourceOffset))
|
||||||
|
{
|
||||||
|
lrOffsets[moveDest] = sourceOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lrOffsets.Remove(moveDest);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mnemonic == "addi" &&
|
||||||
|
TryGetInstructionReg(instruction, 0, out var addDest) &&
|
||||||
|
TryGetInstructionReg(instruction, 1, out var addBase) &&
|
||||||
|
TryGetInstructionImm(instruction, 2, out var imm))
|
||||||
|
{
|
||||||
|
if (lrOffsets.TryGetValue(addBase, out var baseOffset))
|
||||||
|
{
|
||||||
|
lrOffsets[addDest] = checked(baseOffset + imm);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lrOffsets.Remove(addDest);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mnemonic == "mtctr" && TryGetInstructionReg(instruction, 0, out var ctrSource))
|
||||||
|
{
|
||||||
|
ctrOffset = lrOffsets.TryGetValue(ctrSource, out var sourceOffset) ? sourceOffset : null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mnemonic == "bctr")
|
||||||
|
{
|
||||||
|
if (ctrOffset.HasValue)
|
||||||
|
{
|
||||||
|
yield return ctrOffset.Value;
|
||||||
|
}
|
||||||
|
ctrOffset = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryInstructionWritesDest(instruction, out var dest))
|
||||||
|
{
|
||||||
|
lrOffsets.Remove(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryGetInstructionReg(PpcInstruction instruction, int index, out string register)
|
||||||
|
{
|
||||||
|
if (instruction.Operands.Count > index && instruction.Operands[index] is PpcRegisterOperand operand)
|
||||||
|
{
|
||||||
|
register = NormalizeInstructionReg(operand.Name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
register = string.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryGetInstructionImm(PpcInstruction instruction, int index, out int immediate)
|
||||||
|
{
|
||||||
|
if (instruction.Operands.Count > index && instruction.Operands[index] is PpcImmediateOperand operand)
|
||||||
|
{
|
||||||
|
immediate = operand.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
immediate = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryInstructionWritesDest(PpcInstruction instruction, out string destination)
|
||||||
|
{
|
||||||
|
destination = string.Empty;
|
||||||
|
if (instruction.Operands.Count == 0 || instruction.Operands[0] is not PpcRegisterOperand operand)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mnemonic = instruction.Mnemonic.ToLowerInvariant();
|
||||||
|
if (mnemonic.StartsWith("st", StringComparison.Ordinal) ||
|
||||||
|
mnemonic.StartsWith("b", StringComparison.Ordinal) ||
|
||||||
|
mnemonic.StartsWith("cmp", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination = NormalizeInstructionReg(operand.Name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string NormalizeInstructionReg(string register) => register.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
static bool RetroWfcHookSetsLinkRegister(RetroWfcExecutableHookPlan hook) =>
|
static bool RetroWfcHookSetsLinkRegister(RetroWfcExecutableHookPlan hook) =>
|
||||||
hook.TypeName is "call" or "branchCtrLink" ||
|
hook.TypeName is "call" or "branchCtrLink" ||
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Translator.Core.Disassembly;
|
using Translator.Core.Disassembly;
|
||||||
using Translator.Core.Parsing.Kamek;
|
using Translator.Core.Parsing.Kamek;
|
||||||
@@ -274,530 +273,4 @@ public static class ContinuationPlanner
|
|||||||
Or,
|
Or,
|
||||||
AddSigned
|
AddSigned
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<int> DiscoverLrRelativeIndirectJumpOffsets(IReadOnlyList<PpcInstruction> instructions)
|
|
||||||
{
|
|
||||||
if (instructions.Count == 0)
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var indexByAddress = new Dictionary<uint, int>(instructions.Count);
|
|
||||||
for (var i = 0; i < instructions.Count; i++)
|
|
||||||
{
|
|
||||||
indexByAddress.TryAdd(instructions[i].Address, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
var visited = new HashSet<PathState>[instructions.Count];
|
|
||||||
for (var i = 0; i < instructions.Count; i++)
|
|
||||||
{
|
|
||||||
visited[i] = new HashSet<PathState>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var seenOffsets = new HashSet<int>();
|
|
||||||
var worklist = new Queue<(int Index, PathState State)>();
|
|
||||||
const int MaxStatesPerInstruction = 16;
|
|
||||||
|
|
||||||
void Enqueue(int targetIndex, PathState stateToEnqueue)
|
|
||||||
{
|
|
||||||
worklist.Enqueue((targetIndex, stateToEnqueue));
|
|
||||||
}
|
|
||||||
|
|
||||||
int? GetFallthroughIndex(PpcInstruction instruction)
|
|
||||||
{
|
|
||||||
if (indexByAddress.TryGetValue(instruction.EndAddress, out var nextIndex))
|
|
||||||
{
|
|
||||||
return nextIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Enqueue(0, PathState.Empty);
|
|
||||||
|
|
||||||
while (worklist.Count > 0)
|
|
||||||
{
|
|
||||||
var (idx, state) = worklist.Dequeue();
|
|
||||||
if (!visited[idx].Add(state))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visited[idx].Count > MaxStatesPerInstruction)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var instruction = instructions[idx];
|
|
||||||
var mnemonic = instruction.Mnemonic.ToLowerInvariant();
|
|
||||||
var nextState = state;
|
|
||||||
|
|
||||||
if (mnemonic == "mflr" && TryGetInstructionReg(instruction, 0, out var lrDest))
|
|
||||||
{
|
|
||||||
if (lrDest == "r1")
|
|
||||||
{
|
|
||||||
nextState = nextState.WithClearedStackOffsets();
|
|
||||||
}
|
|
||||||
|
|
||||||
nextState = nextState.LrReturnOffset.HasValue
|
|
||||||
? nextState.WithLrOffset(lrDest, nextState.LrReturnOffset.Value)
|
|
||||||
: nextState.WithoutLrOffset(lrDest);
|
|
||||||
}
|
|
||||||
else if ((mnemonic == "mr" || mnemonic == "or") &&
|
|
||||||
TryGetInstructionReg(instruction, 0, out var moveDest) &&
|
|
||||||
TryGetInstructionReg(instruction, 1, out var moveSource) &&
|
|
||||||
(mnemonic == "mr" ||
|
|
||||||
(instruction.Operands.Count >= 3 &&
|
|
||||||
instruction.Operands[2] is PpcRegisterOperand moveSource2 &&
|
|
||||||
string.Equals(NormalizeInstructionReg(moveSource2.Name), moveSource, StringComparison.OrdinalIgnoreCase))))
|
|
||||||
{
|
|
||||||
if (moveDest == "r1" && moveSource != "r1")
|
|
||||||
{
|
|
||||||
nextState = nextState.WithClearedStackOffsets();
|
|
||||||
}
|
|
||||||
nextState = nextState.LrOffsets.TryGetValue(moveSource, out var sourceOffset)
|
|
||||||
? nextState.WithLrOffset(moveDest, sourceOffset)
|
|
||||||
: nextState.WithoutLrOffset(moveDest);
|
|
||||||
}
|
|
||||||
else if ((mnemonic == "addi" || mnemonic == "addic") &&
|
|
||||||
TryGetInstructionReg(instruction, 0, out var addDest) &&
|
|
||||||
TryGetInstructionReg(instruction, 1, out var addBase) &&
|
|
||||||
TryGetInstructionImm(instruction, 2, out var imm))
|
|
||||||
{
|
|
||||||
if (addDest == "r1")
|
|
||||||
{
|
|
||||||
nextState = addBase == "r1"
|
|
||||||
? nextState.WithSpDelta(unchecked(nextState.SpDelta + imm))
|
|
||||||
: nextState.WithClearedStackOffsets();
|
|
||||||
}
|
|
||||||
nextState = nextState.LrOffsets.TryGetValue(addBase, out var baseOffset)
|
|
||||||
? nextState.WithLrOffset(addDest, unchecked(baseOffset + imm))
|
|
||||||
: nextState.WithoutLrOffset(addDest);
|
|
||||||
}
|
|
||||||
else if (mnemonic == "mtctr" && TryGetInstructionReg(instruction, 0, out var ctrSource))
|
|
||||||
{
|
|
||||||
var newCtrOffset = nextState.LrOffsets.TryGetValue(ctrSource, out var sourceOffset) ? sourceOffset : (int?)null;
|
|
||||||
nextState = nextState.WithCtrOffset(newCtrOffset);
|
|
||||||
}
|
|
||||||
else if (mnemonic == "mtlr" && TryGetInstructionReg(instruction, 0, out var lrSource))
|
|
||||||
{
|
|
||||||
var newLrReturnOffset = nextState.LrOffsets.TryGetValue(lrSource, out var sourceOffset) ? sourceOffset : (int?)null;
|
|
||||||
nextState = nextState.WithLrReturnOffset(newLrReturnOffset);
|
|
||||||
}
|
|
||||||
else if (mnemonic == "stw" &&
|
|
||||||
TryGetInstructionReg(instruction, 0, out var storeSrc) &&
|
|
||||||
TryGetInstructionDisplacement(instruction, 1, out var storeDisp, out var storeBase, out _))
|
|
||||||
{
|
|
||||||
if (storeBase == "r1")
|
|
||||||
{
|
|
||||||
var targetSlot = nextState.SpDelta + storeDisp;
|
|
||||||
nextState = nextState.LrOffsets.TryGetValue(storeSrc, out var offset)
|
|
||||||
? nextState.WithStackOffset(targetSlot, offset)
|
|
||||||
: nextState.WithoutStackOffset(targetSlot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (mnemonic == "stwu" &&
|
|
||||||
TryGetInstructionReg(instruction, 0, out var stwuSrc) &&
|
|
||||||
TryGetInstructionDisplacement(instruction, 1, out var stwuDisp, out var stwuBase, out _))
|
|
||||||
{
|
|
||||||
if (stwuBase == "r1")
|
|
||||||
{
|
|
||||||
var targetSlot = nextState.SpDelta + stwuDisp;
|
|
||||||
nextState = nextState.LrOffsets.TryGetValue(stwuSrc, out var offset)
|
|
||||||
? nextState.WithStackOffset(targetSlot, offset)
|
|
||||||
: nextState.WithoutStackOffset(targetSlot);
|
|
||||||
nextState = nextState.WithAdjustedStackPointer(stwuDisp);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nextState = nextState.WithoutLrOffset(stwuBase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (TryGetStackStoreRange(instruction, out var storeOffset, out var storeSize, out var updatesStackPointer))
|
|
||||||
{
|
|
||||||
nextState = nextState.WithoutStackOffsetsInRange(
|
|
||||||
nextState.SpDelta + storeOffset,
|
|
||||||
storeSize);
|
|
||||||
if (updatesStackPointer)
|
|
||||||
{
|
|
||||||
nextState = nextState.WithAdjustedStackPointer(storeOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (mnemonic == "lwz" &&
|
|
||||||
TryGetInstructionReg(instruction, 0, out var loadDest) &&
|
|
||||||
TryGetInstructionDisplacement(instruction, 1, out var loadDisp, out var loadBase, out _))
|
|
||||||
{
|
|
||||||
if (loadBase == "r1")
|
|
||||||
{
|
|
||||||
var targetSlot = nextState.SpDelta + loadDisp;
|
|
||||||
var hasStackOffset = nextState.StackOffsets.TryGetValue(targetSlot, out var offset);
|
|
||||||
|
|
||||||
if (loadDest == "r1")
|
|
||||||
{
|
|
||||||
nextState = nextState.WithClearedStackOffsets();
|
|
||||||
}
|
|
||||||
|
|
||||||
nextState = hasStackOffset
|
|
||||||
? nextState.WithLrOffset(loadDest, offset)
|
|
||||||
: nextState.WithoutLrOffset(loadDest);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nextState = nextState.WithoutLrOffset(loadDest);
|
|
||||||
if (loadDest == "r1")
|
|
||||||
{
|
|
||||||
nextState = nextState.WithClearedStackOffsets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (TryInstructionWritesDest(instruction, out var destinations))
|
|
||||||
{
|
|
||||||
foreach (var dest in destinations)
|
|
||||||
{
|
|
||||||
nextState = nextState.WithoutLrOffset(dest);
|
|
||||||
if (dest == "r1")
|
|
||||||
{
|
|
||||||
nextState = nextState.WithClearedStackOffsets();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instruction.IsCall || mnemonic == "bl" || mnemonic == "blrl")
|
|
||||||
{
|
|
||||||
nextState = nextState.WithLrReturnOffset(null).WithCtrOffset(null);
|
|
||||||
for (var register = 0; register <= 12; register++)
|
|
||||||
{
|
|
||||||
if (register != 1 && register != 2)
|
|
||||||
{
|
|
||||||
nextState = nextState.WithoutLrOffset($"r{register}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mnemonic == "bctr")
|
|
||||||
{
|
|
||||||
if (state.CtrOffset.HasValue && seenOffsets.Add(state.CtrOffset.Value))
|
|
||||||
{
|
|
||||||
yield return state.CtrOffset.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextState = nextState.WithCtrOffset(null);
|
|
||||||
if (instruction.BranchTargets.Count == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isReturn = !instruction.IsCall && (instruction.IsReturn || mnemonic == "blr" || mnemonic == "bclr" ||
|
|
||||||
(mnemonic.StartsWith("b", StringComparison.Ordinal) && mnemonic.EndsWith("lr", StringComparison.Ordinal)));
|
|
||||||
if (isReturn)
|
|
||||||
{
|
|
||||||
if (state.LrReturnOffset.HasValue && state.LrReturnOffset.Value != 0 && seenOffsets.Add(state.LrReturnOffset.Value))
|
|
||||||
{
|
|
||||||
yield return state.LrReturnOffset.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instruction.IsConditionalBranch)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instruction.IsUnconditionalBranch)
|
|
||||||
{
|
|
||||||
foreach (var target in instruction.BranchTargets)
|
|
||||||
{
|
|
||||||
if (indexByAddress.TryGetValue(target, out var targetIndex))
|
|
||||||
{
|
|
||||||
Enqueue(targetIndex, nextState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (instruction.IsConditionalBranch)
|
|
||||||
{
|
|
||||||
var fallthrough = GetFallthroughIndex(instruction);
|
|
||||||
if (fallthrough.HasValue)
|
|
||||||
{
|
|
||||||
Enqueue(fallthrough.Value, nextState);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isReturn)
|
|
||||||
{
|
|
||||||
foreach (var target in instruction.BranchTargets)
|
|
||||||
{
|
|
||||||
if (indexByAddress.TryGetValue(target, out var targetIndex))
|
|
||||||
{
|
|
||||||
Enqueue(targetIndex, nextState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var fallthrough = GetFallthroughIndex(instruction);
|
|
||||||
if (fallthrough.HasValue)
|
|
||||||
{
|
|
||||||
Enqueue(fallthrough.Value, nextState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool TryGetInstructionReg(PpcInstruction instruction, int index, out string register)
|
|
||||||
{
|
|
||||||
if (instruction.Operands.Count > index && instruction.Operands[index] is PpcRegisterOperand operand)
|
|
||||||
{
|
|
||||||
register = NormalizeInstructionReg(operand.Name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
register = string.Empty;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool TryGetInstructionDisplacement(PpcInstruction instruction, int index, out int offset, out string baseRegister, out int baseRegisterNumber)
|
|
||||||
{
|
|
||||||
if (instruction.Operands.Count > index && instruction.Operands[index] is PpcDisplacementOperand operand)
|
|
||||||
{
|
|
||||||
offset = operand.Offset;
|
|
||||||
baseRegister = NormalizeInstructionReg(operand.BaseRegister);
|
|
||||||
baseRegisterNumber = operand.BaseRegisterNumber;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = 0;
|
|
||||||
baseRegister = string.Empty;
|
|
||||||
baseRegisterNumber = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool TryGetInstructionImm(PpcInstruction instruction, int index, out int immediate)
|
|
||||||
{
|
|
||||||
if (instruction.Operands.Count > index && instruction.Operands[index] is PpcImmediateOperand operand)
|
|
||||||
{
|
|
||||||
immediate = operand.Value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
immediate = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool TryInstructionWritesDest(PpcInstruction instruction, out IReadOnlyList<string> destinations)
|
|
||||||
{
|
|
||||||
if (instruction.Operands.Count == 0 || instruction.Operands[0] is not PpcRegisterOperand operand)
|
|
||||||
{
|
|
||||||
destinations = Array.Empty<string>();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mnemonic = instruction.Mnemonic.ToLowerInvariant();
|
|
||||||
if (mnemonic.StartsWith("st", StringComparison.Ordinal) ||
|
|
||||||
mnemonic.StartsWith("b", StringComparison.Ordinal) ||
|
|
||||||
mnemonic.StartsWith("cmp", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
destinations = Array.Empty<string>();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mnemonic == "lmw")
|
|
||||||
{
|
|
||||||
var startReg = Math.Clamp(operand.Number, 0, 31);
|
|
||||||
var regs = new string[32 - startReg];
|
|
||||||
for (var r = startReg; r <= 31; r++)
|
|
||||||
{
|
|
||||||
regs[r - startReg] = $"r{r}";
|
|
||||||
}
|
|
||||||
destinations = regs;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
destinations = [NormalizeInstructionReg(operand.Name)];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool TryGetStackStoreRange(PpcInstruction instruction, out int offset, out int size, out bool updatesStackPointer)
|
|
||||||
{
|
|
||||||
offset = 0;
|
|
||||||
size = 0;
|
|
||||||
updatesStackPointer = false;
|
|
||||||
if (!TryGetInstructionDisplacement(instruction, 1, out offset, out var baseRegister, out _) ||
|
|
||||||
baseRegister != "r1")
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (instruction.Mnemonic.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case "stfs":
|
|
||||||
size = 4;
|
|
||||||
return true;
|
|
||||||
case "stfsu":
|
|
||||||
size = 4;
|
|
||||||
updatesStackPointer = true;
|
|
||||||
return true;
|
|
||||||
case "stfd":
|
|
||||||
size = 8;
|
|
||||||
return true;
|
|
||||||
case "stfdu":
|
|
||||||
size = 8;
|
|
||||||
updatesStackPointer = true;
|
|
||||||
return true;
|
|
||||||
case "stmw" when instruction.Operands[0] is PpcRegisterOperand register:
|
|
||||||
size = checked((32 - Math.Clamp(register.Number, 0, 31)) * 4);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static string NormalizeInstructionReg(string register) => register.ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class PathState : IEquatable<PathState>
|
|
||||||
{
|
|
||||||
public ImmutableDictionary<string, int> LrOffsets { get; }
|
|
||||||
public int? CtrOffset { get; }
|
|
||||||
public int? LrReturnOffset { get; }
|
|
||||||
public int SpDelta { get; }
|
|
||||||
public ImmutableDictionary<int, int> StackOffsets { get; }
|
|
||||||
|
|
||||||
public PathState(
|
|
||||||
ImmutableDictionary<string, int> lrOffsets,
|
|
||||||
int? ctrOffset,
|
|
||||||
int? lrReturnOffset,
|
|
||||||
int spDelta,
|
|
||||||
ImmutableDictionary<int, int> stackOffsets)
|
|
||||||
{
|
|
||||||
LrOffsets = lrOffsets;
|
|
||||||
CtrOffset = ctrOffset;
|
|
||||||
LrReturnOffset = lrReturnOffset;
|
|
||||||
SpDelta = spDelta;
|
|
||||||
StackOffsets = stackOffsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly PathState Empty = new(
|
|
||||||
ImmutableDictionary<string, int>.Empty.WithComparers(StringComparer.OrdinalIgnoreCase),
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
ImmutableDictionary<int, int>.Empty);
|
|
||||||
|
|
||||||
public PathState WithLrOffset(string register, int offset) =>
|
|
||||||
LrOffsets.TryGetValue(register, out var cur) && cur == offset
|
|
||||||
? this
|
|
||||||
: new(LrOffsets.SetItem(register, offset), CtrOffset, LrReturnOffset, SpDelta, StackOffsets);
|
|
||||||
|
|
||||||
public PathState WithoutLrOffset(string register) =>
|
|
||||||
LrOffsets.ContainsKey(register)
|
|
||||||
? new(LrOffsets.Remove(register), CtrOffset, LrReturnOffset, SpDelta, StackOffsets)
|
|
||||||
: this;
|
|
||||||
|
|
||||||
public PathState WithCtrOffset(int? ctrOffset) =>
|
|
||||||
ctrOffset == CtrOffset
|
|
||||||
? this
|
|
||||||
: new(LrOffsets, ctrOffset, LrReturnOffset, SpDelta, StackOffsets);
|
|
||||||
|
|
||||||
public PathState WithLrReturnOffset(int? lrReturnOffset) =>
|
|
||||||
lrReturnOffset == LrReturnOffset
|
|
||||||
? this
|
|
||||||
: new(LrOffsets, CtrOffset, lrReturnOffset, SpDelta, StackOffsets);
|
|
||||||
|
|
||||||
public PathState WithSpDelta(int spDelta) =>
|
|
||||||
spDelta == SpDelta
|
|
||||||
? this
|
|
||||||
: new(LrOffsets, CtrOffset, LrReturnOffset, spDelta, StackOffsets);
|
|
||||||
|
|
||||||
public PathState WithAdjustedStackPointer(int displacement)
|
|
||||||
{
|
|
||||||
// r1 can hold an LR-relative address too. Update both relations;
|
|
||||||
// guest address arithmetic wraps at 32 bits.
|
|
||||||
var updated = WithSpDelta(unchecked(SpDelta + displacement));
|
|
||||||
return LrOffsets.TryGetValue("r1", out var offset)
|
|
||||||
? updated.WithLrOffset("r1", unchecked(offset + displacement))
|
|
||||||
: updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PathState WithStackOffset(int slot, int offset) =>
|
|
||||||
StackOffsets.TryGetValue(slot, out var cur) && cur == offset
|
|
||||||
? this
|
|
||||||
: new(LrOffsets, CtrOffset, LrReturnOffset, SpDelta, StackOffsets.SetItem(slot, offset));
|
|
||||||
|
|
||||||
public PathState WithoutStackOffset(int slot) =>
|
|
||||||
StackOffsets.ContainsKey(slot)
|
|
||||||
? new(LrOffsets, CtrOffset, LrReturnOffset, SpDelta, StackOffsets.Remove(slot))
|
|
||||||
: this;
|
|
||||||
|
|
||||||
public PathState WithoutStackOffsetsInRange(int start, int size)
|
|
||||||
{
|
|
||||||
var end = checked(start + size);
|
|
||||||
var remaining = StackOffsets;
|
|
||||||
foreach (var slot in StackOffsets.Keys)
|
|
||||||
{
|
|
||||||
if (slot < end && start < checked(slot + 4))
|
|
||||||
{
|
|
||||||
remaining = remaining.Remove(slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return remaining.Count == StackOffsets.Count
|
|
||||||
? this
|
|
||||||
: new(LrOffsets, CtrOffset, LrReturnOffset, SpDelta, remaining);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PathState WithClearedStackOffsets() =>
|
|
||||||
StackOffsets.IsEmpty
|
|
||||||
? this
|
|
||||||
: new(LrOffsets, CtrOffset, LrReturnOffset, SpDelta, ImmutableDictionary<int, int>.Empty);
|
|
||||||
|
|
||||||
public bool Equals(PathState? other)
|
|
||||||
{
|
|
||||||
if (ReferenceEquals(this, other)) return true;
|
|
||||||
if (other is null) return false;
|
|
||||||
if (CtrOffset != other.CtrOffset || LrReturnOffset != other.LrReturnOffset || SpDelta != other.SpDelta) return false;
|
|
||||||
if (LrOffsets.Count != other.LrOffsets.Count || StackOffsets.Count != other.StackOffsets.Count) return false;
|
|
||||||
foreach (var (k, v) in LrOffsets)
|
|
||||||
{
|
|
||||||
if (!other.LrOffsets.TryGetValue(k, out var otherV) || v != otherV)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (var (k, v) in StackOffsets)
|
|
||||||
{
|
|
||||||
if (!other.StackOffsets.TryGetValue(k, out var otherV) || v != otherV)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj) => obj is PathState other && Equals(other);
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var hash = new HashCode();
|
|
||||||
hash.Add(CtrOffset);
|
|
||||||
hash.Add(LrReturnOffset);
|
|
||||||
hash.Add(SpDelta);
|
|
||||||
hash.Add(LrOffsets.Count);
|
|
||||||
var regHash = 0;
|
|
||||||
foreach (var (k, v) in LrOffsets)
|
|
||||||
{
|
|
||||||
regHash ^= HashCode.Combine(StringComparer.OrdinalIgnoreCase.GetHashCode(k), v);
|
|
||||||
}
|
|
||||||
hash.Add(regHash);
|
|
||||||
hash.Add(StackOffsets.Count);
|
|
||||||
var stackHash = 0;
|
|
||||||
foreach (var (k, v) in StackOffsets)
|
|
||||||
{
|
|
||||||
stackHash ^= HashCode.Combine(k, v);
|
|
||||||
}
|
|
||||||
hash.Add(stackHash);
|
|
||||||
return hash.ToHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Linq;
|
|
||||||
using Translator.Core.Disassembly;
|
|
||||||
using Translator.Core.Mods;
|
using Translator.Core.Mods;
|
||||||
using Translator.Core.Mods.Mkwii;
|
using Translator.Core.Mods.Mkwii;
|
||||||
using Translator.Core.Parsing.Kamek;
|
using Translator.Core.Parsing.Kamek;
|
||||||
@@ -130,54 +128,6 @@ public class ContinuationPlannerTests
|
|||||||
Assert.Contains("Retro WFC executable hook continuation", entry.Reason);
|
Assert.Contains("Retro WFC executable hook continuation", entry.Reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void DiscoverLrRelativeIndirectJumpOffsets_DiscoversSkipReturnOffset()
|
|
||||||
{
|
|
||||||
var instructions = new[]
|
|
||||||
{
|
|
||||||
PpcDecoder.Decode(0x8180D8E8, 0x7FE802A6u), // mflr r31
|
|
||||||
PpcDecoder.Decode(0x8180D8EC, 0x3BFF0014u), // addi r31, r31, 20
|
|
||||||
PpcDecoder.Decode(0x8180D8F0, 0x7FE803A6u), // mtlr r31
|
|
||||||
PpcDecoder.Decode(0x8180D8F4, 0x4E800020u), // blr
|
|
||||||
};
|
|
||||||
|
|
||||||
var offsets = ContinuationPlanner.DiscoverLrRelativeIndirectJumpOffsets(instructions).ToArray();
|
|
||||||
var offset = Assert.Single(offsets);
|
|
||||||
Assert.Equal(20, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void DiscoverLrRelativeIndirectJumpOffsets_IgnoresStandardLrRestore()
|
|
||||||
{
|
|
||||||
var instructions = new[]
|
|
||||||
{
|
|
||||||
PpcDecoder.Decode(0x8180D8E8, 0x7FE802A6u), // mflr r31
|
|
||||||
PpcDecoder.Decode(0x8180D8EC, 0x93E10008u), // stw r31, 8(r1)
|
|
||||||
PpcDecoder.Decode(0x8180D8F0, 0x83E10008u), // lwz r31, 8(r1)
|
|
||||||
PpcDecoder.Decode(0x8180D8F4, 0x7FE803A6u), // mtlr r31
|
|
||||||
PpcDecoder.Decode(0x8180D8F8, 0x4E800020u), // blr
|
|
||||||
};
|
|
||||||
|
|
||||||
var offsets = ContinuationPlanner.DiscoverLrRelativeIndirectJumpOffsets(instructions);
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void DiscoverLrRelativeIndirectJumpOffsets_SupportsBctrOffset()
|
|
||||||
{
|
|
||||||
var instructions = new[]
|
|
||||||
{
|
|
||||||
PpcDecoder.Decode(0x8180D8E8, 0x7FE802A6u), // mflr r31
|
|
||||||
PpcDecoder.Decode(0x8180D8EC, 0x397F0008u), // addi r11, r31, 8
|
|
||||||
PpcDecoder.Decode(0x8180D8F0, 0x7D6903A6u), // mtctr r11
|
|
||||||
PpcDecoder.Decode(0x8180D8F4, 0x4E800420u), // bctr
|
|
||||||
};
|
|
||||||
|
|
||||||
var offsets = ContinuationPlanner.DiscoverLrRelativeIndirectJumpOffsets(instructions).ToArray();
|
|
||||||
var offset = Assert.Single(offsets);
|
|
||||||
Assert.Equal(8, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static KamekChunk EmptyChunk() =>
|
private static KamekChunk EmptyChunk() =>
|
||||||
new(
|
new(
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
using Translator.Core.Analysis.Ssa;
|
|
||||||
using Translator.Core.Analysis.Representation;
|
|
||||||
using Translator.Core.CodeGen;
|
|
||||||
using Translator.Core.Ir;
|
|
||||||
using Translator.Core.Representation;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Translator.Tests;
|
|
||||||
|
|
||||||
// This binary-free code-generation regression must run in the default suite.
|
|
||||||
public class LrContinuationCodeGenTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void CodeGenerator_DispatchesGuestCallLrContinuationWithoutMarkingTargetNonReturning()
|
|
||||||
{
|
|
||||||
var function = new IrFunction(
|
|
||||||
"lr_continuation_call",
|
|
||||||
"0x800E591C",
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new IrBasicBlock("0x800E591C", new IrInstruction[]
|
|
||||||
{
|
|
||||||
new IrAssign("lr", IrValue.Imm(unchecked((int)0x800E5920u))),
|
|
||||||
new IrCall(string.Empty, "0x8179AC3C", Array.Empty<IrValue>()),
|
|
||||||
new IrAssign("r3", IrValue.Imm(8)),
|
|
||||||
new IrReturn(null)
|
|
||||||
}),
|
|
||||||
new IrBasicBlock("0x800E5934", new IrInstruction[]
|
|
||||||
{
|
|
||||||
new IrAssign("r3", IrValue.Imm(1)),
|
|
||||||
new IrReturn(null)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
var types = new RepresentationEnvironment(new Dictionary<string, ValueRepresentation>
|
|
||||||
{
|
|
||||||
["lr"] = ValueRepresentation.UInt32,
|
|
||||||
["r3"] = ValueRepresentation.UInt32
|
|
||||||
});
|
|
||||||
var signature = new FunctionAbiClassification("lr_continuation_call", ValueRepresentation.Void);
|
|
||||||
|
|
||||||
var ssa = new SsaTransformer().Convert(function);
|
|
||||||
var code = new CxxLinearCodeGenerator().Emit(
|
|
||||||
0x800E591C,
|
|
||||||
ssa,
|
|
||||||
signature,
|
|
||||||
types,
|
|
||||||
lrContinuationCallTargets: new HashSet<uint> { 0x8179AC3Cu });
|
|
||||||
|
|
||||||
var callIndex = code.IndexOf("InvokeDirectCpu<0x8179AC3Cu>(ctx);", StringComparison.Ordinal);
|
|
||||||
var fallthroughGuardIndex = code.IndexOf("if (ctx->lr != 0x800E5920u)", callIndex, StringComparison.Ordinal);
|
|
||||||
var localCaseIndex = code.IndexOf("case 0x800E5934u:", fallthroughGuardIndex, StringComparison.Ordinal);
|
|
||||||
var returnIndex = code.IndexOf("return;", localCaseIndex, StringComparison.Ordinal);
|
|
||||||
var fallthroughAssignmentIndex = code.IndexOf("r3 = 8;", callIndex, StringComparison.Ordinal);
|
|
||||||
|
|
||||||
Assert.True(callIndex >= 0);
|
|
||||||
Assert.True(fallthroughGuardIndex > callIndex);
|
|
||||||
Assert.True(localCaseIndex > fallthroughGuardIndex);
|
|
||||||
Assert.True(returnIndex > localCaseIndex);
|
|
||||||
Assert.True(fallthroughAssignmentIndex > returnIndex, code);
|
|
||||||
Assert.Contains("goto loc_800E5934;", code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,448 +0,0 @@
|
|||||||
using System.Buffers.Binary;
|
|
||||||
using Translator.Core.Disassembly;
|
|
||||||
using Translator.Core.Loading;
|
|
||||||
using Translator.Core.Mods;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Translator.Tests;
|
|
||||||
|
|
||||||
public class LrRelativeContinuationTests
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[InlineData(20, 40)]
|
|
||||||
[InlineData(40, 20)]
|
|
||||||
public void MutuallyExclusiveAdjustmentsKeepBothOffsets(int firstOffset, int secondOffset)
|
|
||||||
{
|
|
||||||
// Both arms start with the incoming LR and join at mtlr. Adding the
|
|
||||||
// offsets together invents a continuation that neither arm can reach.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // +00: mflr r31
|
|
||||||
0x2C030000u, // +04: cmpwi r3,0
|
|
||||||
0x4182000Cu, // +08: beq +0x14
|
|
||||||
AddiR31(firstOffset), // +0C: addi r31,r31,firstOffset
|
|
||||||
0x48000008u, // +10: b +0x18
|
|
||||||
AddiR31(secondOffset), // +14: addi r31,r31,secondOffset
|
|
||||||
0x7FE803A6u, // +18: mtlr r31
|
|
||||||
0x4E800020u);// +1C: blr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 20, 40 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void NormalReturnArmDoesNotEraseSkipReturnAtSharedBlr()
|
|
||||||
{
|
|
||||||
// The normal arm writes the original LR; it must not overwrite the
|
|
||||||
// other arm's LR + 20 in the analysis of the shared return.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // +00: mflr r31
|
|
||||||
0x2C030000u, // +04: cmpwi r3,0
|
|
||||||
0x41820010u, // +08: beq +0x18
|
|
||||||
0x397F0014u, // +0C: addi r11,r31,20
|
|
||||||
0x7D6803A6u, // +10: mtlr r11
|
|
||||||
0x48000008u, // +14: b +0x1C
|
|
||||||
0x7FE803A6u, // +18: mtlr r31
|
|
||||||
0x4E800020u);// +1C: blr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 20 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ConditionalNormalReturnStillDiscoversSkipOnFallthrough()
|
|
||||||
{
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x2C030000u, // cmpwi r3,0
|
|
||||||
0x4D820020u, // beqlr
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x7FE803A6u, // mtlr r31
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 20 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void SavedNonvolatileLrSurvivesHelperCall()
|
|
||||||
{
|
|
||||||
// r31 survives a normal ABI call even though the call replaces LR.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x48000101u, // bl helper outside this function
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x7FE803A6u, // mtlr r31
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 20 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ReloadingSavedRegisterAfterMtlrDoesNotEraseSkipReturn()
|
|
||||||
{
|
|
||||||
// A hook epilogue restores the caller's r31 after committing its
|
|
||||||
// adjusted return address to LR.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x7FE803A6u, // mtlr r31
|
|
||||||
0x83E10008u, // lwz r31,8(r1)
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 20 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void UnknownLrWriteReplacesEarlierSkipReturn()
|
|
||||||
{
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x7FE803A6u, // mtlr r31
|
|
||||||
0x80010008u, // lwz r0,8(r1)
|
|
||||||
0x7C0803A6u, // mtlr r0
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void UnadjustedRegisterReturnDoesNotAddAContinuation()
|
|
||||||
{
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x7FE803A6u, // mtlr r31
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void LoadMultipleWordOverwritesSavedRegistersThroughR31()
|
|
||||||
{
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0xBB610008u, // lmw r30,8(r1)
|
|
||||||
0x7FE803A6u, // mtlr r31
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BlrlCallIsNotTreatedAsReturn()
|
|
||||||
{
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x4E800021u, // blrl
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x7FE803A6u, // mtlr r31
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 20 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MflrAfterCallDoesNotTreatClobberedLrAsIncomingLr()
|
|
||||||
{
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x48000101u, // bl helper
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x7FE803A6u, // mtlr r31
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void RestoredIncomingLrBeforeCtrSkipStillDiscoversOffset()
|
|
||||||
{
|
|
||||||
// The helper replaces LR, but the stack save/restore recovers the
|
|
||||||
// incoming LR before the hook jumps to the caller's continuation.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7C0802A6u, // mflr r0
|
|
||||||
0x90010004u, // stw r0,4(r1)
|
|
||||||
0x9421FFF0u, // stwu r1,-16(r1)
|
|
||||||
0x48000101u, // bl helper outside this function
|
|
||||||
0x38210010u, // addi r1,r1,16
|
|
||||||
0x80010004u, // lwz r0,4(r1)
|
|
||||||
0x7C0803A6u, // mtlr r0
|
|
||||||
0x7D6802A6u, // mflr r11
|
|
||||||
0x396B0008u, // addi r11,r11,8
|
|
||||||
0x7D6903A6u, // mtctr r11
|
|
||||||
0x4E800420u);// bctr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 8 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void BoundedLoopBeforeCtrSkipStillDiscoversOffset()
|
|
||||||
{
|
|
||||||
// Updating an LR-derived register in a two-iteration loop must not
|
|
||||||
// starve analysis of the exit, whose target uses unchanged r31.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x7FC802A6u, // mflr r30
|
|
||||||
0x38600002u, // li r3,2
|
|
||||||
0x7C6903A6u, // mtctr r3
|
|
||||||
0x3BDE0004u, // addi r30,r30,4
|
|
||||||
0x4200FFFCu, // bdnz -4
|
|
||||||
0x397F0008u, // addi r11,r31,8
|
|
||||||
0x7D6903A6u, // mtctr r11
|
|
||||||
0x4E800420u);// bctr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 8 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void UntrackedR1WriteInvalidatesStackTracking()
|
|
||||||
{
|
|
||||||
// If r1 is overwritten from an untracked source, previously saved stack slots
|
|
||||||
// must not be used to recover LR state.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x93E10008u, // stw r31,8(r1)
|
|
||||||
0x80230000u, // lwz r1,0(r3)
|
|
||||||
0x80010008u, // lwz r0,8(r1)
|
|
||||||
0x7C0803A6u, // mtlr r0
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void LargeStraightLineHandlerStillDiscoversSkipReturn()
|
|
||||||
{
|
|
||||||
// The analyzer's global step budget is spent one step per (instruction,
|
|
||||||
// state) pair, so a long enough handler exhausts it before reaching the
|
|
||||||
// return and silently reports no continuation at all. Main's linear
|
|
||||||
// scanner had no budget and always found the offset.
|
|
||||||
var words = new List<uint>
|
|
||||||
{
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u // addi r31,r31,20
|
|
||||||
};
|
|
||||||
for (var i = 0; i < 10_010; i++)
|
|
||||||
{
|
|
||||||
words.Add(0x60000000u); // nop
|
|
||||||
}
|
|
||||||
words.Add(0x7FE803A6u); // mtlr r31
|
|
||||||
words.Add(0x4E800020u); // blr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 20 }, DiscoverOffsets(words.ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void LargeBranchingHandlerStillDiscoversCtrSkip()
|
|
||||||
{
|
|
||||||
// Same budget, reached far sooner once the handler branches: this is the
|
|
||||||
// bctr shape the pre-PR scanner discovered at any function size.
|
|
||||||
var words = new List<uint> { 0x7FE802A6u }; // mflr r31
|
|
||||||
for (var i = 0; i < 160; i++)
|
|
||||||
{
|
|
||||||
var displacement = (uint)((i + 1) * 4 & 0xFFFF);
|
|
||||||
words.Add(0x2C030000u); // cmpwi r3,0
|
|
||||||
words.Add(0x4182000Cu); // beq +0xC
|
|
||||||
words.Add(0x3BDF0000u | displacement); // addi r30,r31,disp
|
|
||||||
words.Add(0x48000008u); // b +8
|
|
||||||
words.Add(0x3BBF0000u | displacement); // addi r29,r31,disp
|
|
||||||
}
|
|
||||||
words.Add(0x397F0008u); // addi r11,r31,8
|
|
||||||
words.Add(0x7D6903A6u); // mtctr r11
|
|
||||||
words.Add(0x4E800420u); // bctr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 8 }, DiscoverOffsets(words.ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void FloatStoreOverSavedSlotInvalidatesStackTracking()
|
|
||||||
{
|
|
||||||
// stfd writes 0x10..0x17, which covers the slot the adjusted LR was
|
|
||||||
// saved to. Only stw/stwu invalidate slots today, so the reload is
|
|
||||||
// credited with a return address the stack no longer holds.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x93E10014u, // stw r31,0x14(r1)
|
|
||||||
0xD8410010u, // stfd f2,0x10(r1)
|
|
||||||
0x80010014u, // lwz r0,0x14(r1)
|
|
||||||
0x7C0803A6u, // mtlr r0
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void StoreMultipleOverSavedSlotInvalidatesStackTracking()
|
|
||||||
{
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x93E10008u, // stw r31,8(r1)
|
|
||||||
0xBFC10008u, // stmw r30,8(r1)
|
|
||||||
0x80010008u, // lwz r0,8(r1)
|
|
||||||
0x7C0803A6u, // mtlr r0
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void StackPointerUpdatePreservesAdjustedLrOffset()
|
|
||||||
{
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7C2802A6u, // mflr r1
|
|
||||||
0xDC410004u, // stfdu f2,4(r1)
|
|
||||||
0x7C2803A6u, // mtlr r1
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Equal(new[] { 4 }, offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void VolatileRegisterDoesNotSurviveHelperCall()
|
|
||||||
{
|
|
||||||
// r3 is caller-saved, so the callee is free to destroy the adjusted
|
|
||||||
// return address this hook staged before the call.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7C6802A6u, // mflr r3
|
|
||||||
0x38630014u, // addi r3,r3,20
|
|
||||||
0x48000101u, // bl helper outside this function
|
|
||||||
0x7C6803A6u, // mtlr r3
|
|
||||||
0x4E800020u);// blr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CtrDoesNotSurviveHelperCall()
|
|
||||||
{
|
|
||||||
// CTR is volatile across a call for the same reason.
|
|
||||||
var offsets = DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x7FE903A6u, // mtctr r31
|
|
||||||
0x48000101u, // bl helper outside this function
|
|
||||||
0x4E800420u);// bctr
|
|
||||||
|
|
||||||
Assert.Empty(offsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MflrR1InvalidatesOldStackSlots()
|
|
||||||
{
|
|
||||||
// After mflr r1, 8(r1) refers to incoming LR + 8, not the old
|
|
||||||
// stack slot. Its contents are unknown; do not invent a +20 return.
|
|
||||||
Assert.Empty(DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x93E10008u, // stw r31,8(r1)
|
|
||||||
0x7C2802A6u, // mflr r1
|
|
||||||
0x80010008u, // lwz r0,8(r1)
|
|
||||||
0x7C0803A6u, // mtlr r0
|
|
||||||
0x4E800020u)); // blr
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void AddiR1UpdatesLrRelativeOffset()
|
|
||||||
{
|
|
||||||
// Like StackPointerUpdatePreservesAdjustedLrOffset, r1 holds incoming
|
|
||||||
// LR here. Updating r1 must update that relation as well as stack state.
|
|
||||||
Assert.Equal(new[] { 4 }, DiscoverOffsets(
|
|
||||||
0x7C2802A6u, // mflr r1
|
|
||||||
0x38210004u, // addi r1,r1,4
|
|
||||||
0x7C2803A6u, // mtlr r1
|
|
||||||
0x4E800020u)); // blr
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0x38210004u, 4)] // addi r1,r1,4
|
|
||||||
[InlineData(0x30210004u, 4)] // addic r1,r1,4
|
|
||||||
[InlineData(0x94210004u, 4)] // stwu r1,4(r1)
|
|
||||||
[InlineData(0xD4410004u, 4)] // stfsu f2,4(r1)
|
|
||||||
[InlineData(0xDC410004u, 4)] // stfdu f2,4(r1)
|
|
||||||
[InlineData(0x3821FFFCu, -4)] // addi r1,r1,-4
|
|
||||||
public void StackPointerUpdatesPreserveLrRelation(uint update, int expectedOffset)
|
|
||||||
{
|
|
||||||
Assert.Equal(new[] { expectedOffset }, DiscoverOffsets(
|
|
||||||
0x7C2802A6u, // mflr r1
|
|
||||||
update,
|
|
||||||
0x7C2803A6u, // mtlr r1
|
|
||||||
0x4E800020u)); // blr
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(0x7FE1FB78u)] // mr r1,r31
|
|
||||||
[InlineData(0x383F0000u)] // addi r1,r31,0
|
|
||||||
public void CopyingLrIntoR1PreservesReturnButInvalidatesOldStack(uint copy)
|
|
||||||
{
|
|
||||||
var prefix = new uint[]
|
|
||||||
{
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x93E10008u, // stw r31,8(r1)
|
|
||||||
copy,
|
|
||||||
};
|
|
||||||
Assert.Equal(new[] { 20 }, DiscoverOffsets(
|
|
||||||
prefix.Concat(new uint[] { 0x7C2803A6u, 0x4E800020u }).ToArray()));
|
|
||||||
Assert.Empty(DiscoverOffsets(prefix.Concat(new uint[]
|
|
||||||
{
|
|
||||||
0x80010008u, // lwz r0,8(r1): no longer the old stack slot
|
|
||||||
0x7C0803A6u, // mtlr r0
|
|
||||||
0x4E800020u,
|
|
||||||
}).ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void StackPointerSelfMovePreservesSavedLr()
|
|
||||||
{
|
|
||||||
Assert.Equal(new[] { 20 }, DiscoverOffsets(
|
|
||||||
0x7FE802A6u, // mflr r31
|
|
||||||
0x3BFF0014u, // addi r31,r31,20
|
|
||||||
0x93E10008u, // stw r31,8(r1)
|
|
||||||
0x7C210B78u, // mr r1,r1
|
|
||||||
0x80010008u, // lwz r0,8(r1)
|
|
||||||
0x7C0803A6u, // mtlr r0
|
|
||||||
0x4E800020u));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void IncompleteInstructionListDoesNotInventFallthroughAcrossGap()
|
|
||||||
{
|
|
||||||
// Defensive incomplete-input test, not a production disassembly trace:
|
|
||||||
// the missing instruction could overwrite r31 or branch elsewhere.
|
|
||||||
// Address sorting alone does not establish a fallthrough edge.
|
|
||||||
var instructions = new[]
|
|
||||||
{
|
|
||||||
PpcDecoder.Decode(0x81800000u, 0x7FE802A6u), // mflr r31
|
|
||||||
PpcDecoder.Decode(0x81800008u, 0x3BFF0014u), // addi r31,r31,20
|
|
||||||
PpcDecoder.Decode(0x8180000Cu, 0x7FE803A6u), // mtlr r31
|
|
||||||
PpcDecoder.Decode(0x81800010u, 0x4E800020u), // blr
|
|
||||||
};
|
|
||||||
|
|
||||||
Assert.Empty(ContinuationPlanner.DiscoverLrRelativeIndirectJumpOffsets(instructions));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static uint AddiR31(int offset) => 0x3BFF0000u | (uint)(offset & 0xFFFF);
|
|
||||||
|
|
||||||
private static int[] DiscoverOffsets(params uint[] words)
|
|
||||||
{
|
|
||||||
const uint entry = 0x81800000u;
|
|
||||||
var memory = new byte[words.Length * 4];
|
|
||||||
for (var i = 0; i < words.Length; i++)
|
|
||||||
{
|
|
||||||
BinaryPrimitives.WriteUInt32BigEndian(memory.AsSpan(i * 4, 4), words[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = AddressRange.FromStartAndSize(entry, (uint)memory.Length);
|
|
||||||
var image = new ProgramImage(memory, range, range, default, "lr-continuation-test", entry);
|
|
||||||
using var disassembler = new PpcDisassembler();
|
|
||||||
// Use the production reachable-instruction traversal and ordering,
|
|
||||||
// rather than handing the planner an artificial execution trace.
|
|
||||||
var instructions = disassembler.DisassembleFunction(
|
|
||||||
image, entry, maxInstructions: words.Length + 1, maxBytes: memory.Length);
|
|
||||||
|
|
||||||
return ContinuationPlanner.DiscoverLrRelativeIndirectJumpOffsets(instructions)
|
|
||||||
.Distinct().OrderBy(offset => offset).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user