mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 17:16:47 -04:00
feature: add apple silicon native macOS support (#81)
* feature: add apple silicon native macOS support - #81 * (macos): Fix crash This fixes a crash when viewing the rear camera * fix(macos): keep interpolated presentation on main thread * fix(macos): supply Retro-WFC payload during setup * perf(windows): compile out flat-memory fallback check * remove duplicate smoke test * test(macos): name and focus host platform tests * fix(macos): validate Retro-WFC payload cache * fix(payload): preserve staged file access failures * Limit flat-page checks to variable-page hosts --------- Co-authored-by: patchzyy <64382339+patchzyy@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef NOMINMAX
|
||||
@@ -22,7 +23,7 @@
|
||||
// Forward declarations
|
||||
struct CpuContext;
|
||||
|
||||
// GuestFiberManager: each guest OSThread maps to a Windows Fiber. A scheduler fiber picks
|
||||
// GuestFiberManager: each guest OSThread maps to a host context. A scheduler context picks
|
||||
// which guest fiber runs; a real timer thread queues VI retraces at the VI cadence. Guest
|
||||
// threads only switch at explicit yield points (OSSleepThread, OSYieldThread, ...), matching
|
||||
// Wii cooperative semantics exactly.
|
||||
@@ -39,7 +40,7 @@ enum class ThreadState : uint32_t {
|
||||
|
||||
// Information about a guest fiber
|
||||
struct GuestFiber {
|
||||
void* fiber = nullptr; // Windows fiber handle
|
||||
void* fiber = nullptr; // Host context handle
|
||||
uint32_t entryPoint = 0; // Thread entry function
|
||||
uint32_t entryArg = 0; // Argument to entry function
|
||||
CpuContext cpuContext{}; // Saved CPU context for this fiber
|
||||
@@ -102,10 +103,6 @@ private:
|
||||
static void CALLBACK FiberProc(void* param);
|
||||
#else
|
||||
static void FiberProc(void* param);
|
||||
// libco's co_create() entry points take no argument (unlike CreateFiber's FiberProc(void*)),
|
||||
// so this trampoline reads the guest thread address staged by CreateGuestFiber() and forwards
|
||||
// into the (platform-neutral-bodied) FiberProc above. See fiber_manager.cpp.
|
||||
static void FiberProcTrampoline();
|
||||
#endif
|
||||
// Switch from whichever fiber is currently active straight to the scheduler fiber, without
|
||||
// the SwitchToThread bookkeeping (CPU context save/restore, s_currentGuestThread). Used for
|
||||
@@ -117,9 +114,8 @@ private:
|
||||
static std::mutex s_mutex;
|
||||
static std::unordered_map<uint32_t, GuestFiber> s_fibers;
|
||||
static std::vector<void*> s_fibersPendingDelete;
|
||||
// The scheduler's own "fiber": a Windows HFIBER, or (non-Windows) libco's cothread_t for
|
||||
// whichever native call stack first called GuestFiberManager::Initialize() - both are
|
||||
// plain void* handles, so one field serves both platforms.
|
||||
// The scheduler's own host context. Its opaque handle is supplied by the
|
||||
// active HostContext backend, so one field serves every supported host.
|
||||
static void* s_schedulerFiber;
|
||||
static uint32_t s_currentGuestThread;
|
||||
static bool s_initialized;
|
||||
@@ -135,4 +131,3 @@ private:
|
||||
extern std::atomic<uint32_t> g_viRetracePendingCount;
|
||||
|
||||
} // namespace Fiber
|
||||
|
||||
|
||||
@@ -14,10 +14,17 @@ namespace GuestFlat {
|
||||
// Fixed base so the emitted access is `[reg + imm64-in-register]` with no load
|
||||
// of a global.
|
||||
inline constexpr uint64_t kGuestSpaceSize = 0x1'0000'0000ull;
|
||||
inline constexpr size_t kGuestPageSize = 0x1000;
|
||||
#if defined(__x86_64__)
|
||||
// 16 TiB: clear of the Windows ASan shadow (32 TiB) and of the usual image/heap
|
||||
// placement.
|
||||
inline constexpr uintptr_t kFixedFlatGuestBase = 0x0000'1000'0000'0000ull;
|
||||
#elif defined(__aarch64__) && defined(__APPLE__)
|
||||
// Keep this well above the low address ranges that Darwin's ASLR may use for
|
||||
// a PIE executable and its shared cache. Apple Silicon's user VA is wider
|
||||
// than Linux's 39-bit minimum, so this 512 GiB region is available while the
|
||||
// Linux AArch64 target retains its 64 GiB placement below.
|
||||
inline constexpr uintptr_t kFixedFlatGuestBase = 0x0000'0080'0000'0000ull;
|
||||
#elif defined(__aarch64__)
|
||||
// 16 TiB (this arch's x86_64 sibling value) is unreachable on any AArch64
|
||||
// kernel configured for 39-bit virtual addresses (512 GiB ceiling) - common on
|
||||
@@ -58,6 +65,25 @@ struct FaultCounters {
|
||||
// True once the reservation exists and translated code may use the flat path.
|
||||
bool IsActive();
|
||||
|
||||
// True when a host VM page covers more than one 4 KiB Wii page. In that
|
||||
// configuration, guest-view page protection cannot safely represent per-Wii-
|
||||
// page MMIO, deferred-read, or executable-write state, so general translated
|
||||
// accesses must use the checked Memory::* path.
|
||||
// Windows user mode and x86-64 always use a 4 KiB base page, so those builds
|
||||
// fold this to a compile-time false: it appears in every flat access and must
|
||||
// not become a hot-path load. Only AArch64, where the page size is a kernel
|
||||
// configuration (4/16/64 KiB), has to probe it at runtime.
|
||||
#if defined(_WIN32) || defined(__x86_64__)
|
||||
#define MKW_GUEST_FLAT_FIXED_PAGE_SIZE 1
|
||||
#endif
|
||||
|
||||
#if defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE)
|
||||
inline constexpr bool RequiresCheckedAccess() noexcept { return false; }
|
||||
#else
|
||||
extern bool g_requiresCheckedAccess;
|
||||
inline bool RequiresCheckedAccess() noexcept { return g_requiresCheckedAccess; }
|
||||
#endif
|
||||
|
||||
// Reserves the 4 GiB space (once per process) and maps every requested region
|
||||
// into both views. Throws std::runtime_error with a precise diagnosis when the
|
||||
// reservation, the section objects or a view cannot be created - a silent
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
// HostContext is the deliberately small boundary between the guest scheduler
|
||||
// and the host's cooperative-context facility. Windows uses native Fibers and
|
||||
// Linux uses libco; macOS AArch64 uses the local assembly backend because it
|
||||
// must preserve Darwin's platform-reserved x18 register, which libco's AArch64
|
||||
// backend does not save. Its handles are only valid on the thread that
|
||||
// initialized the scheduler.
|
||||
namespace HostContext {
|
||||
|
||||
using Handle = void*;
|
||||
using Entry = void (*)(void*);
|
||||
|
||||
bool InitializeScheduler(Handle* scheduler);
|
||||
void ShutdownScheduler(Handle scheduler);
|
||||
|
||||
Handle Create(std::size_t stackSize, Entry entry, void* argument);
|
||||
void Destroy(Handle context);
|
||||
bool IsCurrent(Handle context);
|
||||
void Switch(Handle target);
|
||||
|
||||
} // namespace HostContext
|
||||
@@ -264,6 +264,8 @@ inline void PpcWritePairPsqInline(uint32_t addr, T first, T second)
|
||||
// reading stale bytes, and unmapped pages commit on demand, same as MemoryInline::Flat* loads.
|
||||
MKW_PPC_FORCE_INLINE const uint8_t* PpcTryGetPsqReadableHostInline(uint32_t addr)
|
||||
{
|
||||
if (GuestFlat::RequiresCheckedAccess()) [[unlikely]]
|
||||
return nullptr;
|
||||
return MKW_FLAT_GUEST_BASE + addr;
|
||||
}
|
||||
|
||||
@@ -274,6 +276,8 @@ MKW_PPC_FORCE_INLINE const uint8_t* PpcTryGetPsqReadableHostInline(uint32_t addr
|
||||
// executable, and unmapped pages still trap.
|
||||
MKW_PPC_FORCE_INLINE uint8_t* PpcTryGetPsqWritableHostInline(uint32_t addr)
|
||||
{
|
||||
if (GuestFlat::RequiresCheckedAccess()) [[unlikely]]
|
||||
return nullptr;
|
||||
if (addr > UINT32_MAX - 7u) [[unlikely]]
|
||||
return nullptr;
|
||||
if (MemoryInline::FlatWriteNeedsPolicy(addr) ||
|
||||
|
||||
@@ -231,6 +231,13 @@ MKW_MEMORY_FORCE_INLINE uint8_t* ResolveRangeHost(uint32_t base, int32_t minOffs
|
||||
(void)needsRead;
|
||||
const uint32_t guestStart = base + static_cast<uint32_t>(minOffset);
|
||||
if (length == 0 || length > kPageSize || guestStart > UINT32_MAX - (length - 1)) return nullptr;
|
||||
if (GuestFlat::RequiresCheckedAccess()) {
|
||||
// A host page can cover multiple independently-special Wii pages.
|
||||
// Returning null keeps resolved accesses on the checked Memory::*
|
||||
// path, which materializes deferred reads and applies write policy.
|
||||
(void)needsWrite;
|
||||
return nullptr;
|
||||
}
|
||||
if (needsWrite &&
|
||||
(FlatWriteNeedsPolicy(guestStart) || FlatWriteNeedsPolicy(guestStart + (length - 1))))
|
||||
[[unlikely]] return nullptr;
|
||||
@@ -522,6 +529,13 @@ MKW_MEMORY_FORCE_INLINE void WriteResolvedFloat64(uint8_t* r, uint32_t o, uint32
|
||||
// around `*(T*)(base + addr)`, no page-table load or limit check (interception model documented
|
||||
// in guest_flat_memory.h). The one exception kept inline is the MMIO write policy, since the
|
||||
// written value can't be recovered from a fault record.
|
||||
//
|
||||
// When a host VM page is larger than a 4 KiB Wii page, guest-view protections
|
||||
// cannot distinguish adjacent special Wii pages. The general FlatRead*/
|
||||
// FlatWrite* helpers then use the checked page-table path, which materializes
|
||||
// deferred reads and applies executable-write/MMIO policy before touching RAM.
|
||||
// FlatWriteRam* remains direct because the translator emits it only for
|
||||
// addresses it has proven are ordinary RAM.
|
||||
|
||||
template <typename T>
|
||||
MKW_MEMORY_FORCE_INLINE T FlatLoad(uint32_t address) {
|
||||
@@ -536,47 +550,62 @@ MKW_MEMORY_FORCE_INLINE void FlatStore(uint32_t address, T value) {
|
||||
std::memcpy(MKW_FLAT_GUEST_BASE + address, &swapped, sizeof(T));
|
||||
}
|
||||
|
||||
MKW_MEMORY_FORCE_INLINE uint8_t FlatRead8(uint32_t address) { return FlatLoad<uint8_t>(address); }
|
||||
MKW_MEMORY_FORCE_INLINE uint16_t FlatRead16(uint32_t address) { return FlatLoad<uint16_t>(address); }
|
||||
MKW_MEMORY_FORCE_INLINE uint32_t FlatRead32(uint32_t address) { return FlatLoad<uint32_t>(address); }
|
||||
MKW_MEMORY_FORCE_INLINE uint8_t FlatRead8(uint32_t address) {
|
||||
if (GuestFlat::RequiresCheckedAccess()) return Memory::Read8(address);
|
||||
return FlatLoad<uint8_t>(address);
|
||||
}
|
||||
MKW_MEMORY_FORCE_INLINE uint16_t FlatRead16(uint32_t address) {
|
||||
if (GuestFlat::RequiresCheckedAccess()) return Memory::Read16(address);
|
||||
return FlatLoad<uint16_t>(address);
|
||||
}
|
||||
MKW_MEMORY_FORCE_INLINE uint32_t FlatRead32(uint32_t address) {
|
||||
if (GuestFlat::RequiresCheckedAccess()) return Memory::Read32(address);
|
||||
return FlatLoad<uint32_t>(address);
|
||||
}
|
||||
|
||||
MKW_MEMORY_FORCE_INLINE float FlatReadFloat32(uint32_t address) {
|
||||
const uint32_t bits = FlatLoad<uint32_t>(address);
|
||||
const uint32_t bits = FlatRead32(address);
|
||||
float value = 0.0f;
|
||||
std::memcpy(&value, &bits, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
MKW_MEMORY_FORCE_INLINE double FlatReadFloat64(uint32_t address) {
|
||||
const uint64_t bits = FlatLoad<uint64_t>(address);
|
||||
const uint64_t bits = GuestFlat::RequiresCheckedAccess()
|
||||
? Memory::Read64(address) : FlatLoad<uint64_t>(address);
|
||||
double value = 0.0;
|
||||
std::memcpy(&value, &bits, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
MKW_MEMORY_FORCE_INLINE void FlatWrite8(uint32_t address, uint8_t value) {
|
||||
if (GuestFlat::RequiresCheckedAccess()) { Memory::Write8(address, value); return; }
|
||||
if (FlatWriteNeedsPolicy(address)) [[unlikely]] { Write8Slow(address, value); return; }
|
||||
FlatStore<uint8_t>(address, value);
|
||||
}
|
||||
|
||||
MKW_MEMORY_FORCE_INLINE void FlatWrite16(uint32_t address, uint16_t value) {
|
||||
if (GuestFlat::RequiresCheckedAccess()) { Memory::Write16(address, value); return; }
|
||||
if (FlatWriteNeedsPolicy(address)) [[unlikely]] { Write16Slow(address, value); return; }
|
||||
FlatStore<uint16_t>(address, value);
|
||||
}
|
||||
|
||||
MKW_MEMORY_FORCE_INLINE void FlatWrite32(uint32_t address, uint32_t value) {
|
||||
if (GuestFlat::RequiresCheckedAccess()) { Memory::Write32(address, value); return; }
|
||||
if (FlatWriteNeedsPolicy(address)) [[unlikely]] { Write32Slow(address, value); return; }
|
||||
FlatStore<uint32_t>(address, value);
|
||||
}
|
||||
|
||||
|
||||
MKW_MEMORY_FORCE_INLINE void FlatWriteFloat32(uint32_t address, double value) {
|
||||
if (GuestFlat::RequiresCheckedAccess()) { Memory::WriteFloat32(address, value); return; }
|
||||
const uint32_t bits = ConvertPpcDoubleToSingleBits(value);
|
||||
if (FlatWriteNeedsPolicy(address)) [[unlikely]] { WriteFloat32Slow(address, value); return; }
|
||||
FlatStore<uint32_t>(address, bits);
|
||||
}
|
||||
|
||||
MKW_MEMORY_FORCE_INLINE void FlatWriteFloat64(uint32_t address, double value) {
|
||||
if (GuestFlat::RequiresCheckedAccess()) { Memory::WriteFloat64(address, value); return; }
|
||||
uint64_t bits = 0;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
if (FlatWriteNeedsPolicy(address)) [[unlikely]] { WriteFloat64Slow(address, value); return; }
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
// Small host-services boundary for functionality that must not leak Win32
|
||||
// assumptions into runtime or game code. Guest execution, virtual memory, and
|
||||
// cooperative contexts remain outside this layer until dedicated macOS
|
||||
// prototypes establish a safe abstraction.
|
||||
namespace RuntimePlatform {
|
||||
|
||||
std::optional<std::filesystem::path> ExecutableDirectory() noexcept;
|
||||
|
||||
// Returns the platform's conventional per-user application-data directory.
|
||||
// It does not create the directory, leaving that policy to the caller.
|
||||
std::filesystem::path ApplicationDataDirectory(std::string_view applicationName);
|
||||
|
||||
// The root for per-run diagnostics. Keeping this here ensures log placement
|
||||
// follows the same host convention as configuration and other user data.
|
||||
std::filesystem::path LogDirectory(std::string_view applicationName);
|
||||
|
||||
uint64_t CurrentProcessId() noexcept;
|
||||
|
||||
} // namespace RuntimePlatform
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <toml.hpp>
|
||||
#include "platform/host_platform.h"
|
||||
#ifdef _WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@@ -141,7 +142,14 @@ inline bool IsSupportedResolutionMultiplier(float value) {
|
||||
// Must stay in step with the backend table in main.cpp, which is what actually
|
||||
// maps these to AuroraBackend.
|
||||
inline bool IsSupportedGraphicsApi(std::string_view value) {
|
||||
#if defined(__APPLE__)
|
||||
static constexpr std::array<std::string_view, 2> values{"auto", "metal"};
|
||||
// only vulkan for linux
|
||||
#elif defined(__linux__)
|
||||
static constexpr std::array<std::string_view, 2> values{"auto", "vulkan"};
|
||||
#elif defined(_WIN32)
|
||||
static constexpr std::array<std::string_view, 3> values{"auto", "d3d12", "vulkan"};
|
||||
#endif
|
||||
return std::find(values.begin(), values.end(), value) != values.end();
|
||||
}
|
||||
|
||||
@@ -172,6 +180,8 @@ inline std::optional<std::filesystem::path> ExecutableDirectory() {
|
||||
}
|
||||
buffer.resize(buffer.size() * 2);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
return RuntimePlatform::ExecutableDirectory();
|
||||
#else
|
||||
// /proc/self/exe is a Linux-specific magic symlink to the running executable; readlink()
|
||||
// does not NUL-terminate and silently truncates if the buffer is too small, so this grows
|
||||
@@ -229,6 +239,8 @@ inline std::filesystem::path ApplicationDataDirectory() {
|
||||
CoTaskMemFree(rawPath);
|
||||
return directory;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
return RuntimePlatform::ApplicationDataDirectory(kApplicationDirectoryName);
|
||||
#else
|
||||
// XDG Base Directory spec equivalent of FOLDERID_LocalAppData: $XDG_DATA_HOME if set and
|
||||
// non-empty, otherwise its default of $HOME/.local/share.
|
||||
|
||||
@@ -36,6 +36,7 @@ extern thread_local uint32_t g_sehLastAccessType;
|
||||
|
||||
void WriteFatalLog(std::string_view reason);
|
||||
void SetRuntimeExitCode(int code);
|
||||
void MarkFatalErrorReported();
|
||||
|
||||
// Centralized crash reporting (defined in main.cpp). Every fatal path funnels
|
||||
// through these so the per-run log folder always receives the same artifact
|
||||
|
||||
Reference in New Issue
Block a user