My original idea was that the translation from source code to IR should be quick, and the IR should be in a format that's reasonably quick for the interpreter to execute. Doing a naive JIT compilation of this IR to machine code isn't difficult and provides very significant speedups. The code I've refactored so far makes a lot more sense, probably because it decouples the problem of extracting the core language semantics out of the AST from the problem of assigning stack slots to values in a semi-efficient manner, resulting in overall simpler code.