Perché un codice byte VM utilizza stack o registri contrapposti alle operazioni dirette? [duplicare]

1

Sto lavorando su un semplice interprete bytecode per imparare come funzionano le macchine virtuali. Ho letto di VM e sembra che tutti siano basati su stack o registrati. All'epoca aveva senso, ma poi ho iniziato a lavorare sul mio interprete e ho capito cosa sto facendo? (Ho deciso su una macchina di registro.) Sto semplicemente caricando tutti questi valori dai puntatori a questi registri finti, facendo operazioni, quindi inserendo il risultato nel registro e copiando su un puntatore. Ho pensato, perché non farlo direttamente? Perché creare registri falsi che servono solo da sovraccarico. Forse non ne so abbastanza delle VM ancora per capire appieno, ma a me sembrerebbe che il mio modo sarebbe molto più veloce.

    
posta Synaps3 20.10.2014 - 06:13
fonte

2 risposte

3

Il lavoro di una VM è eseguire codice arbitrario. (Metti da parte i pensieri della JIT per un secondo, stiamo solo parlando della pura VM). Come ti aspetti di compilare programmi arbitrari in bytecode per la tua VM? Devi avere un set di istruzioni. Quel set di intruction deve funzionare su qualcosa. Se parli di variabili che operano direttamente da un linguaggio di alto livello, assicurati che sia possibile, ma in realtà non è una VM. È solo un interprete. Un interprete può essere simbolico (cioè può lavorare sui nomi) e può essere di alto livello. Può operare su nodi AST o su testo puro. Ma una VM generalmente è di livello più basso, emula una CPU semplice e il bytecode stesso è una forma di linguaggio intermedio. Quindi puoi distribuire i tuoi programmi in forma bytecode che ti dà un certo livello di protezione (anche se in Java e nel mondo .NET non quanto la VM fosse un po 'più basso).

Il lato negativo degli interpreti di alto livello è che fanno un sacco di lavoro simbolico ogni volta che esegui il programma. La forza di una VM arriva quando combinata con il compilatore. Prendi C # per esempio. Certo, puoi scrivere un programma per interpretare C # ad un livello elevato (l'ho fatto per Parrot VM anni fa), ma l'esecuzione è molto più efficiente se fai un po 'di lavoro in anticipo come le variabili di compilazione fino ai registri o allo stack operandi. Quindi il tuo programma diventa molto più denso di AST, ed è di nuovo, opportunamente rappresentato come denso codice byte.

Si definiscono le operazioni virtuali più semplici o più vicine alle istruzioni reali da compilare in tempo reale con il real eseguibile nativo da parte di JITter, noto anche come Just in Time Compilation. Più semplici sono gli op, più efficiente è l'implementazione di JITter.

Inoltre, in modalità non JIT (pura operazione VM) se si compilano gli accessi simbolici in basso per registrare o impilare gli operandi, è possibile migliorare le prestazioni della VM assicurandosi che l'area del registro centrale sia memorizzata nella cache. Se le variabili sono in tutto lo heap e si sceglie di operare direttamente su di esse, si perde l'efficienza della cache. Con un registro VM potremmo progettarlo con registri fissi, o registri virtuali illimitati, ma in ogni caso, un allocatore li mapperà a registri fissi che sono impacchettati in un segmento contiguo di RAM che speriamo si adatti a meno linee di cache L1 e guadagna prestazioni per località spaziale.

Ma è possibile una VM simbolica di alto livello che opera direttamente sulle variabili. Ecco un paio di alternative.

VM A - Simbolico:

newglobal "count"
add "count", 1

VM B - Registra (trasferimento di memoria):

mkglobal "count"
move $R1, "count"
inc $R1

Il vantaggio di VM B è che gli opcode sono molto vicini o facilmente traducibili a processori live reali, poiché la maggior parte dei processori non consente operazioni su variabili di memoria e richiedono di caricare la variabile in un registro o su uno stack. La squadra investigativa avrà molto meno da fare. Con VM A, l'operazione add è in realtà una grossa operazione di aggregazione che deve essere compilata in singole operazioni native.

Per un semplice confronto con i due approcci, studiare Perl5 e studiare bytecode C # /. NET. Mentre è possibile compilare Perl5 su un bytecode, non è la forma predefinita di esso. Perl fa davvero più interpretazione simbolica di un AST in memoria. Ecco perché è stato un grande cambiamento architettonico quando abbiamo scritto Parrot per Perl6, anche se non sono convinto che fosse la decisione giusta per quel linguaggio.

    
risposta data 20.10.2014 - 07:48
fonte
1

C'è un terzo tipo di macchina virtuale che potresti voler investigare. Questo tipo utilizza un modello di esecuzione denominato Static Single Assignment, che elimina i registri e impila in favore di una nozione più astratta di un risultato che viene calcolato una volta e quindi utilizzato una o più volte, ma il bytecode non dice nulla su come viene memorizzato .

L'implementazione più conosciuta di questa idea è LLVM, quindi potresti voler vedere come hanno fatto.

    
risposta data 20.10.2014 - 10:27
fonte

Leggi altre domande sui tag