Domanda

Implementerò una macchina virtuale in x86 e mi chiedo che tipo di design produrrebbe i migliori risultati. Su cosa dovrei concentrarmi per spremere il succo? Intendo implementare l'intera macchina virtuale nell'assembly x86.

Non ho molte istruzioni e posso scegliere il loro modulo. Le istruzioni si proiettano direttamente nella sintassi di smalltalk in blocchi. Do il disegno delle istruzioni a cui stavo pensando:

^ ...       # return
^null     # return nothing
object    # address to object
... selector: ... # message pass (in this case arity:1 selector: #selector:)
var := ... # set
var # get

Il tipo di VM a cui stavo pensando:

mov eax, [esi]
add esi, 2
mov ecx, eax
and eax, 0xff
and ecx, 0xff00 # *256
shr ecx, 5          # *8
jmp [ecx*4 + operations]
align 8:
    operations:
dd retnull
dd ret
# so on...
    retnull:          # jumps here at retnul
# ... retnull action
    ret:
# ... ret action
#etc.

Non iniziare a chiedere perché ho bisogno di un'altra implementazione di macchina virtuale. Le routine interpretative non sono roba di scorta che raccogli ogni volta che ne hai bisogno. La maggior parte delle macchine virtuali che proponete altrove sono ponderate per la portabilità con il costo delle prestazioni. Il mio obiettivo non è la portabilità, il mio obiettivo è la prestazione.

Il motivo per cui questo interprete è assolutamente necessario è perché i blocchi smalltalk non vengono interpretati allo stesso modo:

A := B subclass: [
    def a:x [^ x*x]
    clmet b [...]
    def c [...]
    def d [...]
]

[ 2 < x ] whileTrue: [...]

(i isNeat) ifTrue: [...] ifFalse: [...]

List fromBlock: [
    "carrots"
    "apples"
    "oranges" toUpper
]

Ho bisogno del vero vantaggio derivante dalle routine interpretative, ovvero dalla scelta del contesto in cui leggere il programma. Naturalmente, un buon compilatore dovrebbe compilare la maggior parte delle volte casi ovvi come: 'ifTrue: ifFalse' o 'whileTrue:' o l'esempio di elenco. La necessità di un interprete non scompare semplicemente perché potresti sempre affrontare un caso in cui non puoi essere sicuro che il blocco ottenga il trattamento che ti aspetti.

È stato utile?

Soluzione

Vedo che c'è un po 'di confusione sulla portabilità qui, quindi mi sento in dovere di chiarire alcune cose. Queste sono le mie modeste opinioni, quindi, naturalmente, sei libero di opporti.

Presumo che tu abbia incontrato http://www.complang.tuwien.ac .at / forth / threading / se pensi di scrivere seriamente una VM, quindi non mi soffermerò sulle tecniche descritte.

Già menzionato, il targeting di una VM ha alcuni vantaggi come la riduzione delle dimensioni del codice, la ridotta complessità del compilatore (spesso si traduce in una compilazione più veloce), la portabilità (notare che il punto di una VM è la portabilità del linguaggio , quindi non importa se la VM stessa non è portatile).

Considerando la natura dinamica del tuo esempio, la tua VM assomiglierà ad un compilatore JIT più di altri più popolari. Quindi, anche se S.Lott ha mancato il punto in questo caso, la sua menzione di Forth è molto sul posto. Se dovessi progettare una VM per un linguaggio molto dinamico, separerei l'interpretazione in due fasi;

  1. Una fase di produzione che consulta un flusso AST su richiesta e lo trasforma in una forma più significativa (ad esempio, prendendo un blocco, decidendo se deve essere eseguito immediatamente o archiviato in un luogo per l'esecuzione successiva) eventualmente introducendo nuovi tipi di token. In sostanza, recuperi informazioni sensibili al contesto che potrebbero essere perse durante l'analisi qui.

  2. Una fase consumer che recupera il flusso generato da 1 e lo esegue alla cieca come qualsiasi altro computer. Se lo fai come Forth, puoi semplicemente spingere un flusso memorizzato ed essere fatto con esso invece di saltare il puntatore delle istruzioni in giro.

Come dici tu, solo imitare il modo in cui il dannato processore funziona in un altro modo non compie alcun dinamismo (o qualsiasi altra caratteristica degna di nota, come la sicurezza) di cui hai bisogno. Altrimenti, dovresti scrivere un compilatore.

Ovviamente, puoi aggiungere ottimizzazioni arbitrariamente complesse nella fase 1.

Altri suggerimenti

Se vuoi qualcosa di veramente veloce, prova a utilizzare LLVM . Può generare codice nativo per la maggior parte dei processori da una descrizione del programma di alto livello. Puoi andare con il tuo linguaggio di assemblaggio o generare la struttura llvm saltando la fase di assemblaggio, a seconda di ciò che ritieni più conveniente.

Non sono sicuro che sia il migliore per il tuo problema, ma è sicuramente quello che userei se farei un'esecuzione critica del codice che non può essere compilata con il resto del programma.

Il punto di un interprete è portabilità, il più delle volte. L'approccio più veloce che mi viene in mente è quello di generare direttamente il codice x86 in memoria, proprio come fanno i compilatori JIT, ma poi, ovviamente, non hai più un interprete. Hai un compilatore.

Tuttavia, non sono sicuro che scrivere l'interprete nell'assemblatore ti darà le migliori prestazioni (a meno che tu non sia un guru dell'assemblatore e il tuo progetto abbia una portata molto limitata). L'uso di un linguaggio di livello superiore può aiutarti a concentrarti su algoritmi migliori per, diciamo, la ricerca dei simboli e le strategie di allocazione dei registri.

puoi velocizzare la tua routine di invio con un'istruzione non codificata impostata su:

mov eax, [esi]
add esi, 4
add eax, pOpcodeTable
jmp eax

che dovrebbe avere un overhead < 4 cicli per ogni spedizione su & Gt della cpu; Pentium 4.

Inoltre, per motivi di prestazioni, è meglio incrementare ESI (IP) in ogni routine primitiva perché le probabilità sono alte che l'incremento possa essere accoppiato con altre istruzioni:

mov eax, [esi]
add eax, pOpcodeTable
jmp eax

~ 1-2 cilindri in testa.

Devo chiedere, perché creare una macchina virtuale con un focus sulle prestazioni? Perché non scrivere semplicemente il codice x86 direttamente? Niente può essere più veloce.

Se desideri una lingua molto interpretata rapidamente, consulta Forth . Il loro design è molto ordinato e molto facile da copiare.

Se non ti piace JIT e il tuo obiettivo non è la portabilità. Penso che potresti interessarti al progetto NativeClient di Google. Fanno analista statico, sandboxing e altri. Consentono all'host di eseguire le istruzioni RAW x86.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top