Your analysis has two fatal flaws: Firstly, you're looking at it from the perspective of the CPU core, and secondly, it's wrong.
Second flaw first, I'm not sure why the ARM7TDMI manual has such an oddly overly-specific wording (you can't realistically do all those things separately anyway) but all 3 aspects are performed by the subs pc, lr, #4 line in your code. subs pc, lr is specifically an exception return instruction - it atomically restores the CPSR from the SPSR and returns to the fixed-up LR address. Since the CPU's interrupt disable flags are in the CPSR, then assuming that the I bit in that SPSR value is clear (which you kind of expect, given that you took an IRQ to get here...), IRQs will also be unmasked automatically in the process.
Thus, from the CPU core's point of view, your code is already (by virtue of its final instruction) doing everything an exception handler needs to, i.e. it resumes execution at the same point and in the same state as when the IRQ happened - there's certainly nothing wrong with the compiler. What it doesn't do, though, is anything about the fact that the peripheral way beyond the core is still asserting its interrupt, therefore the next thing you do after returning from the IRQ is to happily take the same IRQ again immediately. Repeat ad infinitum.
You have at least gone beyond the core as far as the interrupt controller; writing to VICVectAddr acknowledges the interrupt at the VIC, but that merely frees the VIC itself up to prioritise any pending interrupts and deliver the next one, which since the external source is still asserted (and assuming no higher-priority IRQ has come in from elsewhere), is still the same one.
To actually handle the interrupt and make any progress, you need to service the I2C peripheral raising it. Section 13.9 of the LPC213x manual outlines what needs to be done for each interrupt condition, but in terms of clearing the asserted interrupt, you need to write the SIC field of I2C0CONCLR.
VICVectAddr = 0;is clearing the interrupt, then this code is performing all 3 quoted steps appropriately (hint: read up on whatsubs pc,...actually does) - Notlikethatsubs pc.... Having gone away and found the LPC213x manual, I see that that write does ack the interrupt at the VIC, but the interrupt is apparently level-triggered and you're doing nothing to deassert it at the I2C controller end (see section 13.9). Hence when you return you're just immediately taking the same interrupt again, ad infinitum. - NotlikethatVICVectAddris likely wrong (peripheral register names are typically all-uppercase) or insufficient. OP should read the reference manual. - too honest for this site