mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-11 09:25:05 -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:
+24
-139
@@ -2,6 +2,7 @@
|
||||
#include "memory.h"
|
||||
#include "abi_bridge.h"
|
||||
#include "hle_stubs.h"
|
||||
#include "host_context.h"
|
||||
#include "runtime_log.h"
|
||||
|
||||
// Defined in hle/os/os_sleep.cpp; the sleep-timer table is file-local there.
|
||||
@@ -13,24 +14,8 @@
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include "libco.h"
|
||||
#endif
|
||||
|
||||
namespace Fiber {
|
||||
|
||||
#if !defined(_WIN32)
|
||||
namespace {
|
||||
// libco's co_create() entry points take no argument, unlike CreateFiber(size, FiberProc, param).
|
||||
// CreateGuestFiber() stages the guest thread address here immediately before the first co_switch
|
||||
// into a freshly created cothread; FiberProcTrampoline reads it exactly once, at the top of the
|
||||
// fiber's very first activation. Safe because guest fibers are strictly cooperative on a single
|
||||
// OS thread: nothing else can run (and so nothing else can overwrite this) between the staging
|
||||
// write and the trampoline's read of it.
|
||||
thread_local uint32_t s_pendingFiberArg = 0;
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
std::mutex GuestFiberManager::s_mutex;
|
||||
std::unordered_map<uint32_t, GuestFiber> GuestFiberManager::s_fibers;
|
||||
std::vector<void*> GuestFiberManager::s_fibersPendingDelete;
|
||||
@@ -45,21 +30,11 @@ void GuestFiberManager::PurgePendingFibers() {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
toDelete.swap(s_fibersPendingDelete);
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
const void* current = GetCurrentFiber();
|
||||
for (void* f : toDelete) {
|
||||
if (f && f != current) {
|
||||
DeleteFiber(f);
|
||||
if (f && !HostContext::IsCurrent(f)) {
|
||||
HostContext::Destroy(f);
|
||||
}
|
||||
}
|
||||
#else
|
||||
const void* current = co_active();
|
||||
for (void* f : toDelete) {
|
||||
if (f && f != current) {
|
||||
co_delete(static_cast<cothread_t>(f));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Global VI retrace counter
|
||||
@@ -212,27 +187,13 @@ void GuestFiberManager::Initialize() {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Convert the main thread to a fiber (the scheduler fiber)
|
||||
s_schedulerFiber = ConvertThreadToFiber(nullptr);
|
||||
if (!s_schedulerFiber) {
|
||||
// May already be a fiber
|
||||
s_schedulerFiber = GetCurrentFiber();
|
||||
if (!s_schedulerFiber) {
|
||||
RT_LOG(RT_TAG_OS) << "FATAL: Failed to initialize scheduler fiber!" << std::endl;
|
||||
ShowRuntimeFatalPopup("guest scheduler initialization failed",
|
||||
"Windows could not create the scheduler fiber required to run guest threads.");
|
||||
std::abort();
|
||||
}
|
||||
if (!HostContext::InitializeScheduler(&s_schedulerFiber)) {
|
||||
RT_LOG(RT_TAG_OS) << "FATAL: Failed to initialize scheduler context!" << std::endl;
|
||||
ShowRuntimeFatalPopup("guest scheduler initialization failed",
|
||||
"The host could not create the scheduler context required to run guest threads.");
|
||||
std::abort();
|
||||
}
|
||||
|
||||
#else
|
||||
// co_active() returns a handle for whichever native stack is currently running, creating one
|
||||
// on first call if needed - the libco analogue of ConvertThreadToFiber(nullptr): it converts
|
||||
// this call's own stack into a switchable target without altering control flow.
|
||||
s_schedulerFiber = co_active();
|
||||
#endif
|
||||
|
||||
s_currentGuestThread = 0;
|
||||
s_initialized = true;
|
||||
}
|
||||
@@ -240,32 +201,18 @@ void GuestFiberManager::Initialize() {
|
||||
void GuestFiberManager::Shutdown() {
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
|
||||
#if defined(_WIN32)
|
||||
for (auto& [addr, fiber] : s_fibers) {
|
||||
if (fiber.fiber && !fiber.isSchedulerFiber) {
|
||||
DeleteFiber(fiber.fiber);
|
||||
HostContext::Destroy(fiber.fiber);
|
||||
fiber.fiber = nullptr;
|
||||
}
|
||||
}
|
||||
s_fibers.clear();
|
||||
|
||||
// Convert scheduler fiber back to thread
|
||||
if (s_schedulerFiber) {
|
||||
ConvertFiberToThread();
|
||||
HostContext::ShutdownScheduler(s_schedulerFiber);
|
||||
s_schedulerFiber = nullptr;
|
||||
}
|
||||
#else
|
||||
for (auto& [addr, fiber] : s_fibers) {
|
||||
if (fiber.fiber && !fiber.isSchedulerFiber) {
|
||||
co_delete(static_cast<cothread_t>(fiber.fiber));
|
||||
fiber.fiber = nullptr;
|
||||
}
|
||||
}
|
||||
s_fibers.clear();
|
||||
// Unlike ConvertFiberToThread, libco has no "undo" for co_active(): the scheduler's own
|
||||
// stack was never separately allocated, so there is nothing to release here.
|
||||
s_schedulerFiber = nullptr;
|
||||
#endif
|
||||
|
||||
s_initialized = false;
|
||||
}
|
||||
@@ -288,11 +235,7 @@ bool GuestFiberManager::CreateGuestFiber(uint32_t guestThreadAddr, uint32_t entr
|
||||
if (existingIt != s_fibers.end()) {
|
||||
// Delete the old fiber if it exists and is not the scheduler fiber
|
||||
if (existingIt->second.fiber && !existingIt->second.isSchedulerFiber) {
|
||||
#if defined(_WIN32)
|
||||
DeleteFiber(existingIt->second.fiber);
|
||||
#else
|
||||
co_delete(static_cast<cothread_t>(existingIt->second.fiber));
|
||||
#endif
|
||||
HostContext::Destroy(existingIt->second.fiber);
|
||||
}
|
||||
s_fibers.erase(existingIt);
|
||||
}
|
||||
@@ -312,31 +255,17 @@ bool GuestFiberManager::CreateGuestFiber(uint32_t guestThreadAddr, uint32_t entr
|
||||
gf.cpuContext.pc = entryPoint;
|
||||
gf.cpuContext.srr0 = entryPoint;
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Create Windows fiber with reasonable stack size
|
||||
// Use host stack size (64KB should be plenty for translated code)
|
||||
// The host stack models only translated host calls; the guest stack starts
|
||||
// at stackBase in the CPU context above.
|
||||
constexpr size_t kHostStackSize = 64 * 1024;
|
||||
gf.fiber = CreateFiber(kHostStackSize, FiberProc, reinterpret_cast<void*>(static_cast<uintptr_t>(guestThreadAddr)));
|
||||
gf.fiber = HostContext::Create(kHostStackSize, FiberProc,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(guestThreadAddr)));
|
||||
|
||||
if (!gf.fiber) {
|
||||
DWORD err = GetLastError();
|
||||
RT_LOG(RT_TAG_OS) << "CreateFiber failed for thread 0x"
|
||||
<< std::hex << guestThreadAddr
|
||||
<< " error=" << std::dec << err << std::endl;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
// libco's co_create() entry point takes no argument; SwitchToThread() stages guestThreadAddr
|
||||
// into s_pendingFiberArg immediately before the co_switch that first activates this handle.
|
||||
constexpr unsigned int kHostStackSize = 64 * 1024;
|
||||
gf.fiber = co_create(kHostStackSize, &FiberProcTrampoline);
|
||||
|
||||
if (!gf.fiber) {
|
||||
RT_LOG(RT_TAG_OS) << "co_create failed for thread 0x"
|
||||
RT_LOG(RT_TAG_OS) << "Failed to create host context for thread 0x"
|
||||
<< std::hex << guestThreadAddr << std::dec << std::endl;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
s_fibers[guestThreadAddr] = gf;
|
||||
|
||||
@@ -390,24 +319,11 @@ void GuestFiberManager::ExitGuestThread(uint32_t guestThreadAddr, ThreadState fi
|
||||
}
|
||||
|
||||
if (it->second.fiber && !it->second.isSchedulerFiber) {
|
||||
#if defined(_WIN32)
|
||||
const void* current = GetCurrentFiber();
|
||||
if (it->second.fiber == current) {
|
||||
if (HostContext::IsCurrent(it->second.fiber)) {
|
||||
s_fibersPendingDelete.push_back(it->second.fiber);
|
||||
} else {
|
||||
DeleteFiber(it->second.fiber);
|
||||
HostContext::Destroy(it->second.fiber);
|
||||
}
|
||||
#else
|
||||
const void* current = co_active();
|
||||
if (it->second.fiber == current) {
|
||||
// Deleting the coroutine we're currently executing on would free the very stack
|
||||
// this call is running on; defer it (PurgePendingFibers) until some other fiber is
|
||||
// active, exactly like the Windows branch above.
|
||||
s_fibersPendingDelete.push_back(it->second.fiber);
|
||||
} else {
|
||||
co_delete(static_cast<cothread_t>(it->second.fiber));
|
||||
}
|
||||
#endif
|
||||
it->second.fiber = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -474,12 +390,7 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu
|
||||
|
||||
// Check if we're already on the target fiber (e.g., switching to main thread
|
||||
// when we're already on the scheduler fiber)
|
||||
#if defined(_WIN32)
|
||||
void* currentFiber = GetCurrentFiber();
|
||||
#else
|
||||
void* currentFiber = co_active();
|
||||
#endif
|
||||
if (currentFiber == fiberHandle) {
|
||||
if (HostContext::IsCurrent(fiberHandle)) {
|
||||
// Already executing on the target host fiber. This is common for the
|
||||
// default guest thread, which also owns the scheduler fiber. Keep the
|
||||
// live CPU context instead of restoring a possibly stale saved copy
|
||||
@@ -496,15 +407,7 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu
|
||||
}
|
||||
|
||||
// Switch to the target fiber (the target fiber will load its own context)
|
||||
#if defined(_WIN32)
|
||||
SwitchToFiber(fiberHandle);
|
||||
#else
|
||||
// Staged for FiberProcTrampoline's first (and only) read; a no-op for a fiber that has
|
||||
// already started, since resuming it re-enters mid-function rather than through the
|
||||
// trampoline's entry point.
|
||||
s_pendingFiberArg = guestThreadAddr;
|
||||
co_switch(static_cast<cothread_t>(fiberHandle));
|
||||
#endif
|
||||
HostContext::Switch(fiberHandle);
|
||||
|
||||
// When we return here, the fiber that issued SwitchToThread has resumed.
|
||||
// That does not automatically mean the previous guest thread became runnable
|
||||
@@ -618,14 +521,6 @@ void GuestFiberManager::ProcessTimerEvents(CpuContext* cpu) {
|
||||
}
|
||||
}
|
||||
|
||||
void GuestFiberManager::SwitchToScheduler() {
|
||||
#if defined(_WIN32)
|
||||
SwitchToFiber(s_schedulerFiber);
|
||||
#else
|
||||
co_switch(static_cast<cothread_t>(s_schedulerFiber));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
void CALLBACK GuestFiberManager::FiberProc(void* param)
|
||||
#else
|
||||
@@ -634,6 +529,7 @@ void GuestFiberManager::FiberProc(void* param)
|
||||
{
|
||||
uint32_t guestThreadAddr = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(param));
|
||||
|
||||
|
||||
// Get our fiber info
|
||||
GuestFiber* fiber = nullptr;
|
||||
uint32_t entryPoint = 0;
|
||||
@@ -644,7 +540,7 @@ void GuestFiberManager::FiberProc(void* param)
|
||||
auto it = s_fibers.find(guestThreadAddr);
|
||||
if (it == s_fibers.end()) {
|
||||
RT_LOG(RT_TAG_OS) << "FiberProc: fiber not found!" << std::endl;
|
||||
SwitchToScheduler();
|
||||
HostContext::Switch(s_schedulerFiber);
|
||||
return;
|
||||
}
|
||||
fiber = &it->second;
|
||||
@@ -707,7 +603,7 @@ void GuestFiberManager::FiberProc(void* param)
|
||||
<< ", fn=0x" << startFn << ") after retries; continuing anyway." << std::dec << std::endl;
|
||||
break;
|
||||
}
|
||||
SwitchToScheduler();
|
||||
HostContext::Switch(s_schedulerFiber);
|
||||
}
|
||||
|
||||
// The deferral loop above yields to the scheduler and therefore can resume
|
||||
@@ -764,18 +660,7 @@ void GuestFiberManager::FiberProc(void* param)
|
||||
}
|
||||
|
||||
// Return to scheduler
|
||||
SwitchToScheduler();
|
||||
HostContext::Switch(s_schedulerFiber);
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
void GuestFiberManager::FiberProcTrampoline() {
|
||||
const uint32_t guestThreadAddr = s_pendingFiberArg;
|
||||
FiberProc(reinterpret_cast<void*>(static_cast<uintptr_t>(guestThreadAddr)));
|
||||
// FiberProc always calls SwitchToScheduler() on every exit path and never falls off its own
|
||||
// end; this is only a safety net in case that ever changes; falling off co_create's entry
|
||||
// function is otherwise undefined behavior (libco's own crash() fallback aborts instead).
|
||||
SwitchToScheduler();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Fiber
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#endif
|
||||
|
||||
namespace GuestFlat {
|
||||
#if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE)
|
||||
bool g_requiresCheckedAccess = false;
|
||||
#endif
|
||||
namespace {
|
||||
|
||||
#if defined(_WIN32)
|
||||
@@ -48,6 +51,16 @@ constexpr DWORD kMemPreservePlaceholder = 0x00000002;
|
||||
constexpr size_t kAllocationGranularity = 0x10000; // 64 KiB
|
||||
constexpr size_t kHostPageSize = 0x1000;
|
||||
|
||||
// Only hosts that can expose a page larger than 4 KiB need to discover their
|
||||
// size at runtime; see RequiresCheckedAccess() in guest_flat_memory.h.
|
||||
#if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE)
|
||||
size_t HostPageSize()
|
||||
{
|
||||
const long size = sysconf(_SC_PAGESIZE);
|
||||
return size > 0 ? static_cast<size_t>(size) : kGuestPageSize;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Named, platform-neutral protection modes so every fault-interception call site below (the
|
||||
// MMIO window, the executable-write guard, deferred-EFB-read protection, the on-demand
|
||||
// unmapped-block commit) can stay identical text on both platforms; only ProtectRange() and
|
||||
@@ -349,7 +362,7 @@ bool IsMmio(uint32_t address) { return MemoryInline::IsMmioAddress(address); }
|
||||
bool IsGpuFifo(uint32_t address) { return MemoryInline::IsGpuFifoAddress(address); }
|
||||
|
||||
void ApplyExecutableProtectionLocked() {
|
||||
if (g_base == nullptr) return;
|
||||
if (g_base == nullptr || RequiresCheckedAccess()) return;
|
||||
auto& protectedPages = ExecutableProtectedPages();
|
||||
for (const auto& range : ExecutableRanges()) {
|
||||
// Only pages fully inside the range are protected: edge pages often share a page with data
|
||||
@@ -497,6 +510,10 @@ bool IsActive() {
|
||||
void Initialize(const std::vector<RegionRequest>& regions) {
|
||||
std::lock_guard<std::mutex> lock(StateMutex());
|
||||
|
||||
#if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE)
|
||||
g_requiresCheckedAccess = HostPageSize() > kGuestPageSize;
|
||||
#endif
|
||||
|
||||
if (g_initialized) {
|
||||
if (!SameLayout(g_activeRegions, regions)) {
|
||||
throw std::runtime_error(
|
||||
@@ -615,7 +632,7 @@ uint8_t* HostPointer(uint32_t guestAddress) {
|
||||
}
|
||||
|
||||
void ProtectDeferredRange(uint32_t address, size_t length) {
|
||||
if (!g_initialized || length == 0) return;
|
||||
if (RequiresCheckedAccess() || !g_initialized || length == 0) return;
|
||||
const uint64_t end = static_cast<uint64_t>(address) + length;
|
||||
if (end > kGuestSpaceSize) return;
|
||||
std::lock_guard<std::mutex> lock(StateMutex());
|
||||
@@ -630,7 +647,7 @@ void ProtectDeferredRange(uint32_t address, size_t length) {
|
||||
}
|
||||
|
||||
void UnprotectDeferredRange(uint32_t address, size_t length) {
|
||||
if (!g_initialized || length == 0) return;
|
||||
if (RequiresCheckedAccess() || !g_initialized || length == 0) return;
|
||||
std::lock_guard<std::mutex> lock(StateMutex());
|
||||
auto& ranges = DeferredRanges();
|
||||
const uint64_t end = static_cast<uint64_t>(address) + length;
|
||||
@@ -645,7 +662,7 @@ void UnprotectDeferredRange(uint32_t address, size_t length) {
|
||||
}
|
||||
|
||||
void RegisterExecutableRange(uint32_t start, uint32_t end) {
|
||||
if (end <= start) return;
|
||||
if (RequiresCheckedAccess() || end <= start) return;
|
||||
std::lock_guard<std::mutex> lock(StateMutex());
|
||||
auto& ranges = ExecutableRanges();
|
||||
if (std::any_of(ranges.begin(), ranges.end(), [&](const GuardedRange& range) {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "guest_flat_memory.h"
|
||||
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace GuestFlat {
|
||||
bool g_requiresCheckedAccess = false;
|
||||
namespace {
|
||||
struct Mapping { uint32_t base; uint64_t size; uint8_t* host; };
|
||||
std::mutex g_mutex;
|
||||
std::vector<Mapping> g_mappings;
|
||||
std::vector<RegionRequest> g_layout;
|
||||
uint8_t* g_base = nullptr;
|
||||
bool g_active = false;
|
||||
|
||||
uint64_t Offset(const RegionRequest& r) {
|
||||
if (r.backing == Backing::Mem1) return r.base & 0x1fffffffu;
|
||||
if (r.backing == Backing::Mem2) return (r.base & 0x1fffffffu) - 0x10000000u;
|
||||
return 0;
|
||||
}
|
||||
bool Same(const std::vector<RegionRequest>& a, const std::vector<RegionRequest>& b) {
|
||||
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(),
|
||||
[](const auto& x, const auto& y) { return x.base == y.base && x.size == y.size && x.backing == y.backing; });
|
||||
}
|
||||
int BackingFile(size_t size) {
|
||||
char name[] = "/tmp/wiicompiled-guest-XXXXXX";
|
||||
const int fd = mkstemp(name);
|
||||
if (fd >= 0) { unlink(name); if (ftruncate(fd, static_cast<off_t>(size)) != 0) { close(fd); return -1; } }
|
||||
return fd;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool IsActive() { return g_active; }
|
||||
void Initialize(const std::vector<RegionRequest>& regions) {
|
||||
std::lock_guard lock(g_mutex);
|
||||
g_requiresCheckedAccess = static_cast<size_t>(getpagesize()) > kGuestPageSize;
|
||||
if (g_active) { if (!Same(g_layout, regions)) throw std::runtime_error("flat guest layout cannot be remapped"); return; }
|
||||
mach_vm_address_t address = kFixedFlatGuestBase;
|
||||
if (mach_vm_allocate(mach_task_self(), &address, kGuestSpaceSize, VM_FLAGS_FIXED) != KERN_SUCCESS || address != kFixedFlatGuestBase)
|
||||
throw std::runtime_error("unable to reserve fixed 4 GiB macOS guest address space");
|
||||
g_base = reinterpret_cast<uint8_t*>(address);
|
||||
struct Store { Backing kind; uint32_t owned; uint64_t size; int fd; };
|
||||
std::vector<Store> stores;
|
||||
for (const auto& r : regions) {
|
||||
if (!r.size) continue;
|
||||
const uint32_t owned = r.backing == Backing::Owned ? r.base : 0;
|
||||
auto it = std::find_if(stores.begin(), stores.end(), [&](const Store& s) { return s.kind == r.backing && s.owned == owned; });
|
||||
const uint64_t need = Offset(r) + r.size;
|
||||
if (it == stores.end()) stores.push_back({r.backing, owned, need, -1}); else it->size = std::max(it->size, need);
|
||||
}
|
||||
for (auto& s : stores) { s.fd = BackingFile(s.size); if (s.fd < 0) throw std::runtime_error("unable to create macOS guest backing store"); }
|
||||
for (const auto& r : regions) {
|
||||
if (!r.size) continue;
|
||||
const uint32_t owned = r.backing == Backing::Owned ? r.base : 0;
|
||||
const auto& s = *std::find_if(stores.begin(), stores.end(), [&](const Store& x) { return x.kind == r.backing && x.owned == owned; });
|
||||
auto* host = static_cast<uint8_t*>(mmap(nullptr, r.size, PROT_READ | PROT_WRITE, MAP_SHARED, s.fd, Offset(r)));
|
||||
auto* guest = mmap(g_base + r.base, r.size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, s.fd, Offset(r));
|
||||
if (host == MAP_FAILED || guest != g_base + r.base) throw std::runtime_error("unable to map macOS guest alias");
|
||||
g_mappings.push_back({r.base, r.size, host});
|
||||
}
|
||||
for (auto& s : stores) close(s.fd);
|
||||
g_layout = regions; g_active = true;
|
||||
}
|
||||
uint8_t* HostPointer(uint32_t a) { for (const auto& m : g_mappings) if (a >= m.base && uint64_t(a - m.base) < m.size) return m.host + (a - m.base); return nullptr; }
|
||||
void ProtectDeferredRange(uint32_t, size_t) {}
|
||||
void UnprotectDeferredRange(uint32_t, size_t) {}
|
||||
void RegisterExecutableRange(uint32_t, uint32_t) {}
|
||||
FaultCounters Counters() { return {}; }
|
||||
void LogFaultSummary() noexcept {}
|
||||
bool HandleAccessViolation(void*, bool) noexcept { return false; }
|
||||
} // namespace GuestFlat
|
||||
@@ -394,3 +394,4 @@ extern std::mutex g_tlutObjMutex;
|
||||
// that happens on another key: unordered_map keeps references valid across a
|
||||
// rehash, an open-addressed table would not.
|
||||
extern std::unordered_map<uint32_t, TexObjSlot> g_TexObjMeta;
|
||||
extern std::map<uint32_t, TlutObjMeta> g_TlutObjMeta;
|
||||
|
||||
@@ -33,6 +33,15 @@ void WriteGuestFloat(uint32_t addr, float value, const char* label) {
|
||||
|
||||
void* GuestToHostPtr(uint32_t addr, size_t len) {
|
||||
if (addr == 0) return nullptr;
|
||||
#if defined(__APPLE__)
|
||||
// The macOS flat guest map exposes separate host aliases for cached,
|
||||
// uncached, and physical MEM1/MEM2 addresses. GX resources are identified
|
||||
// by their host pointer, so all aliases of one guest allocation must use
|
||||
// the same physical mapping before they reach Aurora. Kept macOS-only:
|
||||
// changing which alias the other hosts hand out would re-key their existing
|
||||
// GX resource identity.
|
||||
addr = CanonicalizeGxMainRamAddress(addr);
|
||||
#endif
|
||||
try { return Memory::GetPointer(addr, len); } catch (const Memory::AccessViolation& e) { LogMemoryError(RT_TAG_GX, "GX guest pointer", e); return nullptr; }
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "aurora_events.h"
|
||||
#include "settings_overlay.h"
|
||||
#include "fiber_manager.h"
|
||||
#include "platform/host_platform.h"
|
||||
#include "runtime_log.h"
|
||||
|
||||
#include <dolphin/vi.h>
|
||||
@@ -20,18 +21,15 @@
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
// Forward declaration for OSWakeupThread - used to wake threads on VI retrace queue
|
||||
extern "C" void OSWakeupThread_HLE_801aaaa4(CpuContext* ctx);
|
||||
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "host_context.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__) && defined(__aarch64__)
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern "C" void mkw_co_switch(void** targetSp, void** sourceSp);
|
||||
extern "C" void* mkw_co_init(void* stackTop, void (*entry)(void*), void* argument);
|
||||
#elif defined(__linux__)
|
||||
#include <libco.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <unordered_map>
|
||||
#else
|
||||
#error "HostContext needs a supported cooperative-context backend"
|
||||
#endif
|
||||
|
||||
namespace HostContext {
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
namespace {
|
||||
thread_local bool g_convertedScheduler = false;
|
||||
}
|
||||
|
||||
bool InitializeScheduler(Handle* scheduler)
|
||||
{
|
||||
void* context = ConvertThreadToFiber(nullptr);
|
||||
g_convertedScheduler = context != nullptr;
|
||||
if (!context) {
|
||||
context = GetCurrentFiber();
|
||||
}
|
||||
*scheduler = context;
|
||||
return context != nullptr;
|
||||
}
|
||||
|
||||
void ShutdownScheduler(Handle scheduler)
|
||||
{
|
||||
if (scheduler && g_convertedScheduler) {
|
||||
ConvertFiberToThread();
|
||||
}
|
||||
g_convertedScheduler = false;
|
||||
}
|
||||
|
||||
Handle Create(std::size_t stackSize, Entry entry, void* argument)
|
||||
{
|
||||
return CreateFiber(stackSize, entry, argument);
|
||||
}
|
||||
|
||||
void Destroy(Handle context)
|
||||
{
|
||||
if (context) {
|
||||
DeleteFiber(context);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCurrent(Handle context)
|
||||
{
|
||||
return context != nullptr && GetCurrentFiber() == context;
|
||||
}
|
||||
|
||||
void Switch(Handle target)
|
||||
{
|
||||
SwitchToFiber(target);
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__) && defined(__aarch64__)
|
||||
|
||||
namespace {
|
||||
struct Context {
|
||||
void* savedStackPointer = nullptr;
|
||||
void* stack = nullptr;
|
||||
std::size_t stackSize = 0;
|
||||
};
|
||||
|
||||
// Guest scheduling is confined to the initialized main host thread. Keeping
|
||||
// this as ordinary process state also avoids relying on Darwin TLS internals
|
||||
// while executing on a manually managed stack.
|
||||
Context* g_current = nullptr;
|
||||
}
|
||||
|
||||
bool InitializeScheduler(Handle* scheduler)
|
||||
{
|
||||
auto* context = new Context();
|
||||
g_current = context;
|
||||
*scheduler = context;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShutdownScheduler(Handle scheduler)
|
||||
{
|
||||
auto* context = static_cast<Context*>(scheduler);
|
||||
if (g_current == context) {
|
||||
g_current = nullptr;
|
||||
}
|
||||
delete context;
|
||||
}
|
||||
|
||||
Handle Create(std::size_t stackSize, Entry entry, void* argument)
|
||||
{
|
||||
auto* context = new Context();
|
||||
const std::size_t guardSize = static_cast<std::size_t>(getpagesize());
|
||||
const std::size_t totalSize = stackSize + guardSize;
|
||||
context->stack = mmap(nullptr, totalSize, PROT_READ | PROT_WRITE,
|
||||
MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
if (context->stack == MAP_FAILED) {
|
||||
delete context;
|
||||
return nullptr;
|
||||
}
|
||||
// Fault on stack overflow instead of corrupting the preceding mapping.
|
||||
if (mprotect(context->stack, guardSize, PROT_NONE) != 0) {
|
||||
munmap(context->stack, totalSize);
|
||||
delete context;
|
||||
return nullptr;
|
||||
}
|
||||
context->stackSize = totalSize;
|
||||
|
||||
auto* stackTop = static_cast<char*>(context->stack) + totalSize;
|
||||
context->savedStackPointer = mkw_co_init(stackTop, entry, argument);
|
||||
return context;
|
||||
}
|
||||
|
||||
void Destroy(Handle context)
|
||||
{
|
||||
auto* nativeContext = static_cast<Context*>(context);
|
||||
if (!nativeContext) {
|
||||
return;
|
||||
}
|
||||
if (nativeContext->stack) {
|
||||
munmap(nativeContext->stack, nativeContext->stackSize);
|
||||
}
|
||||
delete nativeContext;
|
||||
}
|
||||
|
||||
bool IsCurrent(Handle context)
|
||||
{
|
||||
return context != nullptr && context == g_current;
|
||||
}
|
||||
|
||||
void Switch(Handle target)
|
||||
{
|
||||
auto* destination = static_cast<Context*>(target);
|
||||
Context* source = g_current;
|
||||
if (!destination || destination == source) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_current = destination;
|
||||
mkw_co_switch(&destination->savedStackPointer, &source->savedStackPointer);
|
||||
g_current = source;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
namespace {
|
||||
struct Context {
|
||||
cothread_t native = nullptr;
|
||||
Entry entry = nullptr;
|
||||
void* argument = nullptr;
|
||||
bool ownsNative = false;
|
||||
};
|
||||
|
||||
thread_local Context* g_current = nullptr;
|
||||
thread_local std::unordered_map<cothread_t, Context*> g_contexts;
|
||||
|
||||
void ContextEntry()
|
||||
{
|
||||
const auto found = g_contexts.find(co_active());
|
||||
if (found == g_contexts.end() || !found->second || !found->second->entry) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Context* context = found->second;
|
||||
g_current = context;
|
||||
context->entry(context->argument);
|
||||
|
||||
// A guest fiber must return through FiberProc's scheduler handoff. There
|
||||
// is no valid native caller to return to from libco's entry trampoline.
|
||||
std::abort();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool InitializeScheduler(Handle* scheduler)
|
||||
{
|
||||
auto* context = new Context();
|
||||
context->native = co_active();
|
||||
if (!context->native) {
|
||||
delete context;
|
||||
return false;
|
||||
}
|
||||
|
||||
g_current = context;
|
||||
g_contexts.emplace(context->native, context);
|
||||
*scheduler = context;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShutdownScheduler(Handle scheduler)
|
||||
{
|
||||
auto* context = static_cast<Context*>(scheduler);
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_contexts.erase(context->native);
|
||||
if (g_current == context) {
|
||||
g_current = nullptr;
|
||||
}
|
||||
delete context;
|
||||
}
|
||||
|
||||
Handle Create(std::size_t stackSize, Entry entry, void* argument)
|
||||
{
|
||||
auto* context = new Context();
|
||||
context->entry = entry;
|
||||
context->argument = argument;
|
||||
context->native = co_create(static_cast<unsigned int>(stackSize), ContextEntry);
|
||||
context->ownsNative = context->native != nullptr;
|
||||
if (!context->native) {
|
||||
delete context;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
g_contexts.emplace(context->native, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
void Destroy(Handle context)
|
||||
{
|
||||
auto* nativeContext = static_cast<Context*>(context);
|
||||
if (!nativeContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_contexts.erase(nativeContext->native);
|
||||
if (nativeContext->ownsNative) {
|
||||
co_delete(nativeContext->native);
|
||||
}
|
||||
delete nativeContext;
|
||||
}
|
||||
|
||||
bool IsCurrent(Handle context)
|
||||
{
|
||||
return context != nullptr && context == g_current;
|
||||
}
|
||||
|
||||
void Switch(Handle target)
|
||||
{
|
||||
auto* destination = static_cast<Context*>(target);
|
||||
Context* source = g_current;
|
||||
if (!destination || destination == source) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_current = destination;
|
||||
co_switch(destination->native);
|
||||
g_current = source;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace HostContext
|
||||
@@ -23,6 +23,10 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
@@ -38,7 +42,12 @@
|
||||
#include <dbghelp.h>
|
||||
#else
|
||||
#include <signal.h>
|
||||
#if defined(__x86_64__)
|
||||
// Only the x86 POSIX fault path inspects ucontext_t to recover the page-fault
|
||||
// write bit. macOS deprecates ucontext and requires _XOPEN_SOURCE just to
|
||||
// include the header, while the arm64 handler does not use it at all.
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -1370,9 +1379,21 @@ int RuntimeMain(int argc, char** argv) {
|
||||
const char* configName;
|
||||
AuroraBackend backend;
|
||||
};
|
||||
#if defined(__APPLE__)
|
||||
static constexpr std::array<GraphicsBackendEntry, 2> kGraphicsBackends{{
|
||||
{"auto", BACKEND_AUTO}, {"metal", BACKEND_METAL},
|
||||
}};
|
||||
// only vulkan for linux
|
||||
#elif defined(__linux__)
|
||||
static constexpr std::array<GraphicsBackendEntry, 2> kGraphicsBackends{{
|
||||
{"auto", BACKEND_AUTO}, {"vulkan", BACKEND_VULKAN},
|
||||
}};
|
||||
#elif defined(_WIN32)
|
||||
static constexpr std::array<GraphicsBackendEntry, 3> kGraphicsBackends{{
|
||||
{"auto", BACKEND_AUTO}, {"d3d12", BACKEND_D3D12}, {"vulkan", BACKEND_VULKAN},
|
||||
}};
|
||||
|
||||
#endif
|
||||
const auto backendDisplayName = [](AuroraBackend value) -> const char* {
|
||||
for (const auto& entry : kGraphicsBackends) {
|
||||
if (entry.backend == value) {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "platform/host_platform.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
namespace RuntimePlatform {
|
||||
|
||||
std::optional<std::filesystem::path> ExecutableDirectory() noexcept {
|
||||
#if defined(_WIN32)
|
||||
std::wstring buffer(MAX_PATH, L'\0');
|
||||
for (;;) {
|
||||
const DWORD length = GetModuleFileNameW(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
|
||||
if (length == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (length < buffer.size() - 1) {
|
||||
buffer.resize(length);
|
||||
return std::filesystem::path(buffer).parent_path();
|
||||
}
|
||||
buffer.resize(buffer.size() * 2);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
uint32_t size = 0;
|
||||
if (_NSGetExecutablePath(nullptr, &size) != -1 || size == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string path(size, '\0');
|
||||
if (_NSGetExecutablePath(path.data(), &size) != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
path.resize(std::char_traits<char>::length(path.c_str()));
|
||||
std::error_code ec;
|
||||
const auto resolved = std::filesystem::weakly_canonical(path, ec);
|
||||
return (ec ? std::filesystem::path(path) : resolved).parent_path();
|
||||
#else
|
||||
return std::nullopt;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path ApplicationDataDirectory(std::string_view applicationName) {
|
||||
#if defined(_WIN32)
|
||||
PWSTR rawPath = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, nullptr, &rawPath)) && rawPath) {
|
||||
const std::filesystem::path directory = std::filesystem::path(rawPath) / applicationName;
|
||||
CoTaskMemFree(rawPath);
|
||||
return directory;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
if (const char* home = std::getenv("HOME"); home && *home) {
|
||||
return std::filesystem::path(home) / "Library" / "Application Support" / applicationName;
|
||||
}
|
||||
if (const passwd* user = getpwuid(getuid()); user && user->pw_dir && *user->pw_dir) {
|
||||
return std::filesystem::path(user->pw_dir) / "Library" / "Application Support" / applicationName;
|
||||
}
|
||||
#endif
|
||||
return std::filesystem::current_path() / applicationName;
|
||||
}
|
||||
|
||||
std::filesystem::path LogDirectory(std::string_view applicationName) {
|
||||
return ApplicationDataDirectory(applicationName) / "Logs";
|
||||
}
|
||||
|
||||
uint64_t CurrentProcessId() noexcept {
|
||||
#if defined(_WIN32)
|
||||
return static_cast<uint64_t>(::GetCurrentProcessId());
|
||||
#else
|
||||
return static_cast<uint64_t>(::getpid());
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace RuntimePlatform
|
||||
@@ -0,0 +1,59 @@
|
||||
.text
|
||||
.align 2
|
||||
|
||||
// AArch64 Darwin cooperative context frame (240 bytes): x18-x30, then v8-v15.
|
||||
// x18 is platform-reserved on Darwin and is needed by code that accesses TLS.
|
||||
// x0 = address holding the target frame pointer; x1 = address to receive the
|
||||
// current frame pointer. This is intentionally leaf-only: it never calls C++.
|
||||
.globl _mkw_co_switch
|
||||
_mkw_co_switch:
|
||||
sub sp, sp, #240
|
||||
str x18, [sp, #0]
|
||||
stp x19, x20, [sp, #16]
|
||||
stp x21, x22, [sp, #32]
|
||||
stp x23, x24, [sp, #48]
|
||||
stp x25, x26, [sp, #64]
|
||||
stp x27, x28, [sp, #80]
|
||||
stp x29, x30, [sp, #96]
|
||||
stp q8, q9, [sp, #112]
|
||||
stp q10, q11, [sp, #144]
|
||||
stp q12, q13, [sp, #176]
|
||||
stp q14, q15, [sp, #208]
|
||||
mov x2, sp
|
||||
str x2, [x1]
|
||||
|
||||
ldr x2, [x0]
|
||||
mov sp, x2
|
||||
ldr x18, [sp, #0]
|
||||
ldp x19, x20, [sp, #16]
|
||||
ldp x21, x22, [sp, #32]
|
||||
ldp x23, x24, [sp, #48]
|
||||
ldp x25, x26, [sp, #64]
|
||||
ldp x27, x28, [sp, #80]
|
||||
ldp x29, x30, [sp, #96]
|
||||
ldp q8, q9, [sp, #112]
|
||||
ldp q10, q11, [sp, #144]
|
||||
ldp q12, q13, [sp, #176]
|
||||
ldp q14, q15, [sp, #208]
|
||||
add sp, sp, #240
|
||||
ret
|
||||
|
||||
// Creates a frame compatible with mkw_co_switch and returns its saved SP.
|
||||
// x0 = one-past-end stack pointer, x1 = entry(void*), x2 = entry argument.
|
||||
.globl _mkw_co_init
|
||||
_mkw_co_init:
|
||||
bic x0, x0, #0xf
|
||||
sub x0, x0, #240
|
||||
str x18, [x0, #0] // Darwin platform register / TLS base
|
||||
str x1, [x0, #16] // x19: entry
|
||||
str x2, [x0, #24] // x20: argument
|
||||
str xzr, [x0, #96] // x29
|
||||
adrp x3, _mkw_co_entry_trampoline@PAGE
|
||||
add x3, x3, _mkw_co_entry_trampoline@PAGEOFF
|
||||
str x3, [x0, #104] // x30
|
||||
ret
|
||||
|
||||
_mkw_co_entry_trampoline:
|
||||
mov x0, x20
|
||||
blr x19
|
||||
brk #0
|
||||
Reference in New Issue
Block a user