Question

Je vais implémenter une machine virtuelle en x86 et je me demande quel type de conception donnerait les meilleurs résultats. Sur quoi devrais-je me concentrer pour éliminer le jus? Je vais implémenter l’ensemble de la machine virtuelle dans l’assemblage x86.

Je n'ai pas beaucoup d'instructions et je peux choisir leur forme. Les instructions se projettent directement dans la syntaxe de smalltalk en blocs. Je donne les instructions auxquelles je pensais:

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

Le type de machine virtuelle auquel je pensais:

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.

Ne commencez pas à vous demander pourquoi j'ai besoin d'une autre implémentation de machine virtuelle. Les routines d'interprétation ne sont pas des actions que vous prenez simplement lorsque vous en avez besoin. La plupart des machines virtuelles que vous proposez ailleurs sont orientées vers la portabilité avec le coût des performances. Mon objectif n'est pas la portabilité, mon objectif est la performance.

La raison pour laquelle cet interprète est nécessaire est que les blocs smalltalk ne sont pas interprétés de la même manière:

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
]

J'ai besoin du bénéfice réel des routines d'interprétation, c'est-à-dire du choix du contexte dans lequel lire le programme. Bien sûr, un bon compilateur devrait juste compiler la plupart du temps les cas évidents tels que: 'ifTrue: ifFalse' ou 'whileTrue:', ou l'exemple de liste. La nécessité d'un interprète ne disparaît pas simplement parce que vous pouvez toujours vous retrouver dans une situation où vous ne pouvez pas être sûr que le bloc obtiendra le traitement que vous attendez.

Était-ce utile?

La solution

Je vois ici une certaine confusion quant à la portabilité, alors je me sens obligé de clarifier certains points. Ce sont mes humbles opinions, vous êtes donc libre de vous y opposer.

Je suppose que vous avez découvert http://www.complang.tuwien.ac .at / forward / threading / si vous envisagez d'écrire une machine virtuelle sérieusement, je ne m'attarderai donc pas sur les techniques décrites.

Déjà mentionné, le ciblage d'une machine virtuelle présente des avantages tels que la taille du code réduite, la complexité réduite du compilateur (se traduit souvent par une compilation plus rapide), la portabilité (notez que le point d'une machine virtuelle est la portabilité du langage Ainsi, peu importe si la machine virtuelle elle-même n’est pas portable).

Compte tenu de la nature dynamique de votre exemple, votre machine virtuelle ressemblera davantage à un compilateur JIT qu’aux autres plus populaires. Donc, bien que S.Lott ait manqué le point dans cette affaire, sa mention de Forth est très sur les lieux. Si je devais concevoir une machine virtuelle pour un langage très dynamique, je séparerais l’interprétation en deux étapes;

  1. Une étape de production qui consulte un flux AST sur demande et le transforme en une forme plus significative (par exemple, prendre un bloc, décider s'il doit être exécuté immédiatement ou stocké quelque part pour une exécution ultérieure), éventuellement en introduisant nouveaux types de jetons. En gros, vous récupérez des informations sensibles au contexte qui peuvent être perdues lors de l'analyse syntaxique.

  2. Une étape consommateur récupérant le flux généré à partir de 1 et l'exécutant à l'aveuglette comme n'importe quelle autre machine. Si vous le donnez comme vous le souhaitez, vous pouvez simplement pousser un flux stocké au lieu de sauter le pointeur d’instructions.

Comme vous le dites, imiter simplement le fonctionnement de ce maudit processeur ne donne aucun dynamisme (ou toute autre fonctionnalité digne de ce nom, comme la sécurité) dont vous avez besoin. Sinon, vous écririez un compilateur.

Bien sûr, vous pouvez ajouter des optimisations arbitrairement complexes à la première étape.

Autres conseils

Si vous voulez quelque chose de vraiment rapide, essayez d’utiliser LLVM . Il peut générer du code natif pour la plupart des processeurs à partir d'une description de programme de haut niveau. Vous pouvez soit utiliser votre propre langage d'assemblage, soit générer la structure llvm en ignorant la phase d'assemblage, en fonction de ce qui vous semble le plus pratique.

Je ne suis pas sûr que ce soit le meilleur pour votre problème, mais c'est ce que j'utiliserais si je réalisais des exécutions critiques du code qui ne peuvent pas être compilées avec le reste du programme.

L'intérêt d'un interprète est la portabilité, la plupart du temps. L’approche la plus rapide à laquelle je peux penser est de générer directement du code x86 en mémoire, comme le font les compilateurs JIT, mais bien entendu, vous n’avez plus d’interprète. Vous avez un compilateur.

Cependant, je ne suis pas sûr que l'écriture de l'interprète dans l'assembleur vous donnera les meilleures performances (à moins que vous ne soyez un gourou de l'assembleur et que votre projet ait une portée très limitée). L'utilisation d'un langage de niveau supérieur peut vous aider à vous concentrer sur de meilleurs algorithmes, par exemple pour la recherche de symboles et les stratégies d'allocation de registres.

vous pouvez accélérer votre routine d'envoi avec une instruction non codée définie sur:

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

qui devrait avoir une surcharge < 4 cycles pour chaque envoi sur & Gt du cpu; Pentium 4.

De plus, pour des raisons de performances, il est préférable d’incrémenter ESI (IP) dans chaque routine primitive car il est très probable que l’incrémentation puisse être associée à d’autres instructions:

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

~ 1-2 cylts de frais généraux.

Je dois demander pourquoi créer une machine virtuelle centrée sur les performances. Pourquoi ne pas simplement écrire du code x86 directement? Rien ne peut être plus rapide.

Si vous voulez un langage interprété très très rapide, consultez Forth . Leur design est très soigné et très facile à copier.

Si vous n'aimez pas JIT et que votre objectif n'est pas la portabilité. Je pense que le projet NativeClient de Google peut vous intéresser. Ils font analyste statique, bac à sable et autres. Ils permettent à l'hôte d'exécuter les instructions RAW x86.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top