Puoi dividere qualsiasi codice in parti costituite da una sequenza lineare di passaggi separati da variazioni del flusso di controllo (rami, chiamate di funzioni, resi, ecc.)
Per eseguire la compilazione JIT, inizi immediatamente dopo una modifica del flusso di controllo, esegui la scansione in avanti fino a trovare la successiva modifica del flusso di controllo, converti la sequenza lineare di passaggi in codice nativo, ottimizza il codice nativo, posiziona un modifica del flusso di controllo "alla fine di esso, quindi memorizzare il risultato (chiamiamolo" una traccia ") in una sorta di" trace cache "ed eseguire la traccia. Se non c'è traccia nella cache per la parte successiva del codice, allora quella "modifica del flusso di controllo finale" può passare il flusso di controllo alla macchina virtuale. Se c'è una traccia per il prossimo pezzo di codice, allora quel "cambiamento di controllo del flusso di terminazione" può passare il controllo direttamente (o indirettamente) alla traccia successiva. Nota: qui sto semplificando eccessivamente - che "terminare il cambiamento del flusso di controllo" può avere 2 o più destinazioni (ad esempio può essere un ramo, dove una destinazione è il percorso "vero" e l'altra è il "falso") percorso) e potrebbe richiedere assistenza dalla macchina virtuale anche se la traccia successiva si trova nella cache.
Il problema qui è che è tutto costoso (e più difficile è cercare di ottimizzare il codice nativo generato più è costoso). Se la sequenza di codice viene eseguita spesso, il sovraccarico della compilazione JIT diventa insignificante e c'è un guadagno in termini di prestazioni. Se la sequenza di codice non viene eseguita spesso, il costo della compilazione JIT può facilmente superare i vantaggi.
Ora; se non c'è traccia per il prossimo pezzo di codice, allora "terminare il controllo del flusso di cambiamento" può passare il flusso di controllo alla macchina virtuale. Invece della compilazione JIT, nulla impedisce alla macchina virtuale di interpretare a questo punto. Se si decide di eseguire la compilazione JIT, è possibile correggere la "modifica del flusso di controllo terminata" della traccia precedente in modo che punti alla nuova traccia generata. Se decidi di interpretare invece; allora va bene - immediatamente dopo che l'interprete interpreta un cambiamento nel flusso di controllo, controlla la cache di traccia per vedere se c'è una traccia "già nativa" per il pezzo successivo. Se esiste, è possibile eseguire la traccia già compilata e, in caso contrario, può semplicemente continuare a interpretare o decidere di passare alla compilazione JIT.
L'unica altra cosa che dovresti fare è decidere quando usare JIT e quando interpretare. In genere ci sono alcuni suggerimenti che è possibile utilizzare per dedurre che il JIT è probabile che valga la pena (in particolare, i rami condizionali presi dove la destinazione si trova in un indirizzo inferiore tipicamente indicano cicli e i loop sono buoni candidati per la compilazione JIT). Oltre a questo è necessario mantenere alcune statistiche (come il numero di volte in cui qualcosa è stato interpretato) e usarle come base per la decisione "JIT o interpretare".