Interprétation et compilation – Bytecode

icône de pdf
Signaler

Les ordinateurs contemporains sont pour la plupart basés sur des architectures de Von Neumann. Quel que soit le langage utilisé pour écrire des programmes, ils doivent être traduits en langage machine pour être compris par l’ordinateur ciblé : ils sont soit interprétés, soit compilés.

I. Langage machine et bytecode

La plupart des langages modernes sont traduits en bytecode qui est un langage d’assemblage de haut niveau, et il existe une correspondance biunivoque entre ce langage d’assemblage et le langage machine de bas niveau compris par un système cible donné, c’est-à-dire des suites de 0 et de 1. Le bytecode est utilisé comme représentation intermédiaire avant la transformation en code machine pour l’architecture cible (x86, ARM, MIPS, etc.).

Mot-clé

Une correspondance biunivoque fait correspondre un élément d’un ensemble à un autre élément d’un autre ensemble.

Par exemple, si nous écrivons en Python ce morceau de code :

c949d053-6452-4679-9a85-8240832dc6c8

Nous pouvons demander à la librairie dis de le désassembler, c’est-à-dire de trouver son équivalent en langage d’assemblage (ici du bytecode Python) en invoquant dis.dis(succ). Il s’agit essentiellement d’un appel à l’opération d’addition, en langage d’assemblage :

5478ecb2-4610-41eb-9a95-56be7a0de25d

II. Interprétation et compilation

Un langage interprété est un langage dans lequel les instructions ne sont pas directement exécutées par la machine cible, mais lues et exécutées une à une par un programme externe (qui est normalement écrit dans le langage de la machine cible). Une opération « + » est ainsi reconnue par l’interpréteur lors de l’exécution, qui appelle alors sa propre fonction add(a, b) avec les arguments appropriés, qui déclenche ensuite l’exécution de l’instruction « ADD » en langage machine.

Un langage compilé est un langage où le programme, une fois compilé, est exprimé dans les instructions d’un langage d’assemblage de plus bas niveau adapté à un système spécifique. Le processus de compilation peut comporter plusieurs étapes dont une étape de liaison avec des librairies externes avant de produire un exécutable pour la plateforme donnée.

Les langages interprétés et compilés peuvent coder les mêmes algorithmes, comme réaliser l’addition de 2 nombres en Bash ou en langage C (ce dernier langage est compilé).

Les procédés de compilation ou d’interprétation présentent des avantages et des inconvénients lors de leur mise en œuvre et ne forment pas deux catégories étanches. Dans tous les cas, le procédé commence par effectuer une analyse lexicale pour repérer les entités fondamentales d’un programme, aussi appelées tokens, comme les noms de variables, de fonctions, les mots-clefs du langage, ou encore les expressions. Puis il vérifie si le programme respecte bien une certaine structure tout en l’exécutant s’il s’agit d’un langage interprété ou en le traduisant en langage cible ou en bytecode.

L’outil fondamental lors de cette analyse lexicale, comme lors des étapes ultérieures du processus de vérification syntaxique et de traduction, est la grammaire. Voici un exemple simple de grammaire qui génère des expressions comme (3+5)*((7-3)+9) en partant de expr :

d26f2976-afc4-41cc-9895-eb871558a1dd

Pour les langages à base de bytecode, comme Java ou Python, les instructions peuvent être compilées à la volée (just-in-time ou JIT) lors de l’exécution pour obtenir le code natif approprié à la machine hôte. Les dossiers __pycache__ que l’on peut trouver dans un répertoire de travail contiennent ce bytcode en Python (version ≥ 3).

Production de bytecode et exécution d’un programme Python

7c8cb50f-bcd8-4103-9b64-cd091b0e076c