Search code examples
interpreterbytecodejitvm-implementation

How does a VM switch from interpreting to running JITed code?


There are some great explanations of JIT on stackoverflow, like this one.

One thing all these explanations leave me wondering is this:

If half of the bytecode has been JITed, then presumably you have a mix of bytecode and machine code, or at some point you will need to switch between interpreting bytecode and running compiled machine code.

How does a virtual machine transition from one scheme to the other?

Related: this question is specifically about java, I'm looking for a general answer about implementation strategies


Solution

  • Most any software problem can be solved with an extra level of indirection. A VM keeps track of loaded functions in a table that contains the machine code address of the code that runs when the CALL byte code is encountered. This address is at first not the actual address of the function.

    A tracing jitter initializes it to an entrypoint in the interpreter. So when the function is called, it simply keeps interpreting the byte code of the function. Also keeping statistics to determine what is "hot". When the hotness factor is high enough, it runs the jitter to translate the function's byte code to machine code. And patches the address in the table to that machine code. So the next CALL now automagically jumps to that machine code instead of the interpreter entrypoint.

    A non-tracing jitter does much the same, the address in the table is initialized to an entrypoint in the jitter. The CALL byte code is translated to a machine code call to that address. Eventually the processor executes that call and lands in the jitter. Which generates the machine code for the function and patches the call instruction to that machine code address. As well as the table entry. So any subsequent call to the same function now automagically bypasses the jitter and executes the machine code directly.