Les rédacteurs de compilateurs doivent-ils réellement «comprendre» le code machine? [fermé]

10

Cela pourrait être une sorte de question étrange.

Un gars qui écrit un compilateur C ++ (ou n'importe quel langage non VM): doit-il être capable de lire / écrire du langage machine brut? Comment ça marche?

EDIT: Je fais spécifiquement référence aux compilateurs qui compilent en code machine, pas en un autre langage de programmation.

Aviv Cohn
la source
1
Non. Vous n'avez même pas besoin de le savoir, vous pouvez simplement copier aveuglément et sans réfléchir
SK-logic
1
Coffescript se compile en javascript.
Kartik
@Kartik Le compilateur CoffeeScript qui compile en Javascript inclut-il également un compilateur Javascript qui compile en tout ce qui est compilé en Javascript? Ou compile-t-il uniquement en code source Javascript et rien de plus?
Aviv Cohn
Le compilateur coffeescript convertit simplement le cofeescript en javascript. Javascript n'est pas compilé, il est géré par le navigateur. Je voulais dire que vous pouvez écrire un compilateur qui compile une langue dans une autre, vous n'avez pas besoin de connaître le langage machine pour cela. Un autre exemple est le compilateur «SPL» qui compile les lectures de Shakespeare en C ++. shakespearelang.sourceforge.net
Kartik

Réponses:

15

Non pas du tout. Il est parfaitement possible (et souvent même préférable) que votre compilateur émette du code d'assembly à la place. L'assembleur se charge ensuite de créer le code machine réel.

Soit dit en passant, votre distinction entre l'implémentation non VM et l'implémentation VM n'est pas utile.

  • Pour commencer, l'utilisation d'une machine virtuelle ou d'une précompilation pour coder une machine n'est que différentes façons d'implémenter un langage; dans la plupart des cas, un langage peut être implémenté en utilisant l'une ou l'autre stratégie. J'ai dû utiliser une fois un interpréteur C ++ .

  • De plus, de nombreuses machines virtuelles comme la JVM ont toutes deux un code machine binaire et un assembleur, tout comme une architecture ordinaire.

Le LLVM (qui est utilisé par les compilateurs Clang) mérite une mention spéciale ici: il définit une machine virtuelle pour laquelle les instructions peuvent être représentées soit par du code d'octets, un assemblage textuel ou une structure de données qui le rend très facile à émettre à partir d'un compilateur. Donc, même si cela serait utile pour le débogage (et pour comprendre ce que vous faites), vous n'auriez même pas besoin de connaître le langage d'assemblage, uniquement sur l'API LLVM.

La bonne chose à propos du LLVM est que sa VM n'est qu'une abstraction et que le code d'octet n'est généralement pas interprété, mais JITted de manière transparente à la place. Il est donc tout à fait possible d'écrire un langage qui est effectivement compilé, sans jamais avoir à connaître le jeu d'instructions de votre CPU.

amon
la source
Et une autre belle propriété de LLVM est qu'il n'est pas nécessaire de comprendre en profondeur l'ISA cible pour implémenter un backend efficace. C'est assez déclaratif, donc on peut presque copier-coller une spécification ISA dans des fichiers .td sans même essayer de la comprendre.
SK-logic
Merci de répondre. Question: Je comprends de votre réponse et des réponses des autres que le compilateur-rédacteur n'a pas à comprendre le code machine, il peut utiliser un autre outil qui fait le code de conversion en machine lui-même. Cependant, le gars qui a écrit cet outil ne doivent comprendre le langage de la machine, non? Le gars qui a écrit le logiciel qui effectue la conversion réelle d'une langue en code machine doit réellement comprendre le langage machine, n'est-ce pas?
Aviv Cohn
5
@Prog oui. Si vous créez une couche d'abstraction, il vous suffit de comprendre la couche en dessous de vous. Bien qu'il soit utile d'avoir une compréhension de base de toutes les couches, ce n'est pas vraiment nécessaire. Vous n'avez pas besoin de comprendre la physique quantique pour utiliser un transistor. Vous n'avez pas besoin de comprendre la conception des puces pour utiliser un processeur. Vous n'avez pas besoin de connaître le code machine pour écrire l'assembly. Vous n'avez pas besoin de connaître le jeu d'instructions de la plate-forme lorsque vous utilisez une machine virtuelle, etc. Mais quelqu'un a dû construire ces niveaux d'abstraction en dessous du vôtre.
amon
1
@ SK-logic Pas vrai (du moins si vous voulez du bon code) d'après ce que j'ai entendu. Les personnes qui ont implémenté le backend Aarch64 pour llvm ont eu pas mal de défis (délocalisations pour une personne, modèles de magasin de charge horribles, ..). Et c'est ignorer le plus grand éléphant de la pièce: le modèle de mémoire de l'ISA et le modèle de mémoire du langage qui vous intéresse. Vous pouvez travailler sur un compilateur, mais vous ne pouvez pas travailler sur un backend sans comprendre l'architecture ..
Voo
1
J'ajouterais qu'il n'y a vraiment aucune différence substantielle entre l'assemblage et le code machine, conceptuellement. Vous n'aurez pas vraiment beaucoup d'avantages, une fois que vous savez comment utiliser une instruction particulière, quel est le code d'opération de cette instruction. Si vous savez comment utiliser MOV, cela n'a pas vraiment d'importance si vous ne savez pas que c'est l'instruction 27, qui je pense est similaire à ce que décrit @ SK-logic.
whatsisname
9

Non. Le point clé de votre question est que la compilation est un terme extrêmement large. La compilation peut se produire de n'importe quelle langue vers n'importe quelle langue. Et le code assembleur / machine n'est que l'un des nombreux langages pour la cible de compilation. Par exemple, les langages Java et .NET comme C #, F # et VB.NET se compilent tous en une sorte de code intermédiaire au lieu d'un code spécifique à la machine. Peu importe qu'il s'exécute ensuite sur VM, le langage est toujours compilé. Il existe également une option pour compiler dans un autre langage, comme C. C est en fait une cible de compilation très populaire et de nombreux outils le font. Et enfin, vous pouvez utiliser un outil ou une bibliothèque pour faire le dur travail de production de code machine pour vous. il y a par exemple LLVM qui peut réduire l'effort nécessaire pour créer un compilateur autonome.

De plus, votre modification n'a aucun sens. C'est comme demander "Est-ce que chaque ingénieur doit comprendre comment fonctionne le moteur? Et je pose des questions sur les ingénieurs travaillant sur les moteurs." Si vous travaillez sur un programme ou une bibliothèque qui émet un code machine, vous devez le comprendre. Le fait est que vous n'avez pas à faire une telle chose lors de l'écriture du compilateur. Beaucoup de gens l'ont fait avant vous, vous devez donc avoir de sérieuses raisons de le refaire.

Euphorique
la source
Et la personne qui écrit l'outil ou la bibliothèque qui effectue la conversion réelle en langage machine, doit comprendre complètement le langage machine, non?
Aviv Cohn
3
@Prog Avez-vous besoin de bien comprendre un langage de programmation pour le programmer? Non, mais vous allez peut-être écrire du code sous-optimal et vous ne pouvez pas faire certaines choses que d'autres pourraient faire. Avez-vous besoin de comprendre complètement le langage machine si vous écrivez un compilateur qui se traduit par cela. Non, mais votre compilateur sera sous-optimal et incapable de faire certaines choses.
Sumurai8
@ Sumurai8: bien que dans une certaine mesure, vous puissiez "comprendre" le langage machine par morceaux afin d'écrire un émetteur de code machine qui comprend mieux le tout que vous. Par exemple, si vous écrivez un bon framework, vous pouvez configurer la définition de chaque opcode ainsi que ses coûts et considérations de pipelining, puis votre framework peut écrire du code machine optimisé même si vous n'avez aucune expertise pour optimiser cette machine particulière. Être capable de programmer ce code machine avec compétence vous-même ne fait probablement pas de mal.
Steve Jessop
@SteveJessop Si vous comprenez chaque opcode au point que vous pouvez apprendre à une machine comment enchaîner cet opcode avec d'autres opcodes pour exprimer un concept de niveau supérieur, vous comprenez parfaitement le langage machine. Vous êtes alors juste trop paresseux pour trouver la solution optimale pour chaque problème là-bas ;-)
Sumurai8
@ Sumurai8: hmm, mais au moins en principe je pourrais "comprendre" brièvement chaque opcode pendant les cinq minutes qu'il me faut pour le configurer, puis l'avoir oublié au moment où je "comprend" l'opcode après la prochaine. Ce n'est probablement pas ce que l'interrogateur entend par "être capable de lire / écrire un langage machine brut". Bien sûr, je suppose qu'un bon cadre est ici, suffisamment configurable pour définir et utiliser toutes les informations utiles sur chaque opcode du jeu d'instructions. LLVM vise un peu cela, mais selon "Voo" (dans un commentaire ci-dessous) ne l'a pas atteint.
Steve Jessop
3

Classiquement, un compilateur comprend trois parties: l'analyse lexicale, l'analyse et la génération de code. L'analyse lexicale décompose le texte du programme en mots-clés, noms et valeurs de langue. L'analyse montre comment les jetons qui proviennent de l'analyse lexicale sont combinés dans des déclarations syntaxiquement correctes pour la langue. La génération de code prend les structures de données produites par l'analyseur et les traduit en code machine ou en une autre représentation. De nos jours, l'analyse lexicale et l'analyse syntaxique peuvent être combinées en une seule étape.

Il est clair que la personne qui écrit le générateur de code doit comprendre le code machine cible à un niveau très profond, y compris les jeux d'instructions, les pipelines de processeur et le comportement du cache. Sinon, les programmes produits par le compilateur seraient lents et inefficaces. Ils peuvent très bien être capables de lire et d'écrire du code machine comme représenté par des nombres octaux ou hexadécimaux, mais ils écrivent généralement des fonctions pour générer le code machine, se référant en interne aux tableaux d'instructions machine. Théoriquement, les gens qui écrivent le lexer et l'analyseur peuvent ne rien savoir de la génération du code machine. En fait, certains compilateurs modernes vous permettent de brancher vos propres routines de génération de code qui pourraient émettre du code machine pour certains CPU dont les rédacteurs de lexers et d'analyseurs n'ont jamais entendu parler.

Cependant, dans la pratique, les rédacteurs de compilateurs à chaque étape en savent beaucoup sur les différentes architectures de processeur, ce qui les aide à concevoir les structures de données dont l'étape de génération de code aura besoin.

Charles E. Grant
la source
2

Il y a longtemps, j'ai écrit un compilateur qui convertissait entre deux scripts shell différents. Cela n'allait pas du tout près du code machine.

Une écriture de compilateur doit comprendre leur sortie , mais ce n'est souvent pas du code machine.

La plupart des programmeurs n'écriront jamais un compilateur qui génère du code machine ou du code assembleur, mais des compilateurs personnalisés peuvent être très utiles sur de nombreux projets pour produire d'autres sorties.

YACC est un compilateur de ce type qui ne génère pas de code machine….

Ian
la source
0

Vous n'avez pas besoin de commencer par une connaissance détaillée de la sémantique de vos langages d'entrée et de sortie, mais vous feriez mieux de terminer par une connaissance extrêmement détaillée des deux, sinon votre compilateur sera anormalement bogué. Donc, si votre entrée est C ++ et votre sortie est un langage machine spécifique, vous devrez éventuellement connaître la sémantique des deux.

Voici quelques-unes des subtilités de la compilation de C ++ en code machine: (juste au-dessus de ma tête, je suis sûr qu'il y en a plus que j'oublie.)

  1. Quelle sera la taille int? Le choix «correct» ici est un art, basé à la fois sur la taille naturelle du pointeur de la machine, les performances de l'ALU pour différentes tailles d'opérations arithmétiques et les choix faits par les compilateurs existants pour la machine. La machine a-t-elle même une arithmétique 64 bits? Si ce n'est pas le cas, l'ajout d'entiers 32 bits doit se traduire par une instruction tandis que l'ajout d'entiers 64 bits doit se traduire par un appel de fonction pour effectuer l'ajout 64 bits. La machine a-t-elle des opérations d'ajout 8 bits et 16 bits ou devez-vous simuler celles avec des opérations 32 bits et un masquage (par exemple le DEC Alpha 21064)?

  2. Quelle est la convention d'appel utilisée par les autres compilateurs, bibliothèques et langages de la machine? Les paramètres sont-ils poussés de droite à gauche ou de gauche à droite sur la pile? Certains paramètres vont-ils dans les registres tandis que d'autres vont sur la pile? Les entiers et flottants sont-ils dans différents espaces de registre? Les paramètres alloués au registre doivent-ils être traités spécialement lors des appels varargs? Quels registres sont sauvegardés par l'appelant et lesquels sont sauvegardés par l'appelé? Pouvez-vous effectuer des optimisations d'appel feuille?

  3. Que fait chacune des instructions de changement de vitesse de la machine? Si vous demandez de décaler un entier 64 bits de 65 bits, quel est le résultat? (Sur de nombreuses machines, le résultat est le même que le décalage d'un bit, sur d'autres le résultat est "0".)

  4. Quelle est la sémantique de cohérence de la mémoire de la machine? C ++ 11 a une sémantique de mémoire très bien définie qui impose des restrictions sur certaines optimisations dans certains cas, mais permet des optimisations dans d'autres cas. Si vous compilez un langage qui n'a pas de sémantique mémoire bien définie (comme toutes les versions de C / C ++ avant C ++ 11, et de nombreux autres langages impératifs), vous devrez inventer la sémantique mémoire au fur et à mesure, et généralement vous voudrez inventer la sémantique de la mémoire qui correspond le mieux à la sémantique de votre machine.

Logique errante
la source