Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion runtime/include/abi_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class AbiTrampoline<Ret(CpuContext*)> {
#define MKW_DETAIL_CAT(a, b) a##b
#define MKW_DETAIL_MAKE_UNIQUE(a, b) MKW_DETAIL_CAT(a, b)

// Three hand-written registration macros, given the PAL address. Generated code never registers
// Hand-written registration macros, given the PAL address. Generated code never registers
// per function; every translated function reaches the registry via the bulk
// BulkTranslatedFunctionRecord tables in the translator's *_registration TUs.
#define REGISTER_TRANSLATED_FUNCTION(address, fn) \
Expand All @@ -168,3 +168,9 @@ class AbiTrampoline<Ret(CpuContext*)> {

#define REGISTER_NATIVE_FUNCTION_AS(address, fn, pretty_name) \
static AbiTrampoline<decltype(fn)> MKW_DETAIL_MAKE_UNIQUE(_abi_native_trampoline_named_, __COUNTER__)(::GuestRegion::Translate(address), pretty_name, fn, FunctionKind::Native, false, kPpcAllNonvolatileFprMask, 0, 0, &AbiRawCpuThunk<&fn>::Invoke)

// A native that calls through with MKW_GUEST_NEXT, so a mod overlay at its address still runs.
#define REGISTER_NATIVE_WRAPPER_AS(address, fn, pretty_name) REGISTER_NATIVE_FUNCTION_AS(address, fn, pretty_name)

// A native that deliberately takes over its address from any mod overlay there.
#define REGISTER_NATIVE_REPLACEMENT_AS(address, fn, pretty_name) REGISTER_NATIVE_FUNCTION_AS(address, fn, pretty_name)
12 changes: 12 additions & 0 deletions runtime/include/guest_function_imports.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// Base translations the runtime calls directly, named by PAL identity and resolved through the
// function registry on first use.
// MKW_GUEST_FUNC(801A0620)(ctx);
// A native registered with REGISTER_NATIVE_WRAPPER_AS calls through with MKW_GUEST_NEXT instead,
// so a mod overlay at the same address still runs.

#include "guest_abi/cpu_context.h"
#include "guest_abi/guest_region.h"
Expand All @@ -12,10 +14,20 @@ using Entry = void (*)(CpuContext*);

// The base translation at a guest address, also where a native or mod overrides it.
Entry BaseTranslation(uint32_t address);

// The translation a native wrapper sits on top of: the winning mod overlay, else the base
// translation.
Entry NextTranslation(uint32_t address);
} // namespace GuestFunctions

#define MKW_GUEST_FUNC(pal) \
(*[] { \
static const ::GuestFunctions::Entry entry = ::GuestFunctions::BaseTranslation(MKW_GADDR(pal)); \
return entry; \
}())

#define MKW_GUEST_NEXT(pal) \
(*[] { \
static const ::GuestFunctions::Entry entry = ::GuestFunctions::NextTranslation(MKW_GADDR(pal)); \
return entry; \
}())
55 changes: 41 additions & 14 deletions runtime/src/abi_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,20 +426,10 @@ const TranslatedFunctionInfo* TranslatedFunctionRegistry::FindByAddressPtrSlow(u
return nullptr;
}

GuestFunctions::Entry GuestFunctions::BaseTranslation(uint32_t address) {
std::unique_lock<std::mutex> lock(RegistryMutex(), std::defer_lock);
if (!RegistryFrozen().load(std::memory_order_acquire)) {
lock.lock();
}
for (const auto& entry : Registry()) {
if (entry.address == address && entry.kind == FunctionKind::BaseTranslated && entry.rawCpuInvoker) {
return entry.rawCpuInvoker;
}
}
if (lock.owns_lock()) {
lock.unlock();
}
RT_LOG(RT_TAG_RUNTIME) << "ERROR: no base translation registered at 0x" << std::hex << address << std::dec
namespace {

[[noreturn]] void ReportMissingGuestFunction(uint32_t address) {
RT_LOG(RT_TAG_RUNTIME) << "ERROR: no translation registered at 0x" << std::hex << address << std::dec
<< std::endl;
std::ostringstream message;
message << "The runtime calls the translated function at guest address 0x" << std::hex << address
Expand All @@ -451,6 +441,43 @@ GuestFunctions::Entry GuestFunctions::BaseTranslation(uint32_t address) {
std::exit(EXIT_FAILURE);
}

} // namespace

GuestFunctions::Entry GuestFunctions::BaseTranslation(uint32_t address) {
{
std::unique_lock<std::mutex> lock(RegistryMutex(), std::defer_lock);
if (!RegistryFrozen().load(std::memory_order_acquire)) {
lock.lock();
}
for (const auto& entry : Registry()) {
if (entry.address == address && entry.kind == FunctionKind::BaseTranslated && entry.rawCpuInvoker) {
return entry.rawCpuInvoker;
}
}
}
ReportMissingGuestFunction(address);
}

GuestFunctions::Entry GuestFunctions::NextTranslation(uint32_t address) {
{
std::unique_lock<std::mutex> lock(RegistryMutex(), std::defer_lock);
if (!RegistryFrozen().load(std::memory_order_acquire)) {
lock.lock();
}
const TranslatedFunctionInfo* best = nullptr;
for (const auto& entry : Registry()) {
if (entry.address == address && entry.kind != FunctionKind::Native && entry.rawCpuInvoker &&
(!best || IsBetterCandidate(entry, *best))) {
best = &entry;
}
}
if (best) {
return best->rawCpuInvoker;
}
}
ReportMissingGuestFunction(address);
}

const RawDispatchRecord* TranslatedFunctionRegistry::FindRawByAddressPtrSlow(uint32_t address) {
if (!RegistryFrozen().load(std::memory_order_acquire)) {
return nullptr;
Expand Down
6 changes: 3 additions & 3 deletions runtime/src/hle/boot_race.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,23 +162,23 @@ REGISTER_NATIVE_FUNCTION_AS(0x805B9344, ArgumentParser_Parse_805b9344, "Argument
// RacedataScenario::InitScreens; local racers keep no real controller channel.
extern "C" void RacedataScenario_InitScreens_8052f4e8(CpuContext* ctx) {
if (!g_cpuDriven) {
MKW_GUEST_FUNC(8052F4E8)(ctx);
MKW_GUEST_NEXT(8052F4E8)(ctx);
return;
}
const uint32_t players = ctx->gpr[3] + kPlayersOffset;
std::array<uint8_t, kPlayerCount> channels{};
for (uint32_t i = 0; i < kPlayerCount; ++i) {
channels[i] = Memory::Read8(players + i * kPlayerSize + kPlayerControllerChannelOffset);
}
MKW_GUEST_FUNC(8052F4E8)(ctx);
MKW_GUEST_NEXT(8052F4E8)(ctx);
for (uint32_t i = 0; i < kPlayerCount; ++i) {
const uint32_t player = players + i * kPlayerSize;
if (Memory::Read32(player + kPlayerTypeOffset) == kPlayerRealLocal) {
Memory::Write8(player + kPlayerControllerChannelOffset, channels[i]);
}
}
}
REGISTER_NATIVE_FUNCTION_AS(0x8052F4E8, RacedataScenario_InitScreens_8052f4e8, "RacedataScenario_InitScreens_8052f4e8");
REGISTER_NATIVE_WRAPPER_AS(0x8052F4E8, RacedataScenario_InitScreens_8052f4e8, "RacedataScenario_InitScreens_8052f4e8");

// KartAIController::IsCPU
extern "C" void KartAIController_IsCPU_8072624c(CpuContext* ctx) {
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/hle/storage/nand_isfs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ extern "C" void NAND_IOS_OpenBody_HLE_801938FC(CpuContext* ctx) {
ctx->gpr[3] = static_cast<uint32_t>(result);
ctx->gpr[1] = ctx->gpr[1] + 32u;
}
REGISTER_NATIVE_FUNCTION_AS(0x801938FC, NAND_IOS_OpenBody_HLE_801938FC, "NAND_IOS_OpenBody_HLE_801938FC");
REGISTER_NATIVE_REPLACEMENT_AS(0x801938FC, NAND_IOS_OpenBody_HLE_801938FC, "NAND_IOS_OpenBody_HLE_801938FC");

extern "C" int32_t NAND_IOS_Close_HLE(uint32_t fd) {
if (fd == ISFS_DEV_FD) {
Expand Down
17 changes: 13 additions & 4 deletions translator/src/Translator.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2278,12 +2278,21 @@ void AddNative(uint address, string symbol, string sourceFile)
// functions the runtime must own (hardware, the strap scene). But when the mod's whole point
// is replacing that function, e.g. Retro Rewind's DVDOpen override at 0x8015E2BC, the overlay
// is silently dropped. Name every such address so the tradeoff is a choice, not an accident.
// Wrapper natives still run the overlay, and replacement natives already made that choice.
var nativesKeepingModOverlayDecision = runtimeNativeIndex.Value.Registrations
.Where(static registration => registration.ModOverlay != NativeModOverlay.Unspecified)
.Select(static registration => (registration.Address, registration.Symbol))
.ToHashSet();
var nativeOverriddenModPatches = groups
.Where(group =>
group.Any(entry => entry.Kind == "rr") &&
group.OrderByDescending(entry => entry.Priority)
.ThenByDescending(entry => KindRank(entry.Kind))
.First().Kind == "native")
{
var winner = group.OrderByDescending(entry => entry.Priority)
.ThenByDescending(entry => KindRank(entry.Kind))
.First();
return group.Any(entry => entry.Kind == "rr") &&
winner.Kind == "native" &&
!nativesKeepingModOverlayDecision.Contains((winner.Address, winner.Symbol));
})
.Select(group => new
{
Address = group.Key,
Expand Down
6 changes: 4 additions & 2 deletions translator/src/Translator.Core/GeneratedMarkers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ public static string ModRegistration(

/// <summary>
/// <c>REGISTER_NATIVE_FUNCTION(0x…, symbol)</c> and its <c>_AS</c> alias form;
/// the <c>as</c> group tells the two apart.
/// the <c>as</c> group tells the two apart. <c>REGISTER_NATIVE_WRAPPER_AS</c> and
/// <c>REGISTER_NATIVE_REPLACEMENT_AS</c> are alias forms too, named by the
/// <c>modOverlay</c> group.
/// </summary>
[GeneratedRegex(@"REGISTER_NATIVE_FUNCTION(?<as>_AS)?\s*\(\s*0x(?<address>[0-9A-Fa-f]{8})\s*,\s*(?<symbol>[A-Za-z_][A-Za-z0-9_]*)", RegexOptions.CultureInvariant)]
[GeneratedRegex(@"REGISTER_NATIVE_(?:FUNCTION(?<as>_AS)?|(?<modOverlay>WRAPPER|REPLACEMENT)_AS)\s*\(\s*0x(?<address>[0-9A-Fa-f]{8})\s*,\s*(?<symbol>[A-Za-z_][A-Za-z0-9_]*)", RegexOptions.CultureInvariant)]
public static partial Regex NativeFunctionRegistrationPattern();

[GeneratedRegex(@"REGISTER_TRANSLATED_FUNCTION\s*\(\s*0x(?<address>[0-9A-Fa-f]{8})\s*,\s*(?<symbol>[A-Za-z_][A-Za-z0-9_]*)", RegexOptions.CultureInvariant)]
Expand Down
34 changes: 29 additions & 5 deletions translator/src/Translator.Core/RuntimeNativeIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,19 @@ public sealed record RuntimeNativeRegistration(
string Symbol,
string SourceFile,
bool IsTranslatedOverride,
bool ExcludesBaseTranslation);
bool ExcludesBaseTranslation,
NativeModOverlay ModOverlay = NativeModOverlay.Unspecified);

/// <summary>What happens to a mod overlay at a native's address.</summary>
public enum NativeModOverlay
{
/// <summary>Plain registration: the overlay is lost, and reported as such.</summary>
Unspecified,
/// <summary>The native calls through to the overlay (<c>MKW_GUEST_NEXT</c>).</summary>
Wrapper,
/// <summary>The native deliberately takes over the overlay's address.</summary>
Replacement
}

public sealed record RuntimeNativeAbiEntry(
uint Address,
Expand Down Expand Up @@ -119,18 +131,29 @@ private static IEnumerable<RuntimeNativeRegistration> ScanRegistrations(
{
var source = sourceFile.Content;
foreach (Match match in GeneratedMarkers.NativeFunctionRegistrationPattern().Matches(source))
Add(match, match.Groups["symbol"].Value, false, !match.Groups["as"].Success);
{
var modOverlay = match.Groups["modOverlay"];
Add(match, match.Groups["symbol"].Value, false,
!match.Groups["as"].Success && !modOverlay.Success,
modOverlay.Value switch
{
"WRAPPER" => NativeModOverlay.Wrapper,
"REPLACEMENT" => NativeModOverlay.Replacement,
_ => NativeModOverlay.Unspecified
});
}
foreach (Match match in GeneratedMarkers.TranslatedFunctionRegistrationPattern().Matches(source))
Add(match, match.Groups["symbol"].Value, true, true);
foreach (Match match in GeneratedMarkers.NativeOverridePattern().Matches(source))
Add(match, match.Groups["symbol"].Value, false, true);
foreach (Match match in GeneratedMarkers.FatalStubPattern().Matches(source))
Add(match, $"GX_FATAL_STUB_{match.Groups["address"].Value}", false, true);

void Add(Match match, string symbol, bool translated, bool excludesBase) =>
void Add(Match match, string symbol, bool translated, bool excludesBase,
NativeModOverlay modOverlay = NativeModOverlay.Unspecified) =>
registrations.Add(new RuntimeNativeRegistration(
ParseAddress(match.Groups["address"].Value), symbol, sourceFile.RelativePath,
translated, excludesBase));
translated, excludesBase, modOverlay));
}

return registrations
Expand All @@ -139,7 +162,8 @@ void Add(Match match, string symbol, bool translated, bool excludesBase) =>
.ThenBy(static registration => registration.Symbol, StringComparer.Ordinal)
.ThenBy(static registration => registration.SourceFile, StringComparer.Ordinal)
.ThenBy(static registration => registration.IsTranslatedOverride)
.ThenBy(static registration => registration.ExcludesBaseTranslation);
.ThenBy(static registration => registration.ExcludesBaseTranslation)
.ThenBy(static registration => registration.ModOverlay);
}

private static uint ParseAddress(string value) => GuestTargetParser.ParseHexAddress(value);
Expand Down
17 changes: 13 additions & 4 deletions translator/src/Translator.Core/RuntimeNativeIndexFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ namespace Translator.Core;
public static class RuntimeNativeIndexFile
{
public const string Format = "wiicompiled-runtime-native-index";
public const int FormatVersion = 1;
public const int FormatVersion = 2;
// Bump whenever the scanner or the ABI/effect analyzers can produce different output for the same sources.
public const int ExtractorVersion = 1;
public const int ExtractorVersion = 2;
public const string DefaultFileName = "native-index.json";

private static readonly JsonSerializerOptions Options = new()
Expand Down Expand Up @@ -59,13 +59,15 @@ public static byte[] Serialize(RuntimeNativeIndex index, string sourceDigest)
.ThenBy(static registration => registration.SourceFile, StringComparer.Ordinal)
.ThenBy(static registration => registration.IsTranslatedOverride)
.ThenBy(static registration => registration.ExcludesBaseTranslation)
.ThenBy(static registration => registration.ModOverlay)
.Select(static registration => new RegistrationDto
{
Address = FormatAddress(registration.Address),
Symbol = registration.Symbol,
SourceFile = registration.SourceFile,
IsTranslatedOverride = registration.IsTranslatedOverride,
ExcludesBaseTranslation = registration.ExcludesBaseTranslation
ExcludesBaseTranslation = registration.ExcludesBaseTranslation,
ModOverlay = registration.ModOverlay.ToString()
})
.ToList(),
VoidStubAbis = index.VoidStubAbis
Expand Down Expand Up @@ -160,7 +162,8 @@ public static bool TryLoad(
Present(registration.Symbol, "symbol"),
Present(registration.SourceFile, "sourceFile"),
registration.IsTranslatedOverride,
registration.ExcludesBaseTranslation))
registration.ExcludesBaseTranslation,
ParseModOverlay(Present(registration.ModOverlay, "modOverlay"))))
.ToArray(),
Present(document.VoidStubAbis, "voidStubAbis")
.Select(static entry => new RuntimeNativeAbiEntry(
Expand All @@ -185,6 +188,11 @@ public static bool TryLoad(

private static string FormatAddress(uint address) => $"0x{address:X8}";

private static NativeModOverlay ParseModOverlay(string text) =>
Enum.TryParse<NativeModOverlay>(text, ignoreCase: false, out var value) && Enum.IsDefined(value)
? value
: throw new FormatException($"Unknown modOverlay '{text}'.");

private static uint ParseAddress(string text) => GuestTargetParser.ParseHexAddress(text);

private static T Present<T>(T? value, string name) where T : class =>
Expand Down Expand Up @@ -244,6 +252,7 @@ private sealed class RegistrationDto
public required string SourceFile { get; init; }
public required bool IsTranslatedOverride { get; init; }
public required bool ExcludesBaseTranslation { get; init; }
public required string ModOverlay { get; init; }
}

private sealed class AbiDto
Expand Down
4 changes: 2 additions & 2 deletions translator/tests/Translator.Tests/GuestAddressTableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public void RuntimeGuestCallsAreFunctionEntriesInEveryRegion()
var calls = Directory.EnumerateFiles(Path.Combine(root, "runtime"), "*.*", SearchOption.AllDirectories)
.Where(path => (path.EndsWith(".cpp", StringComparison.Ordinal) || path.EndsWith(".h", StringComparison.Ordinal)) &&
!path.Contains(thirdParty, StringComparison.Ordinal))
.SelectMany(static path => Regex.Matches(File.ReadAllText(path), @"MKW_GUEST_FUNC\(\s*([0-9A-Fa-f]{8})\s*\)")
.SelectMany(static path => Regex.Matches(File.ReadAllText(path), @"MKW_GUEST_(?:FUNC|NEXT)\(\s*([0-9A-Fa-f]{8})\s*\)")
.Select(static match => Convert.ToUInt32(match.Groups[1].Value, 16)))
.Distinct()
.ToArray();
Expand All @@ -145,7 +145,7 @@ public void RuntimeGuestCallsAreFunctionEntriesInEveryRegion()
foreach (var (region, starts) in entries)
{
var address = table.Map(pal, region);
Assert.True(starts.Contains(address), $"MKW_GUEST_FUNC({pal:X8}) is not a {region} function entry (0x{address:X8})");
Assert.True(starts.Contains(address), $"MKW_GUEST_FUNC/NEXT({pal:X8}) is not a {region} function entry (0x{address:X8})");
}
}
}
Expand Down
Loading
Loading