Code that rewrites itself
Today it's close to forbidden — your operating system marks code pages read-only and refuses to run pages you can write to, and the CPU's instruction cache would ignore your edits anyway. But on the 6502 a program was just bytes in RAM, indistinguishable from any other bytes. So a program could reach over and change its own instructions while running — and the best coders did, constantly.
There is no line between code and data
An instruction like lda $d020 is three bytes in memory: the opcode, then the low and high bytes of the address. Nothing stops you writing to those last two bytes — they’re RAM like everything else. Change them and you’ve changed where the next run of that instruction reads from:
lda newaddr
sta target+1 ; overwrite the operand's low byte...
lda newaddr+1
sta target+2 ; ...and high byte, of the instruction below
target: lda $0000 ; the $0000 was just rewritten in place
sta (screen),ytarget is set by the code above it. The program edits itself a few bytes before executing the result.Why anyone would do this
Speed, when every cycle counted. Baking a value straight into the instruction skips loading it from memory each pass — patch the operand once, outside the loop, and the inner loop runs leaner forever after. In a tight rendering routine that saving, multiplied by thousands of iterations, was the difference between hitting the frame and missing it. It was an optimisation, not a party trick.
And the idea never died — it just moved up the stack and put on a suit. A JIT compiler writes fresh machine code into memory and jumps to it. A V8 or a JVM does exactly that, millions of times a second. Generating code at runtime is mainstream; the 6502 just let you do it to yourself, in place, with no safety net.
Why it’s “forbidden” now
Three things changed. Instruction caches mean the CPU may be running a stale copy of code you just edited. Memory protection — W^X — lets a page be writable or executable, never both, precisely because self-modifying code is also how attackers inject theirs. And a modern pipeline hates it: rewriting code ahead of the program counter forces expensive flushes. The technique didn’t become wrong — the costs and the threat model moved.
“Code is data” sounds like a slogan until you’ve watched a program reach down and rewrite its own next instruction. Every JIT, every runtime codegen, every page you’re not allowed to write-then-execute is a descendant of that one liberty the 6502 took for granted — and of the reasons we later took it away.