Files
Xenia-Canary/RUST_PORT_REPORT.md
2026-04-12 18:25:46 +02:00

1086 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Xenia → Rust Port: Technical Feasibility Report v2.0
This report consolidates the full analysis from both the JIT-based and interpreter-based approaches, grounded in the actual codebase structure.
---
## 1. Project Architecture
Xenia is organized into ~12 major subsystems, all owned by the central `Emulator` class:
```
src/xenia/
├── app/ # Entry point, premake build
├── apu/ # Audio (XAudio2, SDL2 backends)
├── base/ # Platform abstractions, threading, memory
├── cpu/ # PPC frontend, HIR, compiler passes, x64 JIT backend
│ ├── ppc/ # Decoder, HIR emitters, scanner
│ ├── hir/ # HIR opcodes, builder, values
│ ├── compiler/ # Optimization passes
│ └── backend/x64/ # Xbyak-based x64 emitter
├── gpu/ # Xenos GPU, DXBC/SPIR-V shader translators, shader interpreter
├── hid/ # Input (SDL2, XInput, etc.)
├── kernel/ # HLE kernel (xboxkrnl/, xam/)
├── vfs/ # Virtual filesystem (disc image, STFS, ZArchive)
├── ui/ # ImGui, Vulkan/D3D12 window
├── memory.cc/.h # 4GB+ guest address space
└── emulator.cc/.h
``` [1](#2-0)
---
## 2. The CPU Pipeline in Detail
The current execution path for guest code is:
```
PPC binary → PPCScanner (basic blocks) → PPCHIRBuilder (HIR)
→ Compiler passes (CFG, simplification, const-prop, DCE, regalloc)
→ X64Emitter (Xbyak) → native x64 machine code
``` [2](#2-1)
There is **no existing PPC interpreter**. The `ppc_emit_*.cc` files emit HIR, not execute instructions directly. The `PPCScanner` does basic block analysis for the JIT: [3](#2-2)
The register file is `PPCContext`: 32 GPRs, 32 FPRs, 128 VMX registers, plus LR/CTR/XER/CR: [4](#2-3)
Each guest thread owns a `ThreadState` which holds a `PPCContext*`: [5](#2-4)
The full `PPCOpcode` enum covers ~250 opcodes including many Xbox 360-specific VMX128 extensions: [6](#2-5)
---
## 3. The Two Approaches Compared
### Approach A: JIT Port (keep the JIT, rewrite everything else in Rust)
The JIT backend (`X64Emitter`) inherits from `Xbyak::CodeGenerator` — a C++ header-only library with no Rust equivalent: [7](#2-6)
The MMIO system relies on hardware exceptions (`SIGSEGV`/`VEH`) to intercept guest memory accesses made by JIT-compiled code, then decodes the faulting x64 instruction at runtime: [8](#2-7)
This is fundamentally incompatible with safe Rust. Signal handlers in Rust are `unsafe` and severely restricted in what they can do.
The host↔guest thunks emit raw x64 at runtime and depend on `PPCContext` being at hardcoded `offsetof` positions: [9](#2-8)
**Verdict:** The JIT approach requires keeping the entire `cpu/backend/x64/` subtree in C++ and bridging via FFI. The `cxx` crate is the most ergonomic tool for this, but the FFI boundary would be complex and the safety gains minimal in the hottest code path.
---
### Approach B: Interpreter Port (execute PPC instructions directly, no JIT)
This eliminates the three hardest blockers:
| Blocker | JIT | Interpreter |
|---|---|---|
| Xbyak / x64 codegen | Blocker | Eliminated |
| MMIO exception handler | Blocker | Eliminated |
| Host↔guest ABI thunks | Blocker | Eliminated |
| `PPCContext` layout coupling to JIT | Hard | Eliminated |
In an interpreter, every memory access is a function call. The codebase already has the explicit MMIO-check pattern in `MMIOAwareLoad`: [10](#2-9)
And `MMIOHandler` exposes `CheckLoad`/`CheckStore` for exactly this: [11](#2-10)
The opcode dispatch infrastructure (`LookupOpcode`, `PPCDecodeData`, `PPCOpcode` enum) is pure data-manipulation code with no JIT dependency — directly portable to Rust: [12](#2-11)
The `ppc_emit_*.cc` files serve as a complete semantic reference for every instruction, even though they emit HIR rather than executing directly. The VMX128 instruction encoding macros are documented there: [13](#2-12)
**The dominant cost of Approach B is performance.** The Xbox 360 has 3 PPC cores × 2 hardware threads. A naive interpreter runs ~20100× slower than JIT. Most 3D games would be unplayable.
---
## 4. Subsystem-by-Subsystem Feasibility
### 4.1 Memory System — `unsafe`, but contained
The 4GB+ virtual address space reservation via OS file mappings is required regardless of approach: [14](#2-13)
This is `unsafe` Rust via `libc`/`windows-sys`, but it is a well-defined, bounded `unsafe` block. The `xe::be<T>` big-endian wrapper maps cleanly to a Rust newtype: [15](#2-14)
### 4.2 Kernel HLE — Medium difficulty
The kernel has a massive HLE surface: `xboxkrnl_table.inc` alone is 96KB, covering threading, memory, I/O, RTL, video, audio, crypto, and more. The `X_KTHREAD` struct is a packed big-endian layout that lives in guest memory: [16](#2-15)
In Rust, this becomes a `#[repr(C, packed)]` struct with `be<T>` newtypes. The `unsafe` is explicit and auditable. The threading HLE (`xboxkrnl_threading.cc` at 66KB) is the most complex piece.
### 4.3 GPU — Hard
The GPU has two shader translation pipelines (DXBC and SPIR-V) totaling tens of thousands of lines, translating Xenos microcode to host shader IR. The SPIR-V translator uses the glslang `SpirvBuilder` C++ API directly: [17](#2-16)
There is no Rust equivalent of `SpirvBuilder`. The `rspirv` crate provides partial SPIR-V construction but lacks the full feature set.
**Notable:** The GPU subsystem already has a `ShaderInterpreter` for Xenos microcode — used for internal rectangles and clears where texture fetches are not needed: [18](#2-17)
This demonstrates the pattern: interpreter as a correctness baseline, translator for performance.
### 4.4 VFS — High feasibility
The VFS devices (disc image, ZArchive, STFS/XContent) are pure data-parsing code with no platform dependencies beyond file I/O:
These are ideal candidates for safe Rust. The `stfs_xbox.h` format definitions (15KB of packed structs) translate directly to `#[repr(C)]` Rust structs.
### 4.5 Concurrency — Improved by Rust
The codebase uses custom spinlocks (`xe_fast_mutex`, `xe_global_mutex`, `xe_unlikely_mutex`) alongside `std::atomic`. Rust's `Send`/`Sync` traits would catch data races at compile time that are currently only caught at runtime: [19](#2-18)
### 4.6 Third-party C++ dependencies
| Library | Purpose | Rust status |
|---|---|---|
| Xbyak | x64 JIT codegen | No equivalent; `dynasm-rs` is limited |
| glslang/SPIRV-Tools | Shader compilation | `rspirv` (partial) |
| capstone | x86 disassembly | `capstone` crate (binding) |
| Dear ImGui | UI | `imgui-rs` (binding) |
| FFmpeg | Audio/video decode | `ffmpeg-next` (binding) |
| D3D12/XAudio2 | Windows GPU/audio | `windows-rs` (binding) |
| Vulkan | GPU | `ash` (good) |
| snappy/xxhash | Compression/hashing | Native Rust crates exist | [20](#2-19)
---
## 5. The Interpreter + Basic Block Cache Path
The most realistic path to a working Rust port that is both architecturally feasible and eventually performant:
```
Phase 1: Pure interpreter
- PPCContext as #[repr(C)] Rust struct
- match on PPCOpcode enum → one fn per opcode
- Explicit MMIO check on every load/store (no signal handlers)
- Correct but slow (~20-100x slower than JIT)
Phase 2: Basic block cache
- PPCScanner already identifies basic block boundaries
- Cache translated basic blocks as Vec<fn(&mut PPCContext)>
- Invalidate on self-modifying code
- Recovers significant performance without a full JIT
Phase 3: Optional: LLVM via inkwell
- Replace the basic block cache with LLVM IR generation
- Eliminates Xbyak entirely; LLVM handles x64/ARM64
- Major architectural change but Rust-native
```
The `PPCScanner` already provides the basic block boundary detection needed for Phase 2: [3](#2-2)
---
## 6. Summary Matrix
```
Subsystem | Approach A (JIT) | Approach B (Interpreter)
---------------------------|------------------|-------------------------
VFS / file formats | High | High
Config / logging | High | High
Kernel HLE (non-threading) | Medium | Medium
Kernel threading HLE | Low-Medium | Low-Medium
Memory system | Low (unsafe) | Low (unsafe, same)
GPU shader translation | Very Low | Very Low (same)
VMX128 instructions | Hard (HIR emit) | Hard (fn per opcode)
lwarx/stwcx atomics | Hard (x64 asm) | Medium (AtomicU32 CAS)
MMIO handler | Blocker | Eliminated
x64 JIT / Xbyak | Blocker | Eliminated
Host↔guest thunks | Blocker | Eliminated
Performance | Good | Poor (Phase 1)
```
**Bottom line:** A full JIT port is not practical without either keeping the JIT in C++ (hybrid) or replacing Xbyak with LLVM. An interpreter port is architecturally feasible in Rust and eliminates the three hardest blockers, but requires a basic block cache layer to approach playable performance. The GPU shader translation pipeline is the second major obstacle regardless of CPU approach, with no clean Rust-native equivalent of the glslang `SpirvBuilder` API.
### Citations
**File:** src/xenia/emulator.h (L336-349)
```text
std::unique_ptr<Memory> memory_;
std::unique_ptr<cpu::Processor> processor_;
std::unique_ptr<apu::AudioSystem> audio_system_;
std::unique_ptr<apu::AudioMediaPlayer> audio_media_player_;
std::unique_ptr<gpu::GraphicsSystem> graphics_system_;
std::unique_ptr<hid::InputSystem> input_system_;
std::unique_ptr<cpu::ExportResolver> export_resolver_;
std::unique_ptr<vfs::VirtualFileSystem> file_system_;
std::unique_ptr<patcher::Patcher> patcher_;
std::unique_ptr<patcher::PluginLoader> plugin_loader_;
std::unique_ptr<kernel::KernelState> kernel_state_;
```
**File:** src/xenia/cpu/ppc/ppc_translator.cc (L44-112)
```text
PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) {
Backend* backend = frontend->processor()->backend();
scanner_.reset(new PPCScanner(frontend));
builder_.reset(new PPCHIRBuilder(frontend));
compiler_.reset(new Compiler(frontend->processor()));
assembler_ = backend->CreateAssembler();
assembler_->Initialize();
bool validate = cvars::validate_hir;
// Merge blocks early. This will let us use more context in other passes.
// The CFG is required for simplification and dirtied by it.
compiler_->AddPass(std::make_unique<passes::ControlFlowAnalysisPass>());
compiler_->AddPass(std::make_unique<passes::ControlFlowSimplificationPass>());
// Passes are executed in the order they are added. Multiple of the same
// pass type may be used.
if (!cvars::disable_context_promotion) {
if (validate) {
compiler_->AddPass(std::make_unique<passes::ValidationPass>());
}
compiler_->AddPass(std::make_unique<passes::ContextPromotionPass>());
if (validate) {
compiler_->AddPass(std::make_unique<passes::ValidationPass>());
}
}
// Grouped simplification + constant propagation.
// Loops until no changes are made.
auto sap = std::make_unique<passes::ConditionalGroupPass>();
sap->AddPass(std::make_unique<passes::SimplificationPass>());
if (validate) sap->AddPass(std::make_unique<passes::ValidationPass>());
sap->AddPass(std::make_unique<passes::ConstantPropagationPass>());
if (validate) sap->AddPass(std::make_unique<passes::ValidationPass>());
compiler_->AddPass(std::move(sap));
if (backend->machine_info()->supports_extended_load_store) {
// Backend supports the advanced LOAD/STORE instructions.
// These will save us a lot of HIR opcodes.
compiler_->AddPass(
std::make_unique<passes::MemorySequenceCombinationPass>());
if (validate)
compiler_->AddPass(std::make_unique<passes::ValidationPass>());
}
compiler_->AddPass(std::make_unique<passes::SimplificationPass>());
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
// compiler_->AddPass(std::make_unique<passes::DeadStoreEliminationPass>());
// if (validate)
// compiler_->AddPass(std::make_unique<passes::ValidationPass>());
compiler_->AddPass(std::make_unique<passes::DeadCodeEliminationPass>());
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
//// Removes all unneeded variables. Try not to add new ones after this.
// compiler_->AddPass(new passes::ValueReductionPass());
// if (validate) compiler_->AddPass(new passes::ValidationPass());
// Register allocation for the target backend.
// Will modify the HIR to add loads/stores.
// This should be the last pass before finalization, as after this all
// registers are assigned and ready to be emitted.
compiler_->AddPass(std::make_unique<passes::RegisterAllocationPass>(
backend->machine_info()));
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
// Must come last. The HIR is not really HIR after this.
compiler_->AddPass(std::make_unique<passes::FinalizationPass>());
}
```
**File:** src/xenia/cpu/ppc/ppc_scanner.h (L29-42)
```text
class PPCScanner {
public:
explicit PPCScanner(PPCFrontend* frontend);
~PPCScanner();
bool Scan(GuestFunction* function, FunctionDebugInfo* debug_info);
std::vector<BlockInfo> FindBlocks(GuestFunction* function);
private:
bool IsRestGprLr(uint32_t address);
PPCFrontend* frontend_ = nullptr;
};
```
**File:** src/xenia/cpu/ppc/ppc_context.h (L376-400)
```text
// Most frequently used registers first.
uint64_t r[32]; // 0x20 General purpose registers
uint64_t ctr; // 0x18 Count register
uint64_t lr; // 0x10 Link register
uint64_t msr; // machine state register
double f[32]; // 0x120 Floating-point registers
vec128_t v[128]; // 0x220 VMX128 vector registers
vec128_t vscr_vec;
// XER register:
// Split to make it easier to do individual updates.
uint8_t xer_ca;
uint8_t xer_ov;
uint8_t xer_so;
// Condition registers:
// These are split to make it easier to do DCE on unused stores.
uint64_t cr() const;
void set_cr(uint64_t value);
// todo: remove, saturation should be represented by a vector
uint8_t vscr_sat;
uint32_t vrsave;
```
**File:** src/xenia/cpu/thread_state.h (L24-50)
```text
class ThreadState {
public:
ThreadState(Processor* processor, uint32_t thread_id, uint32_t stack_base = 0,
uint32_t pcr_address = 0);
~ThreadState();
Processor* processor() const { return processor_; }
Memory* memory() const { return memory_; }
void* backend_data() const { return backend_data_; }
ppc::PPCContext* context() const { return context_; }
uint32_t thread_id() const { return thread_id_; }
static void Bind(ThreadState* thread_state);
static ThreadState* Get();
static uint32_t GetThreadID();
private:
Processor* processor_;
Memory* memory_;
void* backend_data_;
uint32_t pcr_address_ = 0;
uint32_t thread_id_ = 0;
// NOTE: must be 64b aligned for SSE ops.
ppc::PPCContext* context_;
};
```
**File:** src/xenia/cpu/ppc/ppc_opcode.h (L14-200)
```text
enum class PPCOpcode : uint32_t {
addcx,
addex,
addi,
addic,
addicx,
addis,
addmex,
addx,
addzex,
andcx,
andisx,
andix,
andx,
bcctrx,
bclrx,
bcx,
bx,
cmp,
cmpi,
cmpl,
cmpli,
cntlzdx,
cntlzwx,
crand,
crandc,
creqv,
crnand,
crnor,
cror,
crorc,
crxor,
dcbf,
dcbi,
dcbst,
dcbt,
dcbtst,
dcbz,
dcbz128,
divdux,
divdx,
divwux,
divwx,
eieio,
eqvx,
extsbx,
extshx,
extswx,
fabsx,
faddsx,
faddx,
fcfidx,
fcmpo,
fcmpu,
fctidx,
fctidzx,
fctiwx,
fctiwzx,
fdivsx,
fdivx,
fmaddsx,
fmaddx,
fmrx,
fmsubsx,
fmsubx,
fmulsx,
fmulx,
fnabsx,
fnegx,
fnmaddsx,
fnmaddx,
fnmsubsx,
fnmsubx,
fresx,
frspx,
frsqrtex,
fselx,
fsqrtsx,
fsqrtx,
fsubsx,
fsubx,
icbi,
isync,
lbz,
lbzu,
lbzux,
lbzx,
ld,
ldarx,
ldbrx,
ldu,
ldux,
ldx,
lfd,
lfdu,
lfdux,
lfdx,
lfs,
lfsu,
lfsux,
lfsx,
lha,
lhau,
lhaux,
lhax,
lhbrx,
lhz,
lhzu,
lhzux,
lhzx,
lmw,
lswi,
lswx,
lvebx,
lvehx,
lvewx,
lvewx128,
lvlx,
lvlx128,
lvlxl,
lvlxl128,
lvrx,
lvrx128,
lvrxl,
lvrxl128,
lvsl,
lvsl128,
lvsr,
lvsr128,
lvx,
lvx128,
lvxl,
lvxl128,
lwa,
lwarx,
lwaux,
lwax,
lwbrx,
lwz,
lwzu,
lwzux,
lwzx,
mcrf,
mcrfs,
mcrxr,
mfcr,
mffsx,
mfmsr,
mfspr,
mftb,
mfvscr,
mtcrf,
mtfsb0x,
mtfsb1x,
mtfsfix,
mtfsfx,
mtmsr,
mtmsrd,
mtspr,
mtvscr,
mulhdux,
mulhdx,
mulhwux,
mulhwx,
mulldx,
mulli,
mullwx,
nandx,
negx,
norx,
orcx,
ori,
oris,
orx,
rldclx,
rldcrx,
rldiclx,
rldicrx,
rldicx,
rldimix,
rlwimix,
rlwinmx,
rlwnmx,
sc,
sldx,
slwx,
sradix,
```
**File:** src/xenia/cpu/backend/x64/x64_emitter.h (L208-212)
```text
class X64Emitter : public Xbyak::CodeGenerator {
public:
X64Emitter(X64Backend* backend, XbyakAllocator* allocator);
virtual ~X64Emitter();
```
**File:** src/xenia/cpu/mmio_handler.cc (L402-470)
```text
bool MMIOHandler::ExceptionCallback(Exception* ex) {
if (ex->code() != Exception::Code::kAccessViolation) {
return false;
}
Exception::AccessViolationOperation operation =
ex->access_violation_operation();
if (operation != Exception::AccessViolationOperation::kRead &&
operation != Exception::AccessViolationOperation::kWrite) {
// Data Execution Prevention or something else uninteresting.
return false;
}
bool is_write = operation == Exception::AccessViolationOperation::kWrite;
if (ex->fault_address() < uint64_t(virtual_membase_) ||
ex->fault_address() > uint64_t(memory_end_)) {
// Quick kill anything outside our mapping.
return false;
}
void* fault_host_address = reinterpret_cast<void*>(ex->fault_address());
// Access violations are pretty rare, so we can do a linear search here.
// Only check if in the virtual range, as we only support virtual ranges.
const MMIORange* range = nullptr;
uint32_t fault_guest_virtual_address = 0;
if (ex->fault_address() < uint64_t(physical_membase_)) {
fault_guest_virtual_address = host_to_guest_virtual_(
host_to_guest_virtual_context_, fault_host_address);
for (const auto& test_range : mapped_ranges_) {
if ((fault_guest_virtual_address & test_range.mask) ==
test_range.address) {
// Address is within the range of this mapping.
range = &test_range;
break;
}
}
}
if (!range) {
// Recheck if the pages are still protected (race condition - another thread
// clears the watch we just hit).
// Do this under the lock so we don't introduce another race condition.
auto lock = global_critical_region_.Acquire();
memory::PageAccess cur_access;
size_t page_length = memory::page_size();
memory::QueryProtect(fault_host_address, page_length, cur_access);
if (cur_access != memory::PageAccess::kNoAccess &&
(!is_write || cur_access != memory::PageAccess::kReadOnly)) {
// Another thread has cleared this watch. Abort.
XELOGD("Race condition on watch, was already cleared by another thread!");
return true;
}
// The address is not found within any range, so either a write watch or an
// actual access violation.
if (access_violation_callback_) {
return access_violation_callback_(std::move(lock),
access_violation_callback_context_,
fault_host_address, is_write);
}
return false;
}
auto rip = ex->pc();
auto p = reinterpret_cast<const uint8_t*>(rip);
DecodedLoadStore decoded_load_store;
if (!TryDecodeLoadStore(p, decoded_load_store)) {
XELOGE("Unable to decode MMIO load or store instruction at {}",
static_cast<const void*>(p));
assert_always("Unknown MMIO instruction type");
return false;
}
```
**File:** src/xenia/cpu/backend/x64/x64_backend.cc (L630-726)
```text
HostToGuestThunk X64HelperEmitter::EmitHostToGuestThunk() {
#ifdef XE_PLATFORM_WIN32
// rcx = target
// rdx = arg0 (context)
// r8 = arg1 (guest return address)
_code_offsets code_offsets = {};
constexpr size_t stack_size = StackLayout::THUNK_STACK_SIZE;
code_offsets.prolog = getSize();
// rsp + 0 = return address
mov(qword[rsp + 8 * 3], r8);
mov(qword[rsp + 8 * 2], rdx);
mov(qword[rsp + 8 * 1], rcx);
sub(rsp, stack_size);
code_offsets.prolog_stack_alloc = getSize();
code_offsets.body = getSize();
// Save nonvolatile registers.
EmitSaveNonvolatileRegs();
mov(rax, rcx);
mov(rsi, rdx); // context
mov(rdi, ptr[rdx + offsetof(ppc::PPCContext, virtual_membase)]); // membase
mov(rcx, r8); // return address
call(rax);
vzeroupper();
EmitLoadNonvolatileRegs();
code_offsets.epilog = getSize();
add(rsp, stack_size);
mov(rcx, qword[rsp + 8 * 1]);
mov(rdx, qword[rsp + 8 * 2]);
mov(r8, qword[rsp + 8 * 3]);
ret();
#elif XE_PLATFORM_LINUX || XE_PLATFORM_MAC
// System-V ABI args:
// rdi = target
// rsi = arg0 (context)
// rdx = arg1 (guest return address)
struct _code_offsets {
size_t prolog;
size_t prolog_stack_alloc;
size_t body;
size_t epilog;
size_t tail;
} code_offsets = {};
constexpr size_t stack_size = StackLayout::THUNK_STACK_SIZE;
code_offsets.prolog = getSize();
// rsp + 0 = return address
sub(rsp, stack_size);
code_offsets.prolog_stack_alloc = getSize();
code_offsets.body = getSize();
// Save nonvolatile registers.
EmitSaveNonvolatileRegs();
mov(rax, rdi);
// mov(rsi, rsi); // context
mov(rdi, ptr[rsi + offsetof(ppc::PPCContext, virtual_membase)]); // membase
mov(rcx, rdx); // return address
call(rax);
EmitLoadNonvolatileRegs();
code_offsets.epilog = getSize();
add(rsp, stack_size);
ret();
#else
assert_always("Unknown platform ABI in host to guest thunk!");
#endif
code_offsets.tail = getSize();
assert_zero(code_offsets.prolog);
EmitFunctionInfo func_info = {};
func_info.code_size.total = getSize();
func_info.code_size.prolog = code_offsets.body - code_offsets.prolog;
func_info.code_size.body = code_offsets.epilog - code_offsets.body;
func_info.code_size.epilog = code_offsets.tail - code_offsets.epilog;
func_info.code_size.tail = getSize() - code_offsets.tail;
func_info.prolog_stack_alloc_offset =
code_offsets.prolog_stack_alloc - code_offsets.prolog;
func_info.stack_size = stack_size;
void* fn = Emplace(func_info);
return (HostToGuestThunk)fn;
}
```
**File:** src/xenia/cpu/backend/x64/x64_seq_memory.cc (L1215-1237)
```text
template <typename T, bool swap>
static T MMIOAwareLoad(void* _ctx, unsigned int guestaddr) {
T value;
if (guestaddr >= 0xE0000000) {
guestaddr += 0x1000;
}
auto ctx = reinterpret_cast<ppc::PPCContext*>(_ctx);
auto gaddr = ctx->processor->memory()->LookupVirtualMappedRange(guestaddr);
if (!gaddr) {
value = *reinterpret_cast<T*>(ctx->virtual_membase + guestaddr);
if (swap) {
value = xe::byte_swap(value);
}
} else {
/*
was having issues, found by comparing the values used with exceptions
to these that we were reversed...
*/
value = gaddr->read(nullptr, gaddr->callback_context, guestaddr);
}
```
**File:** src/xenia/cpu/mmio_handler.h (L73-74)
```text
bool CheckLoad(uint32_t virtual_address, uint32_t* out_value);
bool CheckStore(uint32_t virtual_address, uint32_t value);
```
**File:** src/xenia/cpu/ppc/ppc_opcode_lookup_gen.cc (L261-285)
```text
switch ((ExtractBits(code, 22, 25) << 2)|(ExtractBits(code, 27, 27) << 0)) {
case 0b000101: PPC_DECODER_HIT(vrlw128);
case 0b001101: PPC_DECODER_HIT(vslw128);
case 0b010101: PPC_DECODER_HIT(vsraw128);
case 0b011101: PPC_DECODER_HIT(vsrw128);
case 0b101000: PPC_DECODER_HIT(vmaxfp128);
case 0b101100: PPC_DECODER_HIT(vminfp128);
case 0b110000: PPC_DECODER_HIT(vmrghw128);
case 0b110100: PPC_DECODER_HIT(vmrglw128);
case 0b111000: PPC_DECODER_HIT(vupkhsb128);
case 0b111100: PPC_DECODER_HIT(vupklsb128);
}
PPC_DECODER_MISS;
case 7: PPC_DECODER_HIT(mulli);
case 8: PPC_DECODER_HIT(subficx);
case 10: PPC_DECODER_HIT(cmpli);
case 11: PPC_DECODER_HIT(cmpi);
case 12: PPC_DECODER_HIT(addic);
case 13: PPC_DECODER_HIT(addicx);
case 14: PPC_DECODER_HIT(addi);
case 15: PPC_DECODER_HIT(addis);
case 16: PPC_DECODER_HIT(bcx);
case 17: PPC_DECODER_HIT(sc);
case 18: PPC_DECODER_HIT(bx);
case 19:
```
**File:** src/xenia/cpu/ppc/ppc_emit_altivec.cc (L36-50)
```text
#define OP(x) ((((uint32_t)(x)) & 0x3f) << 26)
#define VX128(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x3d0))
#define VX128_1(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x7f3))
#define VX128_2(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x210))
#define VX128_3(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x7f0))
#define VX128_4(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x730))
#define VX128_5(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x10))
#define VX128_P(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x630))
#define VX128_VD128 (i.VX128.VD128l | (i.VX128.VD128h << 5))
#define VX128_VA128 \
(i.VX128.VA128l | (i.VX128.VA128h << 5) | (i.VX128.VA128H << 6))
#define VX128_VB128 (i.VX128.VB128l | (i.VX128.VB128h << 5))
#define VX128_1_VD128 (i.VX128_1.VD128l | (i.VX128_1.VD128h << 5))
#define VX128_2_VD128 (i.VX128_2.VD128l | (i.VX128_2.VD128h << 5))
```
**File:** src/xenia/memory.cc (L137-168)
```text
bool Memory::Initialize() {
file_name_ = fmt::format("xenia_memory_{}", Clock::QueryHostTickCount());
// Create main page file-backed mapping. This is all reserved but
// uncommitted (so it shouldn't expand page file).
mapping_ = xe::memory::CreateFileMappingHandle(
file_name_,
// entire 4gb space + 512mb physical:
0x11FFFFFFF, xe::memory::PageAccess::kReadWrite, false);
if (mapping_ == xe::memory::kFileMappingHandleInvalid) {
XELOGE("Unable to reserve the 4gb guest address space.");
assert_always();
return false;
}
// Attempt to create our views. This may fail at the first address
// we pick, so try a few times.
mapping_base_ = 0;
for (size_t n = 32; n < 64; n++) {
auto mapping_base = reinterpret_cast<uint8_t*>(1ull << n);
if (!MapViews(mapping_base)) {
mapping_base_ = mapping_base;
break;
}
}
if (!mapping_base_) {
XELOGE("Unable to find a continuous block in the 64bit address space.");
assert_always();
return false;
}
virtual_membase_ = mapping_base_;
physical_membase_ = mapping_base_ + 0x100000000ull;
```
**File:** src/xenia/base/byte_order.h (L83-136)
```text
template <typename T, std::endian E>
struct endian_store {
endian_store() = default;
endian_store(const T& src) { set(src); }
endian_store(const endian_store& other) { set(other); }
operator T() const { return get(); }
void set(const T& src) {
if constexpr (std::endian::native == E) {
value = src;
} else {
value = xe::byte_swap(src);
}
}
void set(const endian_store& other) { value = other.value; }
T get() const {
if constexpr (std::endian::native == E) {
return value;
}
return xe::byte_swap(value);
}
endian_store<T, E>& operator+=(int a) {
*this = *this + a;
return *this;
}
endian_store<T, E>& operator-=(int a) {
*this = *this - a;
return *this;
}
endian_store<T, E>& operator++() {
*this += 1;
return *this;
} // ++a
endian_store<T, E> operator++(int) {
*this += 1;
return (*this - 1);
} // a++
endian_store<T, E>& operator--() {
*this -= 1;
return *this;
} // --a
endian_store<T, E> operator--(int) {
*this -= 1;
return (*this + 1);
} // a--
T value;
};
template <typename T>
using be = endian_store<T, std::endian::big>;
template <typename T>
using le = endian_store<T, std::endian::little>;
```
**File:** src/xenia/kernel/xthread.h (L249-280)
```text
struct X_KTHREAD {
X_DISPATCH_HEADER header; // 0x0
xe::be<uint32_t> unk_10; // 0x10
xe::be<uint32_t> unk_14; // 0x14
X_KTIMER wait_timeout_timer; // 0x18
X_KWAIT_BLOCK wait_timeout_block; // 0x40
uint8_t unk_58[0x4]; // 0x58
xe::be<uint32_t> stack_base; // 0x5C
xe::be<uint32_t> stack_limit; // 0x60
xe::be<uint32_t> stack_kernel; // 0x64
xe::be<uint32_t> tls_address; // 0x68
// state = is thread running, suspended, etc
uint8_t thread_state; // 0x6C
// 0x70 = priority?
uint8_t alerted[2]; // 0x6D
uint8_t alertable; // 0x6F
uint8_t priority; // 0x70
uint8_t fpu_exceptions_on; // 0x71
// these two process types both get set to the same thing, process_type is
// referenced most frequently, however process_type_dup gets referenced a few
// times while the process is being created
uint8_t process_type_dup;
uint8_t process_type;
// apc_mode determines which list an apc goes into
util::X_TYPED_LIST<XAPC, offsetof(XAPC, list_entry)> apc_lists[2];
TypedGuestPointer<X_KPROCESS> process; // 0x84
uint8_t executing_kernel_apc; // 0x88
// when context switch happens, this is copied into
// apc_software_interrupt_state for kpcr
uint8_t deferred_apc_software_interrupt_state; // 0x89
uint8_t user_apc_pending; // 0x8A
uint8_t may_queue_apcs; // 0x8B
```
**File:** src/xenia/gpu/spirv_shader_translator.cc (L153-170)
```text
void SpirvShaderTranslator::StartTranslation() {
// TODO(Triang3l): Logger.
builder_ = std::make_unique<SpirvBuilder>(
features_.spirv_version, (kSpirvMagicToolId << 16) | 1, nullptr);
builder_->addCapability(IsSpirvTessEvalShader() ? spv::CapabilityTessellation
: spv::CapabilityShader);
if (features_.spirv_version < spv::Spv_1_4) {
if (features_.signed_zero_inf_nan_preserve_float32 ||
features_.denorm_flush_to_zero_float32 ||
features_.rounding_mode_rte_float32) {
builder_->addExtension("SPV_KHR_float_controls");
}
}
ext_inst_glsl_std_450_ = builder_->import("GLSL.std.450");
builder_->setMemoryModel(spv::AddressingModelLogical,
spv::MemoryModelGLSL450);
builder_->setSource(spv::SourceLanguageUnknown, 0);
```
**File:** src/xenia/gpu/shader_interpreter.h (L26-60)
```text
class ShaderInterpreter {
public:
ShaderInterpreter(const RegisterFile& register_file, const Memory& memory)
: register_file_(register_file), memory_(memory) {}
class ExportSink {
public:
virtual ~ExportSink() = default;
virtual void AllocExport(ucode::AllocType type, uint32_t size) {}
virtual void Export(ucode::ExportRegister export_register,
const float* value, uint32_t value_mask) {}
};
void SetTraceWriter(TraceWriter* new_trace_writer) {
trace_writer_ = new_trace_writer;
}
ExportSink* GetExportSink() const { return export_sink_; }
void SetExportSink(ExportSink* new_export_sink) {
export_sink_ = new_export_sink;
}
const float* temp_registers() const { return &temp_registers_[0][0]; }
float* temp_registers() { return &temp_registers_[0][0]; }
static bool CanInterpretShader(const Shader& shader) {
assert_true(shader.is_ucode_analyzed());
// Texture instructions are not very common in vertex shaders (and not used
// in Direct3D 9's internal rectangles such as clears) and are extremely
// complex, not implemented.
if (shader.uses_texture_fetch_instruction_results()) {
return false;
}
return true;
}
```
**File:** src/xenia/base/mutex.h (L27-87)
```text
class alignas(4096) xe_global_mutex {
XE_MAYBE_UNUSED
char detail[64];
public:
xe_global_mutex();
~xe_global_mutex();
void lock();
void unlock();
bool try_lock();
};
using global_mutex_type = xe_global_mutex;
class alignas(64) xe_fast_mutex {
XE_MAYBE_UNUSED
char detail[64];
public:
xe_fast_mutex();
~xe_fast_mutex();
void lock();
void unlock();
bool try_lock();
};
// a mutex that is extremely unlikely to ever be locked
// use for race conditions that have extremely remote odds of happening
class xe_unlikely_mutex {
std::atomic<uint32_t> mut;
bool _tryget() {
uint32_t lock_expected = 0;
return mut.compare_exchange_strong(lock_expected, 1);
}
public:
xe_unlikely_mutex() : mut(0) {}
~xe_unlikely_mutex() { mut = 0; }
void lock() {
if (XE_LIKELY(_tryget())) {
return;
} else {
do {
// chrispy: warning, if no SMT, mm_pause does nothing...
#if XE_ARCH_AMD64 == 1
_mm_pause();
#endif
} while (!_tryget());
}
}
void unlock() { mut.exchange(0); }
bool try_lock() { return _tryget(); }
};
using xe_mutex = xe_fast_mutex;
#else
using global_mutex_type = std::recursive_mutex;
using xe_mutex = std::mutex;
using xe_unlikely_mutex = std::mutex;
#endif
```
**File:** src/xenia/app/premake5.lua (L31-42)
```lua
"glslang-spirv",
"imgui",
"libavcodec",
"libavutil",
"mspack",
"snappy",
"xxhash",
})
defines({
"XBYAK_NO_OP_NAMES",
"XBYAK_ENABLE_OMITTED_OPERAND",
})
```