Modern hardware and operating systems require special care when doing this. Firstly, the D-cache must be cleaned and the I-cache invalidated for the relevant memory addresses. This is because the CPU does not in general maintain coherency between these caches for performance reasons (writing instructions is rare). Secondly, memory protection must be set to allow execution of the generated code. Some systems even forbid pages being writeable and executable at the same time in order to make code injection attacks that little bit harder.
By modern hardware I assume you do not mean x86? Self-modifying code (without flushing the I-cache) is still allowed even on the latest processors. Modern OSes indeed do not mark all pages read/execute by default, but that takes a 1-line mprotect() or VirtualProtect() to change. Also, on RISC architectures, D-caches are generally not cleared as part of that process. After all, you are writing instructions, not data.
To the store instruction everything is data, even if it happens to be instructions. Stores initially go the L1 D-cache, and unless the I and D caches are coherent, explicit cleaning (D) and invalidating (I) is required. Maybe they are coherent on x86, but I know with certainty that they are not on for example ARM.
Comments
Modern hardware and operating systems require special care when doing this. Firstly, the D-cache must be cleaned and the I-cache invalidated for the relevant memory addresses. This is because the CPU does not in general maintain coherency between these caches for performance reasons (writing instructions is rare). Secondly, memory protection must be set to allow execution of the generated code. Some systems even forbid pages being writeable and executable at the same time in order to make code injection attacks that little bit harder.
By modern hardware I assume you do not mean x86? Self-modifying code (without flushing the I-cache) is still allowed even on the latest processors. Modern OSes indeed do not mark all pages read/execute by default, but that takes a 1-line mprotect() or VirtualProtect() to change. Also, on RISC architectures, D-caches are generally not cleared as part of that process. After all, you are writing instructions, not data.
To the store instruction everything is data, even if it happens to be instructions. Stores initially go the L1 D-cache, and unless the I and D caches are coherent, explicit cleaning (D) and invalidating (I) is required. Maybe they are coherent on x86, but I know with certainty that they are not on for example ARM.
x86 is cache-coherent; we can directly modify code at runtime and expect things to work.