362 lines
12 KiB
Markdown
362 lines
12 KiB
Markdown
Cours de Florent.
|
|
|
|
# Calculer en théorie et en pratique III/III
|
|
|
|
Dans cette séquence de cours, nous tentons de répondre à la question suivante.
|
|
|
|
C'est quoi un ordinateur?
|
|
|
|
Cette question s'apparente à celle que ce sont posés philosophes et mathématiciens de Leibnitz à Turing en passant par Hilbert.
|
|
|
|
Qu'est-ce qu'une question à laquelle on peut répondre de manière mécanique par un calcul?
|
|
|
|
Nous avons vu une approximation de calcul théorique basé sur le calcul booléen et des portes logiques qui sont implémentable avec des composants electroniques, permettant de réaliser divers opérations comme l'addition bit à bit (schéma de Vernam en cryptographie), la majorité ou encore l'addition avec retenue.
|
|
|
|
Nous avons poursuivi avec un modèle théorique, qui est celui de l'**automate fini**, un modèle assez restreint de machine : l'adjectif fini s'applique à la mémoire de la machine qui est finie.
|
|
Nous avons entrevu le modèle plus général avec une mémoire arbitrairement grande **la machine de Turing** et évoqué la thèse de Church-Turing qui stipule que tout modèle de calcul raisonnable est équivalent à ce modèle de la machine de Turing.
|
|
|
|
Toutefois, le modèle de la machine de Turing reste assez éloigné d'une machine réelle et nous allons maintenant présenter rapidement un modèle simplifié de machine basé sur le modèle de Von Neumann, puis pratiquer l'assembleur avec la Machine Virtuelle à Pile (MVàP).
|
|
|
|
|
|
## Programme vs données
|
|
|
|
Dans le modèle de Turing, le programme est externe et guide la machine qui travaille sur le contenu du ruban qu'on peut voir comme la donnée.
|
|
Dès 1936, Turing montre qu'en fait on peut construire une machine **universelle** qui lit sur son ruban le programme et les données sur lesquelles il faut lancer le calcul de ce programme.
|
|
|
|
C'est cette idée de "programme qu'on va charger" qu'on retrouve dans le modèle de Von Neumann.
|
|
|
|
|
|
## Le modèle de Von Neumann
|
|
|
|
* L'unité de Mémoire contient des *données* et *des insructions*
|
|
* Le processeur se compose d'une *unité de contrôle* et d'une *unité arithmétique et logique*.
|
|
* La communication avec l'extérieur se fait par des dispositifs d'entrée-sortie.
|
|
|
|
Pour plus de détails, voir [wikipedia](https://en.wikipedia.org/wiki/Von_Neumann_architecture)
|
|
|
|
## Zoom sur *l'unité arithmétique et logique*
|
|
|
|
Essentiellement un circuit au sens de ce qu'on a évoqué lors du dernier cours.
|
|
En pratique les entrées/sorties sont découpées en 2 dimensions : une partie **contrôle** et une partie **données**.
|
|
|
|
* [Image](https://commons.wikimedia.org/wiki/File:ALU_block.gif)
|
|
* [wikipedia](https://en.wikipedia.org/wiki/Arithmetic_logic_unit)
|
|
* [Symboles circuit](https://en.wikipedia.org/wiki/Electronic_symbol)
|
|
|
|
|
|
## Zoom sur la mémoire
|
|
|
|
La mémoire peut être vue comme une pyramide.
|
|
|
|
* En haut très peu de place, mais de la mémoire très rapide, ce sont des *registres*.
|
|
* Au milieu, de la mémoire assez rapide, c'est la *mémoire vive* (la RAM) qui n'est pas persistante et quand on étend la machine, le contenu de la mémoire n'est pas conservé.
|
|
* En bas, de la mémoire plutôt lente, mais persistante, typiquement le *disque dur*.
|
|
|
|
En pratique il existe des mécanismes de *cache*, en particulier entre les registres et la RAM.
|
|
|
|
Il y a d'autres ingrédients qui permettent de limiter l'emprunte en mémoire d'un programme (adresses virtuelles, paging) voir de suspendre une exécution et de recopier le programme en train de calculer dans le disque (swapping).
|
|
|
|
* [Image](images/4-hierarchy.jpg)
|
|
* [Jeu du paging](https://en.wikisource.org/wiki/Paging)
|
|
* [Cache vs paging](https://stackoverflow.com/questions/4429025/caches-vs-paging)
|
|
|
|
## la Machine Virtuelle à Pile (MVàP).
|
|
|
|
Normalement dans une machine il faudrait expliquer comment les calculs sont poussés vers les registres et l'ALU.
|
|
Nous allons travailler avec un modèle simplifié qui cache cet aspect et dans laquelle la mémoire est une pile.
|
|
|
|
Par ailleurs la machine possèdes deux registres.
|
|
* PC (pour Program counter) qui contient le numéro de la ligne d'instruction en cours
|
|
* FC (pour frame pointer) qui est un mécanisme permettant de faire des appels de fonctions mais qu'on n'utilisera pas car c'est un peu plus technique à utiliser.
|
|
|
|
La machine accepte plusieurs instructions.
|
|
```
|
|
PUSHI n # pousse l'entier n sur la pile et passe à l'instruction suivante
|
|
POP # décale le sommet de la pile d'un et passe à l'instruction suivante
|
|
|
|
ADD # ajoute les 2 entiers au sommet de la pile, décale le sommet de la pile de 2
|
|
# écrit le résultat en haut de la pile et passe à l'instruction suivante
|
|
SUB # même chose pour la soustraction
|
|
MUL # même chose pour la multiplication
|
|
DIV # même chose pour la division
|
|
|
|
READ # lecture d'un entier qui est poussé en haut de la pile
|
|
WRITE # écriture de l'entier en haut de la pile
|
|
|
|
HALT # fin du programme
|
|
|
|
LABEL x # définition d'une ancre vers laquelle on peut sauter
|
|
JUMP x # Saut vers le label (prochaine instruction à la ligne du label)
|
|
JUMPF x # Instruction suivante si le haut de la pile est différent de 0
|
|
# Instruction x sinon.
|
|
|
|
INF # Mange deux entiers.
|
|
# Remplace par l'entier 1 si le premier inférieur au second et par 0 sinon.
|
|
# Passe à l'instruction suivante
|
|
INFEQ # idem plus petit ou égal
|
|
SUP # idem plus grand
|
|
SUPEQ # idem plus grand ou égal
|
|
EQUAL # idem égal
|
|
NEQ # idem différent
|
|
|
|
# Autres instructions qu'on verra plus tard.
|
|
PUSHG a # empile en haut de la pile l'entier stocké à l'adresse a de la pile
|
|
STOREG a # dépile et stocke à l'adresse a de la pile l'entier en haut de la pile
|
|
|
|
# Autres instructions qu'on ne verra probablement pas plus tard.
|
|
CALL x # Appel du code stocké au label x
|
|
RETURN # Retour dans le code appellant.
|
|
PUSHL a # analogue de PUSHG pour des variables locales (utilise FP)
|
|
STOREL a # analogue de STOREG
|
|
```
|
|
|
|
L'ordre des opérations est naturel, si on voit la pile de côté, bas à gauche, haut à droite. Une opération qui mange deux arguments prendra l'avant dernier argument comme premier argument et le dernier comme second argument.
|
|
|
|
### Un premier exemple
|
|
|
|
```
|
|
PUSHI 11
|
|
PUSHI 6
|
|
PUSHI 15
|
|
MUL
|
|
SUB
|
|
PUSHI 5
|
|
ADD
|
|
PUSHI 12
|
|
ADD
|
|
PUSHI 9
|
|
PUSHI 4
|
|
MUL
|
|
PUSHI 7
|
|
MUL
|
|
ADD
|
|
WRITE
|
|
POP
|
|
HALT
|
|
```
|
|
|
|
Une fois assemblé, le code devient :
|
|
```
|
|
Adr | Instruction
|
|
-----+---------------
|
|
0 | PUSHI 11
|
|
2 | PUSHI 6
|
|
4 | PUSHI 15
|
|
6 | MUL
|
|
7 | SUB
|
|
8 | PUSHI 5
|
|
10 | ADD
|
|
11 | PUSHI 12
|
|
13 | ADD
|
|
14 | PUSHI 9
|
|
16 | PUSHI 4
|
|
18 | MUL
|
|
19 | PUSHI 7
|
|
21 | MUL
|
|
22 | ADD
|
|
23 | WRITE
|
|
24 | POP
|
|
25 | HALT
|
|
```
|
|
La MVàP, en mode debug, écrit à chaque pas :
|
|
|
|
* la valeur du compteur de programme (pc) ;
|
|
* l'instruction à cette adresse ;
|
|
* la valeur du « frame pointer » (toujours 0, quand il n'y a pas d'appel de fonction) ;
|
|
* le contenu de la pile ;
|
|
* la hauteur de la pile.
|
|
|
|
Le début de l'exécution donne la trace suivante :
|
|
|
|
```
|
|
pc | | fp pile
|
|
====================================================
|
|
0 | PUSHI 11 | 0 [ ] 0 # On empile 11
|
|
2 | PUSHI 6 | 0 [ 11 ] 1 # On empile 6
|
|
4 | PUSHI 15 | 0 [ 11 6 ] 2 # On empile 15
|
|
6 | MUL | 0 [ 11 6 15 ] 3 # On dépile et multiplie 6*15, on empile le résultat
|
|
7 | SUB | 0 [ 11 90 ] 2 # On dépile et soustrait 90 à 11, on empile le résultat.
|
|
8 | PUSHI 5 | 0 [ -79 ] 1 # On empile 5.
|
|
etc
|
|
```
|
|
### Exercice
|
|
|
|
Continuez l'exécution.
|
|
|
|
### Exercice
|
|
|
|
Vérifier votre calcul manuel en lançant la machine.
|
|
|
|
Il faut avoir java installé sur votre machine ou utiliser proxmox.
|
|
Il faut utiliser les deux fichiers jar qui sont dans le répertoire MVaP
|
|
Le fichier ```MVaP/ALIRE``` contient la marche à suivre.
|
|
|
|
### Variables globales
|
|
On peut réserver de l'espace en bas de la pile et associer à une variable sa position pour mémoriser des valeurs.
|
|
|
|
```
|
|
PUSHI 1 # Il faut initialement pousser une valeur ( par exemple 1)
|
|
... # Du code qui génère des choses dans la pile et une valeur en haut de la pile, par exemple 42
|
|
STOREG 0 # stocke 42 à l'adresse 0 de la pile (le 1 est remplacé par 42)
|
|
... # Du code qui fait des choses et qui tout à coup a besoin de connaître la valeur stockée
|
|
PUSHG 0 # empile 42 en haut de la pile.
|
|
```
|
|
|
|
### Exemple
|
|
|
|
Imaginons qu'on souhaite faire du code qui :
|
|
* déclare une variable i
|
|
* lui affecte la valeur 6
|
|
* plus tard souhaite l'incrémenter.
|
|
|
|
```
|
|
PUSHI 1 # i habite à l'adresse 0
|
|
...
|
|
PUSHI 6
|
|
STOREG 0
|
|
...
|
|
PUSHI 1
|
|
PUSHG 0
|
|
ADD
|
|
STOREG 0
|
|
```
|
|
|
|
### Boucles
|
|
|
|
Imaginons qu'on souhaite faire une boucle tant que
|
|
```
|
|
Tant que test :
|
|
quelque chose
|
|
La suite
|
|
```
|
|
Il faut utiliser des labels pour délimiter
|
|
* la partie test suivie de la décision de rentrer dans la boucle ou de sortir
|
|
* la partie interne à la boucle (quelque chose)
|
|
* la partie qui suit la boucle (La suite).
|
|
|
|
```
|
|
LABEL 0
|
|
# le test qui va laisser une valeur 0 ou 1 en haut de la pile
|
|
JUMPF 1 # saut conditionnel vers le label 1 si le test est Faux sinon continue
|
|
# quelque chose
|
|
JUMP 0
|
|
LABEL 1
|
|
#La suite
|
|
```
|
|
|
|
### Exercice
|
|
|
|
Produire du code MVàP qui correspond au code python suivant.
|
|
```python
|
|
j = 1
|
|
i = 0
|
|
i = j
|
|
while i < 10:
|
|
i += 1
|
|
print(i)
|
|
```
|
|
Testez votre code avec la MVàP.
|
|
|
|
|
|
### Exercice
|
|
Produire du code MVàP qui s'inspire du code suivant et simule la lecture et le stockage d'un tableau avant de vider la pile.
|
|
La version ci-dessous ne vide pas la pile.
|
|
```
|
|
# Boucle While
|
|
LABEL 19
|
|
# le test qui va laisser une valeur 0 ou 1 en haut de la pile
|
|
# PUSHI 0 # pour faux, donc je saute après
|
|
# PUSHI 1 # pour vrai, donc je ne saute pas.
|
|
READ # on demande à l'utilisateur
|
|
JUMPF 18 # saut conditionnel vers le label 1 si le test est Faux sinon continue
|
|
# quelque chose
|
|
READ
|
|
# Fin du quelque chose
|
|
JUMP 19
|
|
LABEL 18
|
|
#La suite
|
|
PUSHI 666
|
|
WRITE
|
|
POP
|
|
# Fin de la suite.
|
|
HALT
|
|
```
|
|
|
|
### Exercice
|
|
Produire du code MVàP qui correspond au code python suivant.
|
|
|
|
```python
|
|
# integer inputs (input returns a string that needs casting as an int)
|
|
m = int(input())
|
|
n = int(input())
|
|
if (m < n) :
|
|
m = n
|
|
else :
|
|
n = m
|
|
print m
|
|
print n
|
|
```
|
|
|
|
### Réutiliser du code.
|
|
|
|
Si on a du code qu'on veut réutiliser plusieurs fois,
|
|
* on peut l'isoler entre un label et un jump
|
|
* on peut sauter vers le label
|
|
* malheureusement on ne peut pas sauter à la fin vers plusieurs endroits différents
|
|
* on pourrait faire des tests successifs avec des JUMP et des JUMPF pour sauter au bon endroit
|
|
* par contre on ne peut pas réutiliser le code un nombre arbitraire de fois.
|
|
|
|
Solution : on sauve dans un registre l'endroit dans lequel on doit retourner
|
|
Le plus simple consiste à utiliser le haut de la pile.
|
|
|
|
Par contre il faut dans ce cas une instruction pour la MVàP qui permette d'écrire en haut de la pile la position actuelle du pc (c'est une partie du rôle de CALL) et il faut une instruction qui soit l'analogue de HALT pour un bloc et permette de retourner le pc à la valeur antérieure à l'appel (c'est une partie du rôle de RETURN).
|
|
|
|
Avec cette idée on peut faire un appel à une fonction qui ne prend pas de paramètre et ne retourne pas de valeur.
|
|
|
|
On peut communiquer avec le reste du programme via une variable globale.
|
|
|
|
### Exemple
|
|
On peut ainsi par exemple faire une fonction qui mutliplie par 2 une variable globale.
|
|
On appelle 3 fois la fonction et on affiche le résultat à chaque fois.
|
|
|
|
```
|
|
PUSHI 1 # variable globale i habite à l'adresse 0
|
|
JUMP 1
|
|
LABEL 0 # Notre Fonction
|
|
PUSHG 0
|
|
PUSHI 2
|
|
MUL
|
|
STOREG 0
|
|
RETURN # Fin de Notre Fonction
|
|
...
|
|
LABEL 1 # Main
|
|
CALL 0 # appel 1
|
|
PUSHG 0
|
|
WRITE
|
|
CALL 0 # appel 2
|
|
PUSHG 0
|
|
WRITE
|
|
CALL 0 # appel3
|
|
PUSHG 0
|
|
WRITE
|
|
```
|
|
|
|
## Autres Exercices
|
|
Il faut pour chaque exercice produire et tester du code MVàP qui réaliser le calcul ou le programme demandé.
|
|
|
|
### Exercice
|
|
Lire un entier n et calculez n*4+5*6-2
|
|
|
|
### Exercice
|
|
Lire un entier n et calculez 2*4+n*6-2
|
|
|
|
### Exercice
|
|
Même chose que ci-dessus mais avec un programme optimisé (qui donne le même résultat mais avec un minimum d'instructions).
|
|
|
|
### Exercice
|
|
Lire un entier n. Si n est nul le programme écrit 20 sinon 10.
|
|
|
|
### Exercice
|
|
Et parce qu'il faut bien une question un peu plus difficile.
|
|
Lire un entier n. Le programme écrit 2^n.
|