33 KiB
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 ~20–100× 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",
})