Skip to content
Merged
12 changes: 11 additions & 1 deletion projects/mkwii-ntsc-u/MAP.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 12 additions & 7 deletions projects/mkwii-ntsc-u/region_port.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion runtime/cmake/PublicProducts.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ endif()

function(mkw_apply_translated_compile_options target)
target_compile_options(${target} PRIVATE
-O2 ${MKW_TRANSLATED_PPC_FP_OPTIONS} -fno-slp-vectorize -w -pipe)
-O2 ${MKW_TRANSLATED_PPC_FP_OPTIONS} -fno-slp-vectorize -mllvm -hot-cold-split=true -w -pipe)
endfunction()

# Translated shard TUs are the memory-hungry compiles; everything else in the build is
Expand Down
8 changes: 7 additions & 1 deletion runtime/include/aurora_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "settings_overlay.h"
#include "runtime_config.h"
#include "gx_worker.h"
#include "perf_stats.h"

#include <aurora/aurora.h>
#include <aurora/event.h>
Expand Down Expand Up @@ -142,7 +143,12 @@ inline bool BeginAuroraFrame() {
if (GxWorker::ShouldDefer()) {
// The window half can touch the surface and render targets the worker draws into.
GxWorker::Sync();
if (!aurora_begin_frame_window()) {
bool begun = false;
{
PerfStats::WaitScope wait;
begun = aurora_begin_frame_window();
}
if (!begun) {
return false;
}
GxWorker::Submit([] { GxWorkerBeginFrame(); });
Expand Down
10 changes: 10 additions & 0 deletions runtime/include/boot_race.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <string_view>

// `--race[=<course id>]`: boot straight into a 1P VS race instead of the title screen.
// `--race-cpu`: the same, with the local racer driven by the CPU.
namespace BootRace {
// False when `arg` is not a boot-race option; throws on a malformed one.
bool ParseArgument(std::string_view arg);
} // namespace BootRace
53 changes: 44 additions & 9 deletions runtime/include/guest_abi/dispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ struct KnownTypedNativeCpuCall {
static constexpr bool kAvailable = false;
};

template <typename T, typename = void>
struct IsPureTypedNativeCpuCall : std::false_type {};
template <typename T>
struct IsPureTypedNativeCpuCall<T, std::void_t<decltype(T::kPure)>> : std::bool_constant<T::kPure> {};

#include "guest_abi/native_cpu_calls.inc"

// Direct-mapped thread-local memo in front of the sorted registry lookup every bctrl performs;
Expand Down Expand Up @@ -554,7 +559,10 @@ inline void InvokeDirectCpu(CpuContext* ctx) {
return;
}

if constexpr (KnownTypedNativeCpuCall<Target>::kAvailable) {
if constexpr (IsPureTypedNativeCpuCall<KnownTypedNativeCpuCall<Target>>::value) {
KnownTypedNativeCpuCall<Target>::Invoke(cpu);
return;
} else if constexpr (KnownTypedNativeCpuCall<Target>::kAvailable) {
PpcNonvolatileGprGuard gprGuard(cpu);
if (TryGetCpuContext() != cpu) {
CpuContextScope scope(cpu);
Expand All @@ -581,12 +589,20 @@ inline void InvokeDirectCpu(CpuContext* ctx) {
// Cold tail of InvokeIndirectJump: a bctr target that is not a registered function.
[[noreturn]] MKW_PPC_NO_INLINE MKW_PPC_COLD void ReportMissingIndirectJumpTarget(uint32_t target, CpuContext* cpu);

// Indirect jump (bctr without link) - used for switch tables.
inline void InvokeIndirectJump(uint32_t target, CpuContext* ctx) {
// Use the caller's context, not the persistent global context!
// This is essential for tail calls via bctr where the 'this' pointer (r3)
// must be passed correctly to the target function.
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
// The common indirect case: an unguarded record called from its own CpuContext, so the full
// TryDispatchRawCpuTarget would only set the execution address and call.
MKW_PPC_FORCE_INLINE bool TryDispatchUnguardedRawCpuTarget(uint32_t target, CpuContext* cpu) {
const RawDispatchRecord* record = TranslatedFunctionRegistry::FindRawByAddressPtr(target);
if (record == nullptr || record->entry == nullptr || record->preserveNonvolatileGprs ||
record->nonvolatileFprWriteMask != 0 || TryGetCpuContext() != cpu) {
return false;
}
RecompMod::ScopedTranslatedExecutionAddress translatedExecution(record->address);
record->entry(cpu);
return true;
}

MKW_PPC_NO_INLINE inline void InvokeIndirectJumpSlow(uint32_t target, CpuContext* cpu) {
if (TryDispatchRawCpuTarget(TranslatedFunctionRegistry::FindRawByAddressPtr(target), cpu)) {
return;
}
Expand All @@ -601,12 +617,22 @@ inline void InvokeIndirectJump(uint32_t target, CpuContext* ctx) {
ReportMissingIndirectJumpTarget(target, cpu);
}

inline void InvokeIndirectCpu(uint32_t target, CpuContext* ctx) {
// Indirect jump (bctr without link) - used for switch tables.
inline void InvokeIndirectJump(uint32_t target, CpuContext* ctx) {
// Use the caller's context, not the persistent global context!
// This is essential for tail calls via bctr where the 'this' pointer (r3)
// must be passed correctly to the target function.
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
if (TryDispatchUnguardedRawCpuTarget(target, cpu)) {
return;
}
InvokeIndirectJumpSlow(target, cpu);
}

MKW_PPC_NO_INLINE inline void InvokeIndirectCpuSlow(uint32_t target, CpuContext* cpu) {
if (target == 0) {
ReportMissingCpuTarget(target, cpu);
}
ApplyRuntimeCallOptions(target, cpu);
if (TryDispatchRawCpuTarget(TranslatedFunctionRegistry::FindRawByAddressPtr(target), cpu)) {
return;
}
Expand All @@ -615,3 +641,12 @@ inline void InvokeIndirectCpu(uint32_t target, CpuContext* ctx) {
}
ReportMissingCpuTarget(target, cpu);
}

inline void InvokeIndirectCpu(uint32_t target, CpuContext* ctx) {
CpuContext* cpu = ctx ? ctx : &GetPersistentCpuContext();
ApplyRuntimeCallOptions(target, cpu);
if (TryDispatchUnguardedRawCpuTarget(target, cpu)) {
return;
}
InvokeIndirectCpuSlow(target, cpu);
}
11 changes: 6 additions & 5 deletions runtime/include/guest_abi/guest_versions.inc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions runtime/include/guest_abi/native_cpu_calls.inc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ extern "C" void GX__SetNumTevStages_801722a8(uint32_t n);
extern "C" void GX__SetBlendMode_8017277c(uint32_t t, uint32_t s, uint32_t d, uint32_t op);
extern "C" void GX__LoadPosMtxImm_8017310c(uint32_t ma, uint32_t id);
extern "C" void GX__SetCurrentMtx_80173214(uint32_t id);
extern "C" int32_t OS__DisableInterrupts_801a65ac();
extern "C" int32_t OS__EnableInterrupts_801a65c0();
extern "C" int32_t OS__RestoreInterrupts_801a65d4(int32_t level);

// Keyed by the translated region's addresses, so only translated code sees them;
// runtime calls resolve through the registry.
Expand Down Expand Up @@ -90,6 +93,20 @@ MKW_KNOWN_TYPED_NATIVE(80173214, GX__SetCurrentMtx_80173214, cpu->gpr[3]);

#undef MKW_KNOWN_TYPED_NATIVE

// Natives that neither touch CpuContext nor re-enter guest code: no register guard or context scope.
#define MKW_KNOWN_PURE_TYPED_NATIVE_R3(addr, fn, ...) \
template <> struct KnownTypedNativeCpuCall<MKW_GADDR_CT(addr)> { \
static constexpr bool kAvailable = true; \
static constexpr bool kPure = true; \
static void Invoke(CpuContext* cpu) { cpu->gpr[3] = static_cast<uint32_t>(fn(__VA_ARGS__)); } \
}

MKW_KNOWN_PURE_TYPED_NATIVE_R3(801A65AC, OS__DisableInterrupts_801a65ac);
MKW_KNOWN_PURE_TYPED_NATIVE_R3(801A65C0, OS__EnableInterrupts_801a65c0);
MKW_KNOWN_PURE_TYPED_NATIVE_R3(801A65D4, OS__RestoreInterrupts_801a65d4, static_cast<int32_t>(cpu->gpr[3]));

#undef MKW_KNOWN_PURE_TYPED_NATIVE_R3

#define MKW_KNOWN_NATIVE_CPU_CALL(addr, fn) \
template <> struct KnownNativeCpuCall<MKW_GADDR_CT(addr)> { \
static constexpr bool kAvailable = true; \
Expand Down
28 changes: 28 additions & 0 deletions runtime/include/perf_stats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <chrono>

// MKW_PERF_STATS=1: logs the game thread's busy time per presented frame, i.e. wall time minus the
// time it spends blocked on VI pacing, the GX worker or the audio mixer.
namespace PerfStats {

bool Enabled() noexcept;
void AddWait(std::chrono::steady_clock::duration wait) noexcept;
void OnFramePresented() noexcept;

class WaitScope {
public:
WaitScope() noexcept : m_start(Enabled() ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}) {}
~WaitScope() {
if (m_start != std::chrono::steady_clock::time_point{}) {
AddWait(std::chrono::steady_clock::now() - m_start);
}
}
WaitScope(const WaitScope&) = delete;
WaitScope& operator=(const WaitScope&) = delete;

private:
std::chrono::steady_clock::time_point m_start;
};

} // namespace PerfStats
2 changes: 2 additions & 0 deletions runtime/src/hle/audio/ax_mix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "ax_internal.h"

#include "abi_bridge.h"
#include "perf_stats.h"
#include "audio_backend.h"
#include "ax_mix_kernels.h"
#include "isa/big_endian.h"
Expand Down Expand Up @@ -122,6 +123,7 @@ class AXWii {

void JoinMix() {
if (m_mixThread.joinable()) {
PerfStats::WaitScope wait;
std::unique_lock<std::mutex> lock(m_mixMutex);
m_mixIdle.wait(lock, [this] { return !m_mixPending && !m_mixBusy; });
}
Expand Down
Loading
Loading