MIPS Interpreter Implementation
Overview
This skill provides guidance for implementing a MIPS interpreter/emulator capable of running compiled MIPS ELF binaries. The key insight is that building an interpreter is fundamentally an engineering task requiring iterative implementation, not a pure research problem. Start coding early and iterate.
Critical Anti-Pattern: Analysis Paralysis
The most common failure mode is excessive research without implementation. Avoid:
- Reading every source file before writing any code
- Trying to understand every detail of the target program
- Researching exhaustively before creating a basic structure
Instead: Create a skeleton implementation early, then fill in components as understanding grows.
Implementation Strategy
Phase 1: Scaffold First (Start Here)
Create the basic interpreter structure immediately, before deep research:
// vm.js - Create this skeleton FIRST
class MIPSInterpreter {
constructor() {
this.registers = new Uint32Array(32);
this.memory = null; // Will be ArrayBuffer
this.pc = 0;
this.hi = 0;
this.lo = 0;
}
loadELF(buffer) { /* TODO */ }
step() { /* TODO */ }
run() { /* TODO */ }
handleSyscall() { /* TODO */ }
}
Phase 2: ELF Loading
Implement ELF parsing to load the binary:
- Parse ELF header to get entry point and program headers
- Load PT_LOAD segments into memory at specified virtual addresses
- Initialize PC to entry point (typically 0x400000 range for MIPS)
- Handle BSS section (zero-initialized, may be large)
Key ELF details for MIPS:
- Machine type: 0x08 (MIPS)
- Check endianness from ELF header (e_ident[EI_DATA])
- Entry point from e_entry field
- Program headers define memory layout
Phase 3: Core Instruction Execution
Implement instruction decode and execute loop:
- Fetch: Read 4 bytes at PC
- Decode: Extract opcode (bits 31-26) and determine format (R/I/J)
- Execute: Perform the operation
- Advance: Update PC (normally PC += 4, handle branches/jumps)
Start with these essential instruction categories:
- ALU: ADD, ADDU, SUB, AND, OR, XOR, NOR, SLT, SLTU
- Immediate: ADDI, ADDIU, ANDI, ORI, XORI, SLTI, SLTIU, LUI
- Shifts: SLL, SRL, SRA, SLLV, SRLV, SRAV
- Memory: LW, LB, LBU, LH, LHU, SW, SB, SH
- Branches: BEQ, BNE, BGTZ, BLEZ, BLTZ, BGEZ
- Jumps: J, JAL, JR, JALR
- Special: SYSCALL, MFHI, MFLO, MULT, MULTU, DIV, DIVU
Phase 4: Syscall Emulation
Implement syscalls the target program uses. Common syscalls:
- File I/O: open, close, read, write
- Memory: brk, mmap
- Process: exit
- Time: gettimeofday
Map syscall numbers to handlers. Syscall number is in $v0 ($2), arguments in $a0-$a3 ($4-$7).
Phase 5: Test and Iterate
Run the binary, observe failures, fix issues:
- Unimplemented instructions will crash - implement as discovered
- Missing syscalls will fail - add as needed
- Memory issues will manifest - debug with logging
Verification Strategy
Incremental Testing Milestones
- ELF loads: Entry point and segments loaded correctly
- First instruction executes: PC advances, registers change
- First function call works: JAL/JR, stack operations
- First syscall handled: write() outputs something
- Program runs to completion: exit() called normally
Debugging Techniques
- Log PC and instruction at each step (disable for performance later)
- Print register state on syscalls
- Compare against reference MIPS emulator (SPIM, MARS) for simple programs
- Create minimal test programs to verify specific instructions
Common Pitfalls
1. Ignoring Delay Slots
MIPS has branch delay slots - the instruction after a branch executes before the branch takes effect. Many interpreters skip this for simplicity (works for most compiled code).
2. Sign Extension Errors
- LB, LH sign-extend; LBU, LHU zero-extend
- Immediate values in I-type instructions are sign-extended (except for logical ops)
- Failing to sign-extend causes subtle bugs
3. Endianness Mismatch
- MIPS can be big or little endian (check ELF header)
- JavaScript TypedArrays use host endianness
- May need DataView for explicit endianness control
4. Memory Size Underestimation
- BSS sections can be very large (>1GB for programs like Doom)
- Use sparse memory representation or on-demand allocation
- Don't allocate full address space upfront
5. Unsigned vs Signed Arithmetic
- JavaScript numbers are signed; use
>>> 0 for unsigned comparison
- ADDU, SUBU don't trap on overflow; ADD, SUB do (rarely matters)
- SLT vs SLTU: signed vs unsigned comparison
6. Multiplication Result Registers
- MULT/MULTU put 64-bit result in HI:LO
- MFHI/MFLO retrieve the values
- Don't forget to implement these
Time Management
For a complex interpreter task:
- Hour 1: Scaffold + ELF loading
- Hour 2: Core ALU and memory instructions
- Hour 3: Branches, jumps, and basic syscalls
- Hour 4+: Iterate based on runtime failures
An incomplete but running interpreter beats thorough research with no code.
Resources
Refer to references/mips_instruction_reference.md for instruction encoding details and opcode tables.
1---2name: make-mips-interpreter3description: Guide for implementing MIPS CPU interpreters/emulators, particularly for running compiled MIPS ELF binaries. This skill applies when building virtual machines to execute MIPS32 code, creating emulators for retro game ports (like Doom), or implementing CPU simulators. Use for tasks involving ELF parsing, instruction decoding, syscall emulation, and memory management for MIPS architecture.4---56# MIPS Interpreter Implementation78## Overview910This skill provides guidance for implementing a MIPS interpreter/emulator capable of running compiled MIPS ELF binaries. The key insight is that building an interpreter is fundamentally an **engineering task requiring iterative implementation**, not a pure research problem. Start coding early and iterate.1112## Critical Anti-Pattern: Analysis Paralysis1314The most common failure mode is excessive research without implementation. Avoid:15- Reading every source file before writing any code16- Trying to understand every detail of the target program17- Researching exhaustively before creating a basic structure1819Instead: Create a skeleton implementation early, then fill in components as understanding grows.2021## Implementation Strategy2223### Phase 1: Scaffold First (Start Here)2425Create the basic interpreter structure immediately, before deep research:2627```javascript28// vm.js - Create this skeleton FIRST29class MIPSInterpreter {30 constructor() {31 this.registers = new Uint32Array(32);32 this.memory = null; // Will be ArrayBuffer33 this.pc = 0;34 this.hi = 0;35 this.lo = 0;36 }3738 loadELF(buffer) { /* TODO */ }39 step() { /* TODO */ }40 run() { /* TODO */ }41 handleSyscall() { /* TODO */ }42}43```4445### Phase 2: ELF Loading4647Implement ELF parsing to load the binary:48491. Parse ELF header to get entry point and program headers502. Load PT_LOAD segments into memory at specified virtual addresses513. Initialize PC to entry point (typically 0x400000 range for MIPS)524. Handle BSS section (zero-initialized, may be large)5354Key ELF details for MIPS:55- Machine type: 0x08 (MIPS)56- Check endianness from ELF header (e_ident[EI_DATA])57- Entry point from e_entry field58- Program headers define memory layout5960### Phase 3: Core Instruction Execution6162Implement instruction decode and execute loop:63641. **Fetch**: Read 4 bytes at PC652. **Decode**: Extract opcode (bits 31-26) and determine format (R/I/J)663. **Execute**: Perform the operation674. **Advance**: Update PC (normally PC += 4, handle branches/jumps)6869Start with these essential instruction categories:70- **ALU**: ADD, ADDU, SUB, AND, OR, XOR, NOR, SLT, SLTU71- **Immediate**: ADDI, ADDIU, ANDI, ORI, XORI, SLTI, SLTIU, LUI72- **Shifts**: SLL, SRL, SRA, SLLV, SRLV, SRAV73- **Memory**: LW, LB, LBU, LH, LHU, SW, SB, SH74- **Branches**: BEQ, BNE, BGTZ, BLEZ, BLTZ, BGEZ75- **Jumps**: J, JAL, JR, JALR76- **Special**: SYSCALL, MFHI, MFLO, MULT, MULTU, DIV, DIVU7778### Phase 4: Syscall Emulation7980Implement syscalls the target program uses. Common syscalls:81- File I/O: open, close, read, write82- Memory: brk, mmap83- Process: exit84- Time: gettimeofday8586Map syscall numbers to handlers. Syscall number is in $v0 ($2), arguments in $a0-$a3 ($4-$7).8788### Phase 5: Test and Iterate8990Run the binary, observe failures, fix issues:91- Unimplemented instructions will crash - implement as discovered92- Missing syscalls will fail - add as needed93- Memory issues will manifest - debug with logging9495## Verification Strategy9697### Incremental Testing Milestones98991. **ELF loads**: Entry point and segments loaded correctly1002. **First instruction executes**: PC advances, registers change1013. **First function call works**: JAL/JR, stack operations1024. **First syscall handled**: write() outputs something1035. **Program runs to completion**: exit() called normally104105### Debugging Techniques106107- Log PC and instruction at each step (disable for performance later)108- Print register state on syscalls109- Compare against reference MIPS emulator (SPIM, MARS) for simple programs110- Create minimal test programs to verify specific instructions111112## Common Pitfalls113114### 1. Ignoring Delay Slots115MIPS has branch delay slots - the instruction after a branch executes before the branch takes effect. Many interpreters skip this for simplicity (works for most compiled code).116117### 2. Sign Extension Errors118- LB, LH sign-extend; LBU, LHU zero-extend119- Immediate values in I-type instructions are sign-extended (except for logical ops)120- Failing to sign-extend causes subtle bugs121122### 3. Endianness Mismatch123- MIPS can be big or little endian (check ELF header)124- JavaScript TypedArrays use host endianness125- May need DataView for explicit endianness control126127### 4. Memory Size Underestimation128- BSS sections can be very large (>1GB for programs like Doom)129- Use sparse memory representation or on-demand allocation130- Don't allocate full address space upfront131132### 5. Unsigned vs Signed Arithmetic133- JavaScript numbers are signed; use `>>> 0` for unsigned comparison134- ADDU, SUBU don't trap on overflow; ADD, SUB do (rarely matters)135- SLT vs SLTU: signed vs unsigned comparison136137### 6. Multiplication Result Registers138- MULT/MULTU put 64-bit result in HI:LO139- MFHI/MFLO retrieve the values140- Don't forget to implement these141142## Time Management143144For a complex interpreter task:145- **Hour 1**: Scaffold + ELF loading146- **Hour 2**: Core ALU and memory instructions147- **Hour 3**: Branches, jumps, and basic syscalls148- **Hour 4+**: Iterate based on runtime failures149150An incomplete but running interpreter beats thorough research with no code.151152## Resources153154Refer to `references/mips_instruction_reference.md` for instruction encoding details and opcode tables.