Which is better: tracking the register allocations or jumping freely in and out of the code.
At the Department, we have some experince with NanoJIT. (There is even a guy here, whom we call King LIR.) The next thing I want to show you is a simple LIR example. It may help you to understand what NanoJIT actually does.
ins0 = IMMEDIATE()  ins1 = IMMEDIATE() [array_address] ins2 = LOAD(ins1) [mem_offset_1] ins3 = LOAD(ins1) [mem_offset_2] ins4 = ADD(ins2, ins3) ins5 = MUL(ins0, ins4) STORE(ins5, ins1) [mem_offset_3]
As you can see, a LIR instruction can depend on constants (immediate values, memory offsets) or on the result of other LIR instructions. The terms 'LIR instruction', and 'result of LIR instruction' refer to the same thing in our context. I will simply use 'instruction' from now on, because it is the shortest form. One can also notice, that physical memory addresses are calculated by adding a base instruction and an immediate offset.
During compilation, NanoJIT tries to keep the frequently used instructions in registers and trace the stack usage. It works efficiently if there aren't any back-edges. (Backward jumps, in other words. They are mainly used by loops.) In the past, NanoJIT was a tracing JIT, and the support of back edges has only been added recently. Unfortunately, NanoJIT performs poorly in the presence of back edges. A better register allocation algorithm might reduce the number of unnecessary stack stores and loads after a back-edge.
Well, if someone is still interested in the continuation of this work, just let us know.