L'objectif de ce TP est d'étendre le langage A6000 avec des mécanismes de gestion des exceptions. On introduira deux opérations de base :
- throw déclenche une exception.
- try (b₁) catch (b₂) rattrape les exceptions propagées par b₁ en exécutant b₂.
1. Description
1.1. Extension du langage source
1.1.1. Lexique
Le langage contient les nouveaux mots-clés throw, try et catch.
1.1.2. Grammaire
La grammaire des instructions est étendue de deux nouvelles productions pour le déclenchement et le rattrapage d'exceptions.
[instruction] ← ...
| throw
| try ( [instructions] ) catch ( [instructions] )
1.1.3. Sémantique
L'exécution d'une instruction throw déclenche une exception, qui interrompt le cours normal de l'exécution : soit l'exécution s'arrête immédiatement, soit l'exécution reprend au niveau d'un code de rattrapage introduit par catch.
L'exécution d'une instruction try (b₁) catch (b₂) commence par l'exécution de b₁.
- Si l'exécution de b₁ réussit, on passe à la suite
- Si l'exécution de b₁ déclenche une exception, exécution
de b₂, puis :
- si succès, on passe à la suite
- si exception, l'exception est propagée
Remarque : dans cette version de base, on ne distingue pas entre différentes exceptions.
1.2. Extension du compilateur
1.2.1. Conventions
On suivra une stratégie dite de stack cutting, dans laquelle le tas contient un ensemble de gestionnaires d'exceptions correspondant chacun à une construction try/catch en cours d'exécution, les différents gestionnaires étant chaînés entre eux, du plus récent vers le plus ancien.
Un gestionnaire est un bloc dans le tas avec au moins deux champs utiles, contenant l'adresse du code du bloc catch associé et l'adresse du gestionnaire précédent.
--|-----------|--
| |
| ... | <- infos additionnelles éventuelles
| |
|-----------|
| Catch | <- pointeur de code de rattrapage
|-----------|
| Prec | <- bloc du gestionnaire précédent
|-----------|
| En-tête |
--|-----------|--
On réserve l'un des registres (par exemple gp) pour enregistrer
l'adresse du gestionnaire courant (c'est-à-dire le plus récent).
Détail du protocole :
- Lorsqu'une exception est déclenchée, on exécute le code pointé par le gestionnaire courant, ou on interrompt le programme s'il n'y a pas de gestionnaire courant.
- À l'entrée dans un bloc try, on alloue un nouveau gestionnaire qui pointe vers le gestionnaire courant et vers le code du bloc catch associé, et ce nouveau gestionnaire devient le gestionnaire courant.
- À la sortie d'un bloc try ou catch, le gestionnaire pointé par le gestionnaire courant devient le nouveau gestionnaire courant. Dans ce cas, l'ancien bloc courant peut être désalloué explicitement si vous utilisez un gestionnaire de mémoire explicite.
1.2.2. Syntaxes abstraites
Dans la syntaxe abstraite source SourceAst, on étend le type instruction avec deux constructeurs Throw et Try of block * block.
À partir de l'étape GotoAst, le constructeur Try est remplacé par deux constructeurs NewHandler of label et RmHandler qui correspondent respectivement à la création d'un nouveau gestionnaire d'exception (lors de l'entrée dans le bloc try) et à l'abandon du dernier gestionnaire (lors de la sortie avec succès d'un bloc try ou d'un bloc catch).
2. Travail à effectuer
Vous devez étendre l'ensemble du compilateur pour intégrer ces nouveaux mécanismes.
3. Extensions
3.1. Déclaration et analyse des exceptions
Modifier la syntaxe de déclaration des fonctions pour permettre d'annoter une fonction susceptible de propager une exception, que ce soit car elle déclenche elle-même une exception qu'elle ne rattrape pas, ou car elle fait appel sans précautions à une autre fonction susceptible de propager une exception, à la manière de ce qui est fait en Java.
Ajouter vers le début du processus de compilation (par exemple sur la syntaxe SourceAst) une analyse vérifiant que chaque fonction susceptible de propager une exception non rattrapée est bien annotée comme telle.
Remarque : avec des fonctions récursives, cette analyse pourra demander de calculer un point fixe, ainsi qu'il était fait dans le TP 3 avec les équations de flot de données.
3.2. Exceptions distinguées
Enrichir la syntaxe pour permettre la distinction entre plusieurs exceptions, par exemple caractérisées par des nombres entiers.
Une instruction throw doit dans ce cas préciser l'exception qui est déclenchée, et un bloc try peut être associé à plusieurs blocs catch, chacun associé à une exception. Lorsqu'une exception est déclenchée, on regarde d'abord si elle est rattrapée par l'un des catch du gestionnaire courant, et à défaut on propage au gestionnaire précédent.
3.3. Exceptions paramétrées
Enrichier la syntaxe pour permettre d'associer une valeur à une exception, qui sera transmise au bloc catch.
Dans ce cas, la valeur peut être transmise via une case du bloc du gestionnaire d'exception courant, et le code du bloc catch peut faire appel à une variable spéciale liée à l'adresse de cette case.