SED-P.NET

Machine virtuelle, Pascal-S et P-Code

vendredi 2 février 2007, par Bech ()


Une machine virtuelle, pourquoi ?

Pourquoi compiler un programme pour une machine qui n’existe pas, et donc interpreter ce code sur la machine cible ?

- Parce qu’il est alors plus facile de rendre un programme portable. Il suffit d’adapter le code de l’interpéteur (la machine virtuelle) qui est généralement écrit en language de haut niveau, et zou ! vos programmes vont également tourner sur la nouvelle cible. De plus la machine virtuelle sera facilement adaptable aux spécificitées du matériel et extensible en fonction des besoins du programmeur.

- Parce qu’il est plus facile de traduire ce pseudo-code en vrai langage machine que de refaire entièrement la compilation en code machine pour la machine cible. Dans le cas de la JVM, on peu réaliser un compilateur pascal pour la JVM et dans ce cas notre code sera fonctionnel sur toutes les platformes disposant de la JVM.

- Parce que l’on peut permettre d’ettendre ainsi une application en incorporant une VM, sans pour autant ajouter une faille de sécurité au programme. Les scripts tournant sur la machine virtuelle ne peuvent pas faire plus de chose que ce que la machine permet. En gros si la VM ne permet pas d’ouvrir ou modifier des fichiers, le code tournant sur la VM ne pourra modifier le contenu d’un disque !

Une machine à pile ?

Non, je ne parle pas des piles alcalines...

A quoi peut donc ressembler une machine virtuelle ? voici le peu que je comprend sur le sujet :

Pensont au niveau machine : on va parler de pile donc oublions nos multiples pointeurs AX, ECX, Y, R1, SP ... etc (enfin non, SP on le garde). La VM a pile se compose :

- d’un espace mémoire pour le code (un gros tableau d’octets),
- d’un espace mémoire pour les données (encore un gros tableau d’octets),
- Une pile de données (*).

(*) Pour le cas de la pile, on peut utiliser directement l’espace mémoire de données ou une liste chainé. Mais la question se pose coté VM, pas pour le programme.

Ceci est une vue depuis le programme qui tourne sur la VM. bien sûr il y a plus d’informations pour assurer le bon fonctionnement de tout cela (position courante dans la pile, par exemple, ou un pointeur sur la prochaine instruction de code à éxécuter), mais cela n’importe pas pour les programme qui tourne dessus.

En gros nous avons une pile et pas d’autres registres pour traiter les données. On utilise alors le sommet de la pile comme accumulateur. ex : Pour faire une addition, on empile les deux valeurs, puis l’instruction ’add’ laisse le résultat sur le sommet de la pile.

Avec le code suivant :


i := 1+2;

Notre compilateur sort alors le pseudo code suivant :


 Push 1
 Push 2
 Addi
 Push 1024 ; Adresse de i dans la mémoire, par exemple.
 Store

On retrouve donc dans i le résultat de l’addition.

Le PCode

Voici le code de l’interpreteur du PCode. Je ne met pas la partie compilation qui se trouve dans l’archive disponible en bas de page. Tout ceci vient du maitre N. Wirth dans : Algorithms + Data Structures = Programs.

  1.  
  2. procedure interpret;
  3. var
  4.   // PC est la position de l'instruction dans le code
  5.   // SP est la position courante du sommet de la pile
  6.   pc, sp, j, k, n: integer;
  7.   i: instr;   // Instruction en cours de traitement
  8.   c: char;
  9.   h: boolean; // Quitter le programme
  10.  
  11. begin
  12.   pc := 0;
  13.   h := false;
  14.   repeat i := code[pc]; pc := pc + 1;
  15.     case i.op of
  16.       add: begin m[sp + 1] := m[sp + 1] + m[sp]; sp := sp + 1 end;
  17.       neg: m[sp] := -m[sp];
  18.       mul: begin m[sp + 1] := m[sp + 1] * m[sp]; sp := sp + 1 end;
  19.       divd: begin m[sp + 1] := m[sp + 1] div m[sp]; sp := sp + 1 end;
  20.       remd: begin m[sp + 1] := m[sp + 1] mod m[sp]; sp := sp + 1 end;
  21.       div2: m[sp] := m[sp] div 2;
  22.       rem2: m[sp] := m[sp] mod 2;
  23.       eqli: begin m[sp + 1] := ord(m[sp + 1] = m[sp]); sp := sp + 1 end;
  24.       neqi: begin m[sp + 1] := ord(m[sp + 1] <> m[sp]); sp := sp + 1 end;
  25.       lssi: begin m[sp + 1] := ord(m[sp + 1] < m[sp]); sp := sp + 1 end;
  26.       leqi: begin m[sp + 1] := ord(m[sp + 1] <= m[sp]); sp := sp + 1 end;
  27.       gtri: begin m[sp + 1] := ord(m[sp + 1] > m[sp]); sp := sp + 1 end;
  28.       geqi: begin m[sp + 1] := ord(m[sp + 1] >= m[sp]); sp := sp + 1 end;
  29.       dupl: begin sp := sp - 1; m[sp] := m[sp + 1] end;
  30.       swap: begin k := m[sp]; m[sp] := m[sp + 1]; m[sp + 1] := k end;
  31.       andb: begin if m[sp] = 0 then m[sp + 1] := 0; sp := sp + 1 end;
  32.       orb: begin if m[sp] = 1 then m[sp + 1] := 1; sp := sp + 1 end;
  33.       load: m[sp] := m[m[sp]];
  34.       stor: begin m[m[sp]] := m[sp + 1]; sp := sp + 2 end;
  35.       hhalt: h := true;
  36.           (* Entrée/sortie *)
  37.       wri: begin write(m[sp + 1]: m[sp]); sp := sp + 2 end;
  38.       wrc: begin write(chr(m[sp])); sp := sp + 1 end;
  39.       wrl: writeln;
  40.       rdi: begin read(m[m[sp]]); sp := sp + 1 end;
  41.       rdc: begin read(c); m[m[sp]] := ord(c); sp := sp + 1 end;
  42.       rdl: readln;
  43.       eol: begin sp := sp - 1; m[sp] := ord(eoln(input)) end;
  44.           (* Entrée/sortie *)
  45.       ldc: begin sp := sp - 1; m[sp] := i.a end;
  46.       ldla: begin sp := sp - 1; m[sp] := sp + 1 + i.a end;
  47.       ldl: begin sp := sp - 1; m[sp] := m[sp + 1 + i.a] end;
  48.       ldg: begin sp := sp - 1; m[sp] := m[i.a] end;
  49.       stl: begin m[sp + i.a] := m[sp]; sp := sp + 1 end;
  50.       stg: begin m[i.a] := m[sp]; sp := sp + 1 end;
  51.       move: begin k := m[sp]; j := m[sp + 1]; sp := sp + 2; n := i.a;
  52.           repeat n := n - 1; m[k + n] := m[j + n]until n = 0
  53.         end;
  54.       copy: begin j := m[sp]; n := i.a; sp := sp - n + 1;
  55.           repeat n := n - 1; m[sp + n] := m[j + n]until n = 0
  56.         end;
  57.       addc: m[sp] := m[sp] + i.a;
  58.       mulc: m[sp] := m[sp] * i.a;
  59.       jump: pc := i.a;
  60.       jumpz: begin if m[sp] = 0 then pc := i.a; sp := sp + 1 end;
  61.       call: begin sp := sp - 1; m[sp] := pc; pc := i.a end;
  62.       adjs: sp := sp + i.a;
  63.       sets: sp := i.a;
  64.       exit: begin pc := m[sp]; sp := sp + i.a end;
  65.     end
  66.   until h
  67. end;
  68.  

En gros voici le code que vous devez adapter à la machine cible pour rendre votre programme portable. Bien sûr cette VM ne permet pas grand chose sur la machine hôte.

Les entrée/Sortie sont du type stdin/stdout uniquement. Mais vous pouvez rajouter vos propres OPCodes et ainsi rendre d’autres fonctions possibles, comme c’est déjà le cas avec write/read/writeln...

Références

Description du P-Code sur Wikipedia.

Les OPCodes de la JVM.

Les OPCodes du CIL-dotnet (format word)

Vous trouverez également en pièces jointes de cet article deux fichiers Zip contenant Pascal-S et PCode. Ce sont deux exemples de compilateur et interpréteur Pascal.

Documents joints

  • Pascal-S (Zip - 49.4 ko)
    Contient le compilateur + interpreteur, un programme de test ainsi que l’article de N. Wirth
  • PCode (Zip - 8.4 ko)
    Contient le compilateur + interpreteur + un programme de test

Répondre à cet article

Creative Commons License
Cette création est mise à disposition sous un contrat Creative Commons .

Articles de cette rubrique


Dernières brèves


A visiter