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

33 KiB
Raw Blame History

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


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)

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)

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)

  // 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)

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)

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)

class X64Emitter : public Xbyak::CodeGenerator {
 public:
  X64Emitter(X64Backend* backend, XbyakAllocator* allocator);
  virtual ~X64Emitter();

File: src/xenia/cpu/mmio_handler.cc (L402-470)

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)

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)

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)

  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)

    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)

#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)

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)

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)

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)

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)

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)

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)

    "glslang-spirv",
    "imgui",
    "libavcodec",
    "libavutil",
    "mspack",
    "snappy",
    "xxhash",
  })
  defines({
    "XBYAK_NO_OP_NAMES",
    "XBYAK_ENABLE_OMITTED_OPERAND",
  })