Self-Modifying Code
Code that rewrites itself
Self-modifying code changed its own instructions at runtime, enabling impossible optimisations on 8-bit systems by treating code as data.
Overview
On the 6502, indexing memory costs extra cycles. What if the address in your instruction could change dynamically? Self-modifying code rewrote instructions at runtime—changing addresses, operation codes, or branch targets. It was fast, dangerous, and essential for achieving the impossible on 8-bit hardware.
The problem
Standard indexed addressing:
lda table,x ; 4-5 cycles, X is index
But what if you need to change which table?
The solution
Modify the instruction itself:
lda table ; 4 cycles, absolute addressing
; Elsewhere, change the address:
lda #<new_table
sta load_addr+1 ; Modify low byte
lda #>new_table
sta load_addr+2 ; Modify high byte
Now lda table loads from new_table.
Common uses
Unrolled loops
lda $0400 ; First iteration
sta $d020
lda $0401 ; Second iteration (address modified)
sta $d020
; ...repeat, modifying addresses
Dynamic branch targets
jump_target = *+1
jmp $0000 ; Address modified at runtime
Variable table selection
Switch between data sources without index overhead.
6502 advantages
| Factor | Benefit |
|---|---|
| Von Neumann | Code and data same memory |
| No cache | No stale instruction problems |
| Absolute addressing | Faster than indexed |
Examples
Sprite multiplexer
lda #sprite_y_1
sta store_y+1 ; Modify destination
store_y:
sta $d001 ; Y position (address changes)
Music player
lda pattern_ptr
sta fetch+1
fetch:
lda $0000 ; Address modified each note
Dangers
| Risk | Consequence |
|---|---|
| Timing bugs | Wrong code executed |
| Debugging | Hard to trace |
| Maintenance | Confusing to read |
| Portability | Platform-specific |
Z80 differences
Z80 self-modification:
- Slower due to instruction prefetch
- Some instructions partially cached
- Still used, more carefully
Modern perspective
Self-modifying code today:
- Usually forbidden (security)
- JIT compilation uses similar concepts
- No longer necessary for speed
- Historical curiosity
Debugging tips
| Technique | Purpose |
|---|---|
| Mark modifications | Comment clearly |
| Initialise explicitly | Don’t assume values |
| Test boundaries | Check modified ranges |