All MicroEvals
Read C:\Users\adria\Downloads\WASM-Switch-Emulator-main\wasm...
Create MicroEval
Header image for Read C:\Users\adria\Downloads\WASM-Switch-Emulator-main\wasm...

Read C:\Users\adria\Downloads\WASM-Switch-Emulator-main\wasm...

Prompt

Read C:\Users\adria\Downloads\WASM-Switch-Emulator-main\wasm-switch-emulator-design-doc.md See existing md file. Create a great overarching wasmplan.md intricately and properly such that GLM 5.3 Flash can execute it perfectly to create a working WASM Switch Emulator. The plan must be very strict, straightforward, clear and leave very little room for deviation and has definite success criteria for success. I am aware the md file is somewhat of a plan but its written leaving plenty of room for error and isn't a full undeviateable flawless plan exactly. Try your best to create a Overarchingplan.md. Much of the doc has been cut out to fit the 15k character limit so you will have to work with that. file: C:\Users\adria\Downloads\WASM-Switch-Emulator-main\wasm-switch-emulator-design-doc.md ```md # WASM Switch Emulator β€” Design Doc **Owners:** Luke, Adrian **Execution model:** AI-agent-driven (GLM 5.3 Flash for implementation, a powerful detailed overarching plan with architecture decisions by a powerful model), run several hours/day **Base codebase:** Fork of Suyu or Eden (C++, Yuzu-derived, uses dynarmic as ARM64 core) **Target runtime:** Browser, via Emscripten β†’ WASM, WebGPU for graphics ## How to use this doc Each phase has a **Goal**, **Tasks**, and a **Checkpoint** β€” a concrete, mechanically-verifiable test the agent must pass before moving to the next phase. Do not let the agent skip ahead on a phase whose checkpoint hasn't passed; the phases are ordered by hard dependency, not by convenience. If a checkpoint can't be hit after a reasonable number of attempts, that's a signal to stop and re-scope, not to patch around it and move on. Treat "Definition of done" language literally β€” a checkpoint that says "produces correct register state" means an automated diff against expected output, not "looks plausible." --- ## Phase 0 β€” Environment & Baseline **Goal:** Confirm the toolchain works end-to-end before touching emulator internals. **Tasks:** - Install Emscripten SDK, verify `emcc --version`. - Pick Suyu or Eden β€” check both repos for: which is more actively updated, build health, and how cleanly dynarmic is separated as a submodule/library (this matters a lot for Phase 2). - Get the chosen fork building **natively** (not WASM yet) on the dev machine, following its existing build docs. This confirms the codebase itself isn't already broken before you add WASM as a variable. - Compile a trivial "hello world" C++ program through Emscripten to WASM and run it in a browser via a minimal HTML harness, to confirm the toolchain itself works. - Confirm whether the gaming site's hosting supports HTTP Range requests and custom response headers (needed later for the network file-access path in Phase 3, and for COOP/COEP in Phase 2) β€” a hosting limitation found now is a config check, found in Phase 3+ it's a migration. **Checkpoint:** - [ ] Native build of the fork completes and produces a runnable binary (even if it can't run games yet β€” just builds). - [ ] Hello-world WASM module loads and executes in a browser console, output visible. --- ## Browser execution architecture (read before Phase 1) Decided once, up front, so ownership of memory/state doesn't get reinvented ad hoc partway through later phases. ``` Main thread - UI, input, file picker, canvas presentation - never touches guest memory or emulator state directly Emulation Worker (owns everything below) - Horizon/HLE, guest scheduler - dynarmic IR/frontend - WASM block cache, WebAssembly.Memory, WebAssembly.Table - GPU emulation + WebGPU renderer - filesystem/cache coordination Optional Compiler Worker - receives translated block descriptions from the Emulation Worker - emits WASM binaries, compiles them to WebAssembly.Module objects - sends the compiled Module back via postMessage (Module objects are transferable/structured-cloneable β€” no shared memory needed for this) - never touches guest memory directly ``` **Rules:** - The Emulation Worker is the sole owner of `WebAssembly.Memory` and `WebAssembly.Table`. The optional Compiler Worker only ever hands back compiled `WebAssembly.Module` objects β€” it doesn't need access to guest memory or the live table at all. - **`SharedArrayBuffer` and cross-origin isolation (COOP/COEP) are optional, not baseline.** A single Emulation Worker doing its own memory/table/instantiation with ordinary `postMessage` needs no shared memory between browser threads. Only introduce `SharedArrayBuffer` (and the COOP/COEP headers it requires) if profiling later shows you genuinely need concurrent access to the same live memory from multiple workers β€” don't make it a day-one hosting dependency for something the baseline architecture doesn't need. - Browser APIs that are inherently asynchronous (network fetches, most storage APIs) never get called from inside a synchronous dynarmic memory callback β€” see Phase 3's rewritten file-access design for why and how. --- ## Phase 1 β€” Isolate dynarmic **Goal:** Extract dynarmic as a standalone, independently buildable unit, decoupled from the rest of the emulator, so it can be iterated on without rebuilding all of Suyu/Eden each time. **Tasks:** - Identify dynarmic's boundary in the codebase: what calls into it, what it calls out to (memory callbacks, interrupt/exception hooks). - Build a minimal standalone harness: link only against dynarmic, feed it a small hand-written ARM64 instruction stream, run it, and read back register state. - Confirm you can build dynarmic **natively** in this harness first, x86/ARM JIT backend untouched, as a sanity baseline. **Checkpoint:** - [ ] Standalone harness compiles and links against dynarmic alone (no full emulator dependency). - [ ] Feeding it a simple test program (e.g. a handful of `ADD`/`MOV`/`SUB` instructions with known operands) produces the expected register values, verified against a golden/expected value in an automated test β€” not eyeballed. --- ## Phase 2 β€” Dynarmic β†’ WASM backend (the core unlock) **Goal:** Get dynarmic emitting WebAssembly bytecode instead of native x86/ARM machine code, and executing that via `WebAssembly.instantiate()` at runtime. This is the highest-risk, highest-effort phase in the whole project. Budget for it accordingly β€” it is not a "port," it's writing a new code-generation backend. **Tasks:** - Study dynarmic's existing backend structure (it already targets multiple native architectures β€” use that as the template for what a "backend" needs to implement). - Decide on codegen strategy: emit WASM text format (WAT) and compile via a WASM toolchain at build time for early testing, then move to emitting binary WASM modules dynamically at runtime once the emitter is validated. - Implement instruction-by-instruction translation for a **minimal subset** of ARM64 first (arithmetic, moves, branches) β€” do not attempt full ISA coverage yet. - Wire up guest-memory access: WASM linear memory as the "RAM" the translated code reads/writes, with bounds-checked load/store. - Handle the runtime instantiation loop: translate a block of guest code β†’ emit WASM module β†’ `WebAssembly.instantiate()` it β†’ call into it β†’ read results. **Checkpoint:** - [ ] The Phase 1 test program (simple ARM64 instruction stream) now runs through the WASM backend instead of the native backend, in an actual browser tab (not just Node/native). - [ ] Register state after execution matches the same golden values from Phase 1, byte-for-byte. - [ ] Expand the test set to cover branches and memory load/store; each new instruction category gets its own golden-value test before being marked done. - [ ] Benchmark: measure translated-block throughput vs. the interpreter fallback (see below) to confirm the WASM path is actually faster β€” if it isn't yet, that's fine to note, but it must be measured, not assumed. **Fallback path (only if Phase 2 stalls hard):** dynarmic's interpreter mode runs guest instructions without any code generation, which sidesteps the whole browser-sandbox problem β€” at a large speed cost. Wiring the interpreter into the standalone harness is a useful risk-reduction side task early on, so you have *something* working end-to-end while the real WASM backend is still being built. ### Phase 2 β€” Architectural constraints (binding, not suggestions) These are decided up front specifically so an AI agent working unsupervised for hours doesn't rediscover, second-guess, or quietly violate them mid-session. If a diff or design choice contradicts one of these, stop and reconsider rather than proceeding. - **New backend must live in an isolated directory (`backend/wasm/`), full stop.** Never edit files under `backend/x64/` or `backend/A64/`. dynarmic already separates architecture-independent IR (`frontend/`) from per-target codegen (`backend/`) specifically so a new target doesn't require touching the frontend or other backends. If the agent's diff touches either native backend directory, that's an immediate stop-and-reconsider, not a "keep going and see." - **No host register allocator in the WASM backend.** Native backends need register allocation (spilling, live-range analysis) because x86-64/ARM64 only have ~16 physical registers. Map one WASM `local` per IR SSA value instead β€” but note these aren't unlimited hardware registers standing in for a free lunch: you're handing the actual register-allocation/spilling problem to the browser's WASM compiler, which lowers locals to host registers as part of compiling the module. The backend just never needs to *implement* that step itself. - **Compilation unit = one basic block = one flat WASM function, but batched into shared modules.** Each block's body has no internal branching (basic blocks don't branch internally by definition, so no relooper-style control-flow reconstruction is needed). But do **not** instantiate one `WebAssembly.Module` per block β€” that's a module-compile-and-instantiate cycle for every few dozen instructions translated, which is prohibitively slow regardless of sync vs async APIs. Instead: accumulate several newly-translated blocks into one **translation batch**, emit them as functions within a single module, compile and instantiate that module once, then insert its exported block functions into the shared dispatch table. Control flow *between* blocks β€” including across batches β€” goes through a dispatch loop doing `call_indirect` against that shared `WebAssembly.Table`. - **Emulation Worker owns the `WebAssembly.Memory` and `WebAssembly.Table`; an optional Compiler Worker only ever produces `WebAssembly.Module` objects for it to instantiate.** See the "Browser execution architecture" section above β€” this is not something that needs `SharedArrayBuffer` to implement at baseline. - **Guest CPU state = a fixed-offset struct in the Emulation Worker's linear memory.** Agreed and documented before any codegen work starts; cross-block jumps and register-file access go through this shared state. - **No native "fastmem" trick.** WASM memory accesses have mandatory bounds-checking *semantics* β€” an out-of-bounds access must trap β€” though the browser's compiler may optimize or eliminate the actual check where it can prove it's unneeded. Either way, the backend can never request raw, unchecked native-pointer access the way a traditional emulator's fastmem mapping does. Plan around this as a real perf ceiling in Phase 9, not a bug to chase. - **Memory model: WASM32, not WASM64.** memory64 is unsupported in current stable Safari (desktop and iOS both β€” and iOS mandates WebKit for every browser, so this is the entire iOS/iPadOS audience, not just desktop holdouts), though WebKit is actively developing support, so treat this as "we don't currently need WASM64," not "Safari permanently blocks it." The Switch's usable guest RAM (~3.2–3.4GB on original hardware) is *close to* WASM32's 4GiB cap, not comfortably under it once you add CPU state, HLE structures, the WASM block cache, and Emscripten runtime overhead on top β€” so back it with a sparse/chunked allocation strategy (grow linear memory as needed, don't reserve it all up front) rather than treating the full 3.4GB as safe headroom. The `.nsp` file itself (10GB+) is never loaded into linear memory at all regardless of memory model β€” see Phase 3. - **Cache-miss handling needs a defined hybrid path, not just a JIT/no-JIT binary.** Because compilation happens in accumulated batches rather than instantly on first encounter, a guest PC can legitimately be reached before its block has been compiled yet. Keep the interpreter fallback wired in *permanently*, not just as an emergency plan: on a cache miss, interpret that one occurrence while scheduling the block for the next compilation batch, so there's always a correct (if slow) path forward rather than a stall waiting on compilation. - **Cache key is more than raw PC.** A block's correct translation can depend on relevant processor state, not just address β€” don't let the cache collapse to `map<PC, Block>`. Include the CPU state bits that affect codegen and a code-page version/generation number (for the self-modifying-code case below) as part of the cache key. - **Compiled-block lifecycle needs an eviction policy.** A real play session generates far more unique translated blocks than should stay resident as live `Table` entries forever. Needs an LRU-style eviction strategy from the start, same idea as any JIT code cache β€” don't let this grow unbounded and call it done. - **Self-modifying code needs block invalidation.** Homebrew and some titles patch their own code at runtime. dynarmic's native backends already have memory-write hooks that invalidate stale cached blocks when this happens; the WASM backend needs the equivalent β€” bumping the relevant code page's generation number and replacing the affected `Table` slot(s) β€” or stale translated code will silently keep running after a guest-side patch. - **Full ARM64 IR coverage is not an afterthought β€” see Phase 2b below before attempting a real game.** EOF ```