Nous avons choisi de partir de travaux antérieurs de l'équipe
sur la gestion de mémoire des langages de programmation
logique [Bekkers et al. 84, Ridoux 86, Ridoux 87, Bekkers et al. 86, Bekkers et al. 92].
Ces travaux ont abouti à la définition d'une mémoire virtuelle
adaptée à l'exécution des programmes logiques,
et munie d'une gestion de mémoire efficace :
la mémoire
* [Ridoux 91].
La mémoire
fait comme seules hypothèses sur l'exécution des
programmes logiques que ceux-ci créent dynamiquement des structures contenant
des variables,
qu'ils peuvent substituer des structures quelconques aux variables,
et qu'ils peuvent effectuer des retours en arrière
pour parcourir des arbres de recherche en profondeur.
Les substitutions peuvent donc être défaites.
La mémoire
peut donc être définie comme un organe capable de stocker
une pile de termes modifiables réversiblement.
Nous avions montré par le passé comment plusieurs extensions de Prolog
pouvaient être codées dans :
l'unification de termes rationnels et le
«gel» (freeze) de la
résolution [Le Huitouze 88, Le Huitouze 90b],
l'unification de termes «à traits»
(feature terms ou
-termes) [Ridoux 89]
et l'unification d'expressions
booléennes [Ridoux et Tonneau 90].
Nous étions donc confiants dans la possibilité de coder les
-termes et leur réduction,
l'augmentation dynamique du programme par l'implication,
et l'unification d'ordre supérieur.
Nous étions aussi assurés de bénéficier d'une gestion de
mémoire efficace quelle que soit la complexité des structures de données.
L'implémentation d'un langage de programmation doit bien sûr simuler fidèlement
la logique du langage,
mais aussi décider de son intégration dans un environnement de programmation,
même réduit à un système d'exploitation.
C'est pourquoi nous parlons de «système-langage» pour décrire une
implémentation.
Nous avons décrit dans la section
«Langage et machine»* --
comment ces choix induisent la perception qu'un utilisateur a d'un langage
de programmation.
Utiliser est notre premier choix,
mais ne répond pas à toutes les questions.
Utiliser pour implémenter un langage de programmation logique
nécessite de la spécialiser et de l'étendre.
D'une part,
offre un ensemble de constructions qui sont orthogonales,
mais dont toutes les combinaisons ne sont pas utiles pour toutes les
applications.
On spécialise
en encapsulant dans une mémoire de plus haut niveau les
combinaisons utilisées par une implémentation d'un langage de programmation.
Dans le cas de
Prolog,
il faut se demander comment représenter les
continuations*,
les
-termes*,
les variables logiques*,
les constantes universelles*,
etc [Brisset et Ridoux 92b, Brisset et Ridoux 92a].
L'idée générale de la réponse est que
selon le point de vue où
contient une pile de termes modifiables réversiblement,
la pile représente la continuation d´échec*,
et que tout le reste,
y compris les continuations de succès*
et de programme*,
est représenté par des termes.
Les rédex,
les variables logiques
et la continuation de programme
sont représentés par des termes modifiables.
Les représentations étant définies,
il faut ensuite réaliser les opérations de
Prolog
(création et parcours de termes, de clauses ou de points de choix,
unification,
-réduction, etc)
à l'aide de celle de
(création, parcours, substitution, empilement, dépilement, etc).
D'autre part,
n'offre aucun service pour décrire l'enchaînement des opérations
(le contrôle),
ni pour allouer la mémoire qu'elle gère.
Ce dernier point garantit son indépendance d'avec les systèmes d'exploitation.
Il laisse choisir au concepteur du système-langage une
«politique de gestion de mémoire» qui décrit d'où vient la mémoire
gérée par
(allocation statique à la création du processus d'exécution,
ou dynamique, avec ou sans limite,
etc.)
et que faire de la mémoire rendue inutile par
(la rendre au système, la garder pour plus tard, dans quelle proportion ? Etc.).
Concernant le contrôle,
il faut se rappeler que
est une mémoire abstraite,
alors que la
et la plupart des dispositifs décrits pour implémenter des
langages de programmation en les compilant
sont des machines abstraites.
Il faut donc adjoindre à
une
«unité centrale» sous la forme de l'interpréteur d'un langage
de programmation
(dans le cas présent, le langage C)
dont on utilisera essentiellement les structures de contrôle.
Dans le cas de la réalisation d'un système
Prolog ou
Prolog,
l'idée est de traduire chaque prédicat en une procédure dont le seul
effet est d'altérer les continuations
(
*)
et de revenir à une forme dégénérée de boucle d'interprétation
[Brisset et Ridoux 92b, Brisset et Ridoux 92a].
En particulier,
cette procédure n'est pas récursive,
même si le prédicat l'est.
Il s'agit là bien évidemment d'un schéma général,
et il est très important de reconnaître des situations où il n'est pas
nécessaire de revenir à la boucle d'interprétation.
Figure 1: Constitution des applications
La figure 1 illustre l'architecture du
système .
Le quart sud-ouest, «Noyau», symbolise la constitution du noyau :
principalement spécialisation et extension de
.
Cette partie est produite par les concepteurs du système et préexiste
aux autres parties.
Le quart nord-ouest, «Bibliothèques»,
symbolise la constitution des bibliothèques du système.
Ces bibliothèques préexistent aux deux parties restantes
et l'une d'entre elle,
la bibliothèque standard,
préexiste à toutes les autres.
Cette partie est aussi produite par les concepteurs du système.
Le quart nord-est, «Modules d'application»,
symbolise la constitution d'une application par un utilisateur quelconque.
Elle peut être faite de plusieurs modules,
compilés séparément.
Enfin,
le quart sud-est, «Exploitation»,
symbolise l'exécution d'une application.
C'est à ce moment là seulement que les ressources gérées par
sont allouées et utilisées.
Entre-temps,
les programmes issus des différentes parties ont été compilés et reliés
entre eux.
Les compilations sont figurées
par les flèches qui traversent les lignes discontinues.
Elles peuvent être faites dans un ordre quelconque
pourvu qu'il respecte les dépendances entre modules et bibliothèques.
En général,
les compilations sont faites aussitôt que le source correspondant est
constitué,
mais ce n'est pas obligatoire.
L'édition de lien est figurée par les lignes qui convergent vers la case
marquée «Exécutable».
Elle utilise pour la plus grande part l'éditeur de lien du système hôte.
Les quarts nord-ouest et nord-est figurent tous les deux
une activité de programmation en Prolog,
mais le programmeur n'est pas le même pour les deux parties.
Les bibliothèques sont produites par les concepteurs du système :
elles sont leurs premières applications.
Les modules d'application viennent en second
et sont programmés par les utilisateurs.
Les flèches qui pointent vers les cases marquées «relogeable» symbolisent
la compilation des fichiers sources.
Celles venant du quart sud-ouest impliquent qu'un compilateur pour le langage
d'implantation
de la mémoire Prolog est disponible.
Il s'agit de C et il est facile de trouver un compilateur C sur la
plupart des installations.
Les flèches venant des quarts nord-ouest et nord-est impliquent qu'un
compilateur
Prolog est disponible.
En fait,
le compilateur
Prolog de
est lui aussi une application écrite en
Prolog
et il est donc aussi constitué de la manière décrite par la
figure 1.
On voit poindre le problème de l'autocompilation
qui se résout partiellement en notant que le premier système
(premier dans le temps du développement)
n'était pas écrit en
Prolog.
Il reste cependant une difficulté qui dure tout le temps du développement :
les bibliothèques et le compilateur n'appartiennent pas
à la même version de système-langage.
Ils peuvent différer par la logique de leur langage d'implémentation,
en cas de changement de version avec modification de la spécification du langage,
mais ils peuvent aussi différer par les idiomes recommandés,
en cas de changement de version avec modification du schéma d'exécution.
Il est donc quasiment impossible de partager les modules qui dans les
bibliothèques et dans le compilateur implémentent les mêmes fonctionnalités.
Le choix du point de départ ne produit pas
automatiquement un schéma d'exécution efficace.
Nous n'allons pas détailler tous les points
qui concourent selon nous à l'efficacité de notre système.
Nous n'en retenons que deux qui sont des exemples des interactions
entre utilisation et implémentation.
Le premier est la matérialisation au niveau de
l'implémentation d'une idée qui est pertinente à tous les niveaux d'explication
de Prolog :
on ne calcule qu'avec des
combinateurs*.
Le second est le support d'une autre idée :
la
-équivalence* donne des rôles symétriques à la
quantification universelle dans les buts*
et à la
-abstraction*.