HLSL is arguably very different, because (1) we do not put effort into optimizing compilation speed at all, (2) we have to do a *lot* of optimization and lowering passes.
I'm not sure I'd go as far as saying it's very different, but it does have different priorities and emphasis, sure.
If we want the cleanest and safest approach, at the cost of speed, we'd obviously just have helpers that add one instruction at a time, or even helpers that build a "block" of multiple instructions that you can add all at once. That might be less efficient than reserving space and then initialising the instructions as they're done here, though, although I don't know if the difference matters. If it's cases like what's in this patch, just adding one instruction at a time, it probably doesn't...
I don't think it has to be inefficient; for shader_normaliser_eliminate_phase_instance_id() in particular, there are essentially three kinds of modification we do:
- Modifying existing instructions in-place. - Deleting instructions. - Splicing in clones of a range of instructions.
In-place modification is trivial. Deletion can be done by (in-place) replacement of instructions with NOPs. We could do a subsequent cleanup pass to consolidate non-NOP instructions, but for the SPIR-V backend that's not even needed. Splicing is less trivial, but we could essentially just clone instructions into a temporary buffer first, and then memmove() them in. memmove() is not free, but it's not clear to me that it would necessarily be worse than cloning the entire shader one instruction at a time. Going a step further, we could allow ownership of the instruction array to be transferred from the parser to the backend on parser destruction, and avoid the initial copy the normaliser does as well.
That said, if the general consensus is "we'll cross that bridge when we'll get to it", I won't object; "when we'll get to it" does have the unfortunate tendency to be some inopportune time though.