An Intel 8086 instruction decoder and (partial) simulator, written in Rust as part of Casey Muratori's Performance-Aware Programming course.
It reads a binary of assembled 8086 machine code, disassembles it back into NASM-style assembly, and can optionally simulate the effect of instructions on CPU registers and flags.
# Disassemble a binary (defaults: -a ./listing_37, -o ./output_recent.asm)
cargo run -- -a listing_39 -o listing_39_out.asm
# Disassemble and simulate register state
cargo run -- -a listing_39 -o listing_39_out.asm --sim| Flag | Description | Default |
|---|---|---|
-a, --asm-bin-path <PATH> |
Path to the assembled 8086 binary to decode | ./listing_37 |
-o, --output-file <PATH> |
Where to write the disassembled listing | ./output_recent.asm |
-s, --sim |
Simulate register/flag effects while decoding | off |
With --sim, register changes are appended to the listing as comments and the final register state is printed at the end:
mov ax, 1
; ax: 0x00 -> 0x01The repository includes the course listing files (listing_37 … listing_46), both as assembled binaries and as their .asm sources, for testing. To check the decoder's correctness, assemble its output with nasm and compare it against the original binary.
src/
├── main.rs Entry point: reads the binary, runs the decoder, writes output
├── cli.rs Command-line arguments (clap)
├── cpu_state.rs Registers, flags, and register read/write helpers for simulation
└── decoder/
├── mod.rs Decode loop and opcode dispatch
├── stream.rs Byte-stream helpers (u8 / i8 / i16 / u16 reads)
├── fields.rs REG / R/M / accumulator lookup tables
├── operand.rs Shared ModRM, displacement, and immediate decoding
├── mov.rs MOV family handlers
├── arithmetic.rs ADD / SUB / CMP handlers
└── jump.rs Conditional jump handlers
The dispatch loop in decoder/mod.rs matches opcode prefixes from shortest (4 bits) to longest (a full byte) and hands each instruction to a family-specific handler. The handlers share the ModRM/addressing-mode logic in operand.rs, since every two-operand 8086 instruction encodes its operands the same way.
MOV
- Register/memory to/from register
- Immediate to register
- Immediate to register/memory
- Memory to/from accumulator
ADD / SUB / CMP
- Register/memory with register to either
- Immediate to register/memory
- Immediate to accumulator
Conditional jumps: je, jl, jle, jb, jbe
Simulation (--sim) is intentionally partial — it covers what the course exercises needed at this point:
movimmediate → register, and register → registeradd/subimmediate → register, including the zero flag- The sign flag,
cmpsimulation, memory operands, and the instruction pointer are not simulated yet
- Only the instruction subset above is decoded; unknown opcode bytes are skipped silently
- Jump targets are emitted as raw signed displacements (e.g.
je -4) rather than labels, so listings with jumps won't reassemble as-is - Segment registers, string operations, and most of the rest of the 8086 ISA are not implemented