Self-modifying code should not be considered lightly, but can sometimes bring big performance gains. If you've been programming for very long, you've probably used it without realizing it.
Prior to the widespread use of the 486 and higher, many PCs did not include hardware floating support. This left people writing programs involving floating point with a dilemma. If they compiled their program to use in-line floating point instructions it would run fast on a machine with a floating point processor, and not at all on machines without one. If they compiled their program with software floating point emulation, it would run on all machines, but slowly even on machines with hardware floating point.
Many compilers libraries used an interesting trick with self-modifying code. The default behavior was to put a trap instruction where a floating point operation was needed. The trap handler would either emulate the instruction in software, or if it detected it was running on a machine with floating point hardware, it would modify the code by replacing the trap instruction with the appropriate hardware floating point instruction and execute it. The result was software that ran on all machines, and ran almost as fast on a machine with floating point hardware as if the code had been compiled to use floating point hardware directly (since most floating point intensive operations occur in loops that are executed many times).