# Écriture de systèmes d’exploitation à l’aide d’un langage de type pl 360

Jacques Boulenger

► To cite this version:

Jacques Boulenger. Écriture de systèmes d’exploitation à l’aide d’un langage de type pl 360. [Rapport de recherche] IRIA-RR-048, IRIA. 1974, pp.26. hal-04715974

HAL Id: hal-04715974

https://inria.hal.science/hal-04715974v1

Submitted on 1 Oct 2024

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_1.png" /></figure>

Distributed under a Creative Commons Attribution 4.0 International License
Inv. 02004 RR48

RIA
laboria

laboratoire de recherche
en informatique
et automatique

Écriture
de systèmes d'exploitation
à l'aide d'un langage
de type PL 360

z6p

J.Boulenger

Institut de Recherche
d'Informatique
et d'Automatique

Domaine de Voluceau
Rocquencourt
78150 - Le Chesnay
Tél. 954.90.20

Rapport de Recherche n° 48

Janvier 1974
ECRITURE DE SYSTEMES D'EXPLOITATION
A L'AIDE D'UN LANGAGE DE TYPE PL 360

J. Boulenger

Résumé.

Cet article aborde deux aspects de la construction des systèmes d'exploi-
tation : le langage d'écriture et la chaîne de production, en tirant la leçon
de plusieurs réalisations sur la machine CII 10070. On présente deux langages
d'écriture dérivant du PL 360, le LP 70 et le LP 10070 en insistant sur les
caractéristiques qui sont apparues importantes à l'usage. On introduit la
notion de chaîne de production et on décrit celle que l'on a utilisée. En
conclusion, on dégage quelques enseignements pratiques concernant la dépen-
dance vis à vis de la machine câblée, le découpage des grands systèmes
et l'importance des programmes de service.

Abstract.

This paper is based upon constructions of operating systems and deals
with the subjects of implementation languages and software production lines.
Two implementation languages were experimented, LP 70 and LP 10070, both
PL-360 like ; their main features with regard to the implementation problem
are presented. The software production line is also described. Finally
practical conclusions are drawn which concern the direct use of the
hardware facilities, the structuring of large programs and the importance
of auxiliary programming tools.
Nous nous proposons :

de mettre en évidence les points communs aux trois langages PL considérés (§ 1) ;

de préciser un certain nombre de caractéristiques par lesquels ils diffèrent (§ 2, 3, et 4) ;

de décrire l'utilisation qui a été faite du LP10070 comme langage d'écriture de système, d'en montrer les avantages et les inconvénients ; nous mettrons en évidence à cette occasion la notion de chaîne de production (§ 5) ;

enfin de tirer la leçon de cette utilisation, sur le plan du langage, mais surtout sur celui de la chaîne de production (§ 6).

# 1. PRESENTATION DES 3 LANGAGES PL CONSIDERES

## 1.1 Caractères communs conséquences de la propriété Q1

Pour la gestion des objets en mémoire, la propriété Q1 entraîne les conséquences suivantes :

a) le programmeur a accès à tous les registres généraux de la machine ;

b) il gère ces registres lui-même dans tout le programme, comme s'il écrivait en langage d'assemblage ;

c) les objets du langage sont imposés par la structure d'adressage câblée (octets, mots, etc...).

Il en résulte que le compilateur ne peut repousser aucun calcul à l'exécution du programme compilé (car il ne dispose pas des registres généraux pour cela), et en particulier aucun calcul d'adresse. Il n'y a donc pas d'autre structure de données que celle de rangée obtenue par l'in-dexation existant dans certaines instructions câblées : dans ce cas, le calcul d'adresse est fait par l'unité centrale dans ses registres internes. Pour la même raison, la transmission d'arguments à un sous-programme ne peut se faire que par des objets globaux, par exemple les registres généraux, dont l'adresse est connue à la fois du programme appelant et du sous-programme.

Bien évidemment, le programmeur peut obtenir n'importe quelle structure, si compliquée soit-elle, mais il est obligé de la décrire, et elle n'apparaît pas dans l'algorithme comme une structure nouvelle.

d) compte tenu de l'interprétation qui a été faite de Q1, la gestion de la mémoire par le compilateur est statique, et en fait visible du programmeur (existence de synonyme).
La propriété Q1 a également une conséquence sur le code exécutable :

* les instructions qui n'ont pas été formalisées (parce qu'elles ne manipulent pas d'objets du langage : citons par exemple *LM* et *STM*, qui manipulent une collection de registres généraux) sont disponibles sous forme de fonctions.

## 1.2 Caractères communs conséquences des propriétés Q2 et Q3.

Les langages considérés ont hérité de certaines caractéristiques de ALGOL W, à savoir :

* a) la syntaxe est à peu près indépendante du découpage du texte en lignes ou cartes.

* b) la portée des déclarations est limitée à l'intérieur de blocs.

* c) on peut composer une instruction à partir de plusieurs autres, à l'aide d'opérateurs comme *if*, *for*, *case*.

* d) le seul outil de découpage du programme est la notion de sous-programme (mais non récursif !) baptisé *procédure*.

## 1.3 Différences

Dans les paragraphes qui suivent, nous allons, respectivement pour le PL360 tel qu'il est défini dans [Wirth 68], le LP70 ([CII 72]) et le LP10070 ([Boulenger 71]) examiner les points suivants, sur lesquels ces langages diffèrent, et qui se sont révélés être importants :

* P1 : le choix des objets du langage ;

* P2 : la portée des étiquettes ;

* P3 : les opérateurs de répétition ;

* P4 : la notion de programme ;

* P5 : les possibilités de paramétrisation ;

* P6 : les fonctions ;

* P7 : l'accès au code généré par le compilateur ;

* P8 : l'écriture des commentaires ;

* P9 : les facilités de mise au point ;

* P10 : le compilateur ;

* P11 : les expériences d'écriture de systèmes d'exploitation.
## 2. CARACTERES PROPRES AU PL360

P1 : le PL360 traite les nombres représentés en convention flottante, les caractères, les entiers et les booléens. La distinction est mal faite entre d'une part la taille du support d'une information (octet, demi-mot, etc...) et d'autre part la nature de cette information (caractère EBCDIC, booléen) : cette dernière est seule considérée.

P2 : la portée d'une étiquette est rigoureusement limitée au bloc où elle apparaît. Les restrictions d'emploi de B, BAL et EXE qui empêcheraient de tourner cette règle ne sont pas mentionnées dans [Wirth 68].

P3 : en dehors de l'opérateur *for* on dispose de l'opérateur *while* qui s'utilise par :

`while <condition> do <instruction>`

De façon générale, le sens des opérateurs *if*, *case*, *for*, *while* est celui des tournures correspondantes du langage courant. Leur emploi de préférence aux étiquettes et aux branchements conditionnels du langage d'assemblage est conforme à la propriété Q3.

P4 : conformément aux principes de l'ALGOL, un programme est un bloc, il a donc un seul point d'entrée ; en outre il est compilé en une seule fois. Dans [Wirth 68], les programmes de traitement des interruptions sont même cités comme exemples de tels programmes à un point d'entrée.
Deux problèmes sont donc ignorés :

* connaître dans un programme des objets d'un autre programme,

* charger et exécuter plusieurs programmes même complètement indépendants (la collection des programmes de traitement d'interruption par exemple).

P5 : ignoré.

P6 : une fonction génère une instruction de la machine câblée.

P7 : ignoré.

P8 : comme en ALGOL 60.

P9 : ignoré.
P10 : [Wirth 68] illustre l'idée maintenant classique que lorsqu'on a un système d'une certaine ampleur à écrire, il est peu coûteux et payant de se fabriquer d'abord un langage d'écriture adapté. De la chaîne de production, le compilateur est seul considéré, et les qualités suivantes en sont mises en relief :

• rapidité ;

• écriture du compilateur dans le langage qu'il traduit, ce qui permet de vérifier et le langage et le compilateur ;

• "bonne structuration" qui permet des modifications du langage au fur et à mesure des besoins.

P11 : le seul exemple publié d'utilisation du PL360 pour écrire tout ou partie d'un système d'exploitation, est le traitement par N. Wirth d'un problème de synchronisation très simple ([Wirth 69]).

## CARACTERES PROPRES AU LP 70

Nous nous fondons sur la version utilisée par nous en 1969, 1970.

P1 : les objets sont à priori des double-mots, des mots, etc...
Certains ne peuvent en outre que contenir des nombres en convention flottante.
La machine câblée disposant de l'adressage indirect, il y a des pointeurs qui ne peuvent désigner que des objets d'une longueur donnée (double-mots,...)

P2 : les étiquettes doivent être déclarées, comme les objets. L'exemple suivant montre que cela conduit à violer la règle selon laquelle on n'entre dans un bloc que par le début.

EX : *begin*
*label* L ;
...
*IN* : *begin*
...
*L* :
...
*end* ;
...
*goto* L ;
...
*end*
Les seules occurrences de *L* dans le bloc étiqueté *EX* étant celles indiquées ci-dessus, l'ordre *goto L* indique un branchement vers l'ordre étiqueté *L* à l'intérieur du bloc étiqueté *IN*.

En outre, à l'intérieur d'un bloc, déclarations et ordres peuvent être mélangés, pourvu que la déclaration d'une entité (objet, étiquette, procédure) précède toujours son utilisation.

*P3* : le LP 70 fournit l'opérateur de répétition *loop* : répétition indéfinie d'un ordre. On peut sortir d'une telle répétition par un *goto*, ou un *exit*.

*P4* : l'unité de compilation est le *segment procédure*, qui est une *procédure*. Un programme est le résultat de l'édition des liens d'un nombre quelconque de ces segments, et d'un *segment principal*, qui est un bloc, et à qui on donnera le contrôle au moment de l'exécution. Tous les segments sont au même niveau et peuvent s'appeler les uns les autres. Les objets, dont on veut qu'ils soient accessibles depuis plusieurs segments sont regroupés dans un *globaldata* qu'on emploie comme un *commun* en FORTRAN : le texte du *globaldata* doit être compilé en même temps que chaque segment qui utilise tout ou partie des objets qu'il décrit. Un segment peut utiliser un nombre quelconque d'objets dans un nombre quelconque de *globaldata*.

*P5* : dans un *segment*, on peut donner un nom (identificateur) à une valeur numérique, et partout (dans ce *segment*) où on veut mentionner cette valeur, lui substituer ce nom. Ceci est par exemple utile pour déclarer des tableaux de même longueur.

Dans la version du compilateur dont nous disposions, cette possibilité n'était pas utilisable dans les *globaldata*.

*P6* : une fonction génère un ordre à la machine câblée. Toute fonction doit être déclarée dans le *segment* où elle est utilisée, avec la valeur numérique du code de l'instruction correspondante.

*P7* : l'impression du code généré par le compilateur est difficile à obtenir (il faut intercaler des cartes dans le texte du programme), et à déchiffrer.
P8 : un commentaire est toute suite de caractères compris entre un caractère spécial & et la fin de la ligne. Cela a l'avantage d'éliminer les erreurs (fréquentes) dues à l'oubli de la fin de commentaire.

P9 : à l'exécution, un dispositif de _trace_ permet d'imprimer par exemple :

* tous les passages par une étiquette

* tous les appels à une procédure

* toutes les modifications apportées à une variable par les instructions "formalisées" du langage : si une variable est modifiée à travers un pointeur ou par une fonction, la trace ne donnera rien.

La trace d'une entité peut être suspendue ou activée par des instructions spéciales.

P11 : le LP 70 a servi à écrire le système SAM, et est un des langages disponibles sous ce système. Dans cette version, un certain nombre d'entités tels les sémaphores et les processus ont été ajoutés ([Rossiensky 69]). Le LP 70 nous a servi à écrire le compilateur du langage LP 10070, et cette expérience nous a permis d'améliorer ce dernier langage.

### 4. CARACTERES PROPRES AU LP 10070

P1 : Les nombres en convention flottante ont été éliminés, en tant qu'objets du langage.

P2 : la gestion des étiquettes a été conservée telle qu'en PL360 : on n'entre dans un bloc que par le début.

P3 : les opérateurs de répétition sont le *while* et le *repeat*, utilisés sous la forme :

```
while <condition> do <instruction>
```
et
repeat <instruction> until <condition>

P4 : en LP 70, lorsqu'une variable appartient à un *globaldata*, et que sa déclaration doit être modifiée (par exemple on veut changer la longueur d'un tableau), cette modification doit être reportée à la main dans chaque version du *globaldata*. Ceci entraîne de nombreuses erreurs, et nous avons
préféré, pour le LP 10070, revenir à un mécanisme plus primitif, ou une
entité utilisée dans plusieurs modèles est d'une part déclarée en *def* et
décrite complètement dans un module d'autre part déclarée en *ref* avec
une description restreinte dans les autres modules. Un *module* (unité de
compilation) est donc un bloc principal étiqueté précédé de la déclaration
d'un nombre quelconque d'entités en *def* ou en *ref*. Un système est une collection de modules. Le contrôle est donné à l'exécution au bloc principal dont
on mentionne l'étiquette.

P5 : comme en LP 70, mais partout disponible.

P6 : il existe sous le nom de *command* l'équivalent de la directive *COM* de METASYMBOL. Une *command* permet d'obtenir jusqu'à 8 ordres consécutifs à la machine câblée (8 parce qu'on voulait éviter les trop longues portions de programmes écrites avec une seule directive *command*, mais ce nombre s'est révélé trop petit).

Contrairement à ce qui se passe en LP 70, il n'y a aucune vérification sémantique : le programmeur peut obtenir s'il en a envie un branchement à une variable.

L'existence de *command* est à priori contradictoire avec le but Q2, mais a été très utile. On peut conserver dans les tables du compilateur un certain nombre de *command*, dites alors standards, qui sont utilisables sans déclaration préalable. On a pu ainsi étendre le langage par des primitives comme P et V, sans avoir à modifier le compilateur.

P7 : le compilateur du LP 10070 fournit sur option une présentation lisible du code produit. Ceci s'est révélé utile tant pour les utilisateurs que pour les auteurs du compilateur : la mise au point de ce dernier en a été facilitée.

P8 : l'écriture des commentaires se fait comme en PL/1. L'oubli de la fin de commentaire par omission ou par erreur de frappe est fréquent.

P9 : il existe en LP 10070 un dispositif de trace analogue à celui du LP 70 et possédant les mêmes inconvénients et les mêmes avantages. En outre, une instruction *dump* dont l'exécution ne modifie aucun des objets (registres généraux et code condition compris), permet à un endroit donné d'un programme d'imprimer le contenu d'une variable, présenté en tenant compte de sa déclaration, (octet, rangée de mots, etc...) et accompagné du nom et de l'adresse de cette variable.
Ces deux dispositifs se sont révélés aussi utiles l'un que l'autre et non remplaçables l'un par l'autre :

Le compilateur peut fonctionner selon deux modes différents :

• en mode *debug*, les dispositifs de mise au point sont compilés ;

• en mode *normal*, ces dispositifs sont ignorés, et le programme est compilé comme s'ils étaient absents du texte. On peut ainsi conserver en permanence dans le texte du programme les dispositifs qui ont servi à le mettre au point, et qui pourront servir à nouveau, moyennant recompilation, en cas de nouvelle erreur rencontrée.

Les sous-programmes de mise au point et la partie du compilateur correspondant à la traduction des dispositifs de mise au point représentent une bonne moitié du compilateur, proportion très sous-estimée au départ.

*P10* : le compilateur est orienté par la syntaxe et utilise la même méthode d'analyse syntaxique descendante, sans retour en arrière, que le compilateur du LP 70.

Nous examinons *P11* dans le paragraphe qui suit. A cette occasion, nous reviendrons sur *P9* et *P10*.

## 5. UTILISATION DU LP 10070

Le LP 10070 est le langage dans lequel ont été écrits les programmes suivants :

• le système ESOPE ([Bétourné 70]) ;

• le système graphique METAVISU sous ESOPE ([Boullier 72]) ;

• un moniteur de simulation en temps réel sous ESOPE ;

• des programmes utilitaires : éditeurs de texte (ELP : Editeur de Lignes Primitif, EDIT : éditeur standard), éditeur de liens, LPDEBUG : aides à la mise au point ;

• des traducteurs : introducteur de grammaires, compilateur du langage d'interaction dans le système METAVISU.

Une vingtaine de personne ont conçu, écrit et mis au point tout ceci pendant trois ans. Nous exposerons ci-dessous les méthodes utilisées pour l'écriture et la mise au point, et dans le paragraphe suivant les leçons que nous en avons tirées.
Le LP 10070 a été utilisé pour écrire deux types de programmes :

• des programmes séquentiels, bénéficiant à l'exécution des services d'un système d'exploitation ; nous les appellerons programmes *esclaves* ;

• des programmes constituant eux-mêmes un système d'exploitation et devant fonctionner sur une machine pratiquement dépouillée de tout utilitaire programmé (fonctionnement dit en *stand alone*).

## 5.1 *Utilisation standard du compilateur*

Quel que soit le type d'un programme, son texte est fractionné en lambeaux compilables séparément, appelés *modules*.

L'obtention du code binaire d'un module est décomposée en 2 étapes :

• modifications à partir d'une version initiale du texte du module pour obtenir la version à jour ; comme la version initiale est rarement changée, les modifications successives sont cumulées ;

• compilation de cette version à jour.

Ce travail est accompli par la chaîne de production C (figure 1) fonctionnant sous le système d'exploitation BPM (Batch Processing Monitor) fourni par le constructeur.

L'éditeur ELP est indépendant de la nature du texte symbolique qu'il manipule ; ce texte peut être aussi bien un programme en LP 10070 qu'une grammaire en BNF, etc... Le fonctionnement du ELP se fait dans des conditions strictes (modifications ordonnées, abandon total du travail en cas d'erreur), ce qui s'est avéré être un avantage.

En revanche la nécessité de consulter à la fois le listing (fait par le compilateur) d'un module à jour et le listing (fait par ELP) du module initial pour les modifications est une source d'erreurs.

La vitesse de fonctionnement du compilateur est satisfaisante. Celle de l'éditeur ELP ne l'est pas du tout. Son remplacement par un éditeur nettement plus élaboré (EDIT) a montré que le sous-système de gestion des fichiers du BPM était à l'origine de cette lenteur.

Pour fixer les ordres de grandeur, nous avons composé deux travaux typiques consistant en la mise à jour et la compilation (avec listing du texte source et du code produit) d'une part d'un programme en LP 10070, de l'autre d'un programme en METASYMBOL (macro-assembleur du BPM). Ce dernier programme n'utilise en fait de macros que celles engendrant les ordres à la machine câblée et les appels au système : la comparaison est donc licite.

Le tableau 1 donne les éléments de cette comparaison.
<figure><img src="image_2.png" /><figcaption>Figure 1 : chaîne de production C, mise à jour et compilation d'un module.</figcaption></figure>

<table><caption>Tableau 1 : éléments d'évaluation de la chaîne de production C.</caption><thead><tr><th>Traducteurs utilisés</th><th>ELP + 10070</th><th>METASYMBOL</th></tr></thead><tbody><tr><td>Nombre de cartes de modification</td><td>550</td><td>350</td></tr><tr><td>Nombre de cartes de la version initiale</td><td>1300</td><td>950</td></tr><tr><td>Nombre de cartes de la version à jour</td><td>1650</td><td>1200</td></tr><tr><td>Temps d'exécution des traducteurs (en dehors du système BPM)</td><td>1/2 minute</td><td>2 1/2 minutes</td></tr><tr><td>Nombre de mots de données générés</td><td>3800</td><td>4000</td></tr><tr><td>Nombre d'ordres câblés générés</td><td>1200</td><td>200</td></tr><tr><td>Temps passé dans le système pour le compte des traducteurs</td><td>3 1/2 minutes</td><td>2 1/2 minutes</td></tr></tbody></table>
On remarquera :

que le macro-assemblage est beaucoup (5 fois) plus long que la
compilation ;

que le temps passé dans le système est considérable ; mais ce
système est ancien.

## 5.2 Écriture et mise au point.

Le processus d'écriture et de mise au point d'un programme esclave est
représenté (figure 2).

<figure><img src="image_3.png" /><figcaption>Figure 2 : chaîne de production E.</figcaption></figure>

Le lenteur excessive de l'éditeur de liens a la même origine que
celle de ELP.
Outre le listing du texte source du module, le compilateur fournit :

• sur option le code produit, mot par mot, en correspondance avec le texte source,

• le listing de la partie du texte source non compilée (le cas échéant),

• la taille du code exécutable et des données produits,

• le nombre des erreurs détectées, et leur emplacement dans le texte,

• l'explication en clair des erreurs apparues (l'expérience a en effet montré qu'un code permettant de retrouver cette explication dans une brochure est insuffisant, car les utilisateurs n'ont jamais la brochure sous la main au moment où ils en ont besoin).

Tous ces renseignements se sont révélés utiles.

Le compilateur détecte deux sortes d'erreurs :

• des erreurs dites syntaxiques détectées à l'analyse lexicale ou syntaxique, et

• des erreurs dites sémantiques détectées à l'analyse sémantique ou à la génération.

Dans le deuxième cas, l'explication est assez précise pour que la correction ne pose aucune problème, et les conséquences de l'erreur sont assez limitées.

Dans le premier cas, la seule indication qu'ait le programmeur est l'unité syntaxique sur laquelle le compilateur a buté. C'est souvent insuffisant.

Les erreurs syntaxiques sont parfois récupérées (le compilateur abandonnant une petite partie du texte puis reprenant la traduction). La traduction est également abandonnée lorsque le nombre de lignes erronées dépasse une limite (petite). En effet, lorsque quelqu'un a écrit un programme et qu'il le compile pour la première fois, il peut avoir 2 attitudes :

• une bonne : il commence par faire lister son programme pour le relire, et corriger en particulier les erreurs de perforation, les omissions, les interversions de carte. Ensuite seulement il fait compiler son programme.

• une mauvaise : dès que le programme est perforé, il est donné en pâture au compilateur.

L'expérience montre que la plus grande partie des erreurs syntaxiques sont éliminées par une relecture préalable. Pourtant l'existence de la limite du nombre de lignes erronées possible n'a pas contraint les programmeurs à adopter la bonne attitude quand ils ne l'avaient pas par eux-mêmes : ils
préfèrent passer leur programme autant de fois qu'il le faut et se faire indiquer toutes les erreurs par le compilateur.

Les routines de mise au point présentent 3 inconvénients :

a) ni leur code exécutable ni leurs données propres ne sont protégées !
Pour les données, il aurait fallu modifier dynamiquement leur protection, à chaque entrée ou sortie d'une routine. Le système BPM le permet mais nous y avons renoncé étant donné le ralentissement que cela aurait entraîné.

Comme les routines de mise au point sont par commodité rangées en bibliothèque sous la forme d'un fichier binaire absolu, le code exécutable doit avoir la même protection que les données : une seule protection est en effet possible dans un tel fichier. En pratique, les cas de dégradation du code ou des données des routines de mise au point ont été très rares.

b) il est impossible d'appeler ces routines par un *CAL* (il faudrait modifier le système BPM) c'est-à-dire sans détruire aucun des registres généraux de la machine. Par conséquent, la séquence d'appel à une routine de mise au point occupe au moins 3 mots, et l'implantation d'un programme change suivant qu'on l'a compilé ou non en mode *debug*. Il est arrivé une seule fois à ma connaissance qu'un programme fonctionne correctement alors que compilé en mode *debug* et présente des erreurs lorsque compilé en mode *normal*. L'explication est la suivante :

* le programme utilise un mot de mémoire dont l'adresse est obtenue par un calcul erroné, pour y ranger provisoirement une valeur.

En mode *debug* ce mot est dans une zone non protégée mais inutilisée par ailleurs.

En mode *normal* ce mot est dans une zone protégée en écriture, et le programme est avorté par le système.

c) l'exécution d'un programme compilé en mode *debug* est très ralentie par rapport à son exécution lorsqu'il est compilé en mode *normal* ; ce ralentissement a lieu dans le système (sortie sur l'imprimante).
## 5.3 Écriture et mise au point

Le processus d'écriture et de mise au point d'un tel système est représenté figure 3 : il s'agit de ce que l'on appelle communément la génération du système.

<figure><img src="image_4.png" /><figcaption>Figur. 3. Chaîne de production G.</figcaption></figure>
Les routines de mise au point mentionnées dans la chaîne E n'ont pas été récupérées. Trois outils de mise au point existent dans la chaîne G :

a) DEBUG baptisé *stand alone* et fourni par le constructeur. Il opère en mémoire physique. Il permet de faire des impressions ou des modifications en hexadécimal à la console de l'opérateur.

b) un programme d'impression du contenu des tables du système en clair, avec le nom de ces tables, etc... Ce programme doit être mis à jour à la main lorsqu'une modification est faite dans l'organisation de ces tables.

c) DEBUG ad hoc opérant sous le contrôle du système en cours de réalisation (ESOPE), permettant de faire des impressions ou des modifications en hexadécimal sur n'importe quel télétype d'un usager conversationnel. Il opère en mémoire virtuelle.

Lorsqu'une erreur est détectée dans les procédures du système qui sont en mémoire virtuelle, on peut utiliser le DEBUG ad hoc pour la cerner.

Lorsqu'une erreur apparaît dans la partie du système qui est en mémoire physique, on peut encore utiliser le DEBUG *stand alone* et en désespoir de cause l'impression des tables et celle (en hexadécimal) du contenu de la mémoire physique.

Pour la plupart des modules du systèmes ESOPE, les tests ont été menés de la façon suivante :

tests séquentiels sous BPM utilisant la chaîne de production E avec un environnement simulé si nécessaire ;

inclusion dans le système réel une fois que ces tests sont considérés comme suffisants. Cette façon de procéder vise à n'utiliser la chaîne G qu'à coup sûr.

En effet, la mise en oeuvre de cette chaîne est longue et pénible : manipulations de bandes magnétiques, chargement du système *stand alone* à partir d'un paquet de cartes perforées devant être remplacé très souvent, etc... De plus comme les modifications ponctuelles doivent être reportées à la main dans le texte des modules du système, leur nombre doit rester très limité.
Le problème de la génération du système a été sous-estimé au départ. Nous avons cru qu'il suffisait de récupérer les outils du constructeur. Or l'emploi de ceux-ci nous a posé des problèmes. Par exemple l'éditeur de liens *stand alone* n'est pas exactement compatible avec celui du BPM (il ne satisfait pas les références en avant qui sont calculées en fonction de références externes). Ce qui fait que dans certains cas, un programme correct après passage dans la première chaîne de production, se comportait de façon désastreuse dans la seconde !

## 6. DISCUSSION

Des expériences décrites précédemment nous pouvons tirer un certain nombre de conclusions, que nous regrouperons sous trois rubriques :

* dépendance de la machine câblée ;

* découpage du texte des grands programmes ;

* importance des services soi disant annexes.

Nous écartons de cette discussion les améliorations ponctuelles dont l'intérêt apparaît à l'usage mais reste néanmoins limité.

Nous laissons également de côté des problèmes importants tels que la structuration des données ou l'emploi des pointeurs, qui ne se posent pas seulement dans les langages d'écriture de système, et requièrent donc une étude et un solution générales.

### 6.1 Dépendance de la machine câblée

Contrairement à ce qui est implicitement déduit de la propriété de Q1 dans [Wirth 68], donner au programmeur la possibilité d'exploiter dans certains cas, les particularités de la machine câblée (remise à 0 de 63 mots consécutifs en une instruction) n'implique pas qu'il doive en permanence souffrir de ces particularités. Trois de ces particularités se sont révélées particulièrement pénibles :

* l'absence de régularité ; par exemple il existe une addition de mot à registre, mais pas d'octet à registre ;

* l'extrême complication de la mise en oeuvre de certaines opérations (multiplication, EBS) ;

* la nécessité de gérer les registres généraux.

Il nous semble qu'il soit indispensable de les faire disparaître même d'un langage d'écriture de système pour les raisons suivantes :

* elles vont à l'encontre des objectifs Q2 et Q3.
* on connaît les algorithmes (en particulier pour la gestion des registres généraux) qui les prennent en charge en moyenne mieux que les programmeurs ;

* les parties d'un système ou l'utilisation explicite des instructions câblées est indispensable sont extrêmement petites (au plus 5 % du texte source).

### 6.1.1 Expérience du OLP

Pour pallier les inconvénients mis en évidence ci-dessus, nous avons défini un langage (baptisé OLP) qui inclut le LP 10070. Un module en OLP est constitué d'une collection de *def procédure* et de *routine*. Une *routine* est un sous-programme à l'intérieur duquel des registres sont gérés par le compilateur, sauf réserve explicite du programmeur. On a pu ainsi implanter :

* une transmission de *routine* à *routine* d'arguments désignés "par référence" ;

* des expressions et des conditions plus évoluées par systématisation des opérateurs existant dans le LP 10070 ;

* des structures (au sens PL/1), mais composées d'objets du LP10070 ;

L'expérience a montré que :

a) un pré-processeur traduisant le OLP en LP 10070 pouvait être réalisé rapidement et sans trop diminuer la vitesse de la chaîne de production.

Le tableau ci-dessous donne des ordres de grandeur.

<table><caption>Tableau 2 : pour évaluation du OLP</caption><tr><td>Nombre de cartes OLP</td><td>600</td></tr><tr><td>Nombre de cartes LP 10070 générées</td><td>1000</td></tr><tr><td>Temps d'exécution des 2 traducteurs (OLP+LP10070)</td><td>20 s</td></tr><tr><td>Temps passé dans le système (BPM)</td><td>2 1/2 mn</td></tr><tr><td>Nombre de mots de code exécutable générés</td><td>700</td></tr><tr><td>Nombre de mots de données générés</td><td>1300</td></tr></table>
b) des utilisateurs connaissant aussi bien le OLP que le LP 10070 ne font pratiquement pas d'erreur de codage en utilisant le OLP (restent évidemment les erreurs de logique).

c) le OLP reste toutefois difficile à utiliser pour 2 raisons :

on a voulu conserver le LP 10070 inclus dans le langage ;

la version du pré-processeur que nous avons réalisée est incomplète : nous n'avons pas traité toute une collection de cas particuliers qui ne nous intéressaient pas pour cette expérience.

### 6.1.2 Introduction de la machine câblée

Le texte d'un système devrait donc être constitué de 2 parties :

une partie I indépendante de la machine câblée,

une (petite) partie D très dépendante de la machine câblée.

Exemple : Supposons :

a) que l'on veuille utiliser les sémaphores dans une partie du système ;

b) qu'aux opérations CREER, P, V, DETRUIRE, correspondent des appels au superviseur (ordres câblés), les paramètres actuels étant dans certains registres généraux bien précis ;

c) qu'on ne tienne pas à leur faire correspondre un sous-programme. Est-il possible de décrire la façon dont ces opérations doivent être complilées sans que ces opérations aient été prévues lors de la conception du langage et qu'on ait à modifier le compilateur ?

On remarquera qu'il ne s'agit pas d'étendre le langage par de nouvelles abréviations.

Le problème des relations entre les deux parties D et I peut recevoir deux types de solution :

le programmeur connait la façon dont la partie indépendante est compilée, et il peut à tout endroit de cette partie insérer des ordres câblés. C'est la solution adoptée dans les langages FORTRAN IV et LPS disponibles sur la 10070.
Il existe une interface bien définie entre le langage de I et le langage de la machine câblée, mais on ne peut pas mélanger ces deux langages. C'est la solution adoptée dans LIS ([Ichbiah 72]).

Il semble que cette seconde solution soit meilleure car la partie D y est soigneusement localisée et son extension au gré des programmeurs impossible. En outre, dans la première solution, l'optimisation se trouve compliquée, du fait que le programme risque d'être fractionné en séquences dans lesquelles le compilateur connaît par exemple l'utilisation des registre, séparées par des séquences dans lesquelles il l'ignore.

La mise ou non d'opérations d'entrée ou de sortie dans un langage d'écriture de système pose un choix similaire :

mettre ces opérations dans le langage. C'est ce qui a été fait pour le LP70. Ces opérations diffèrent suivant le système d'exploitation (SIRIS 7 ou SAM) ;

faire une interface entre le langage et un langage différent possédant les opérations d'entrée et de sortie. C'est ce qui a été fait pour le LP 10070, avec les avantages suivants :

localisation, comme ci-dessus ;

centralisation : chaque opération d'entrée ou de sortie étant une *ref procédure*, le programme qui utilise ces opérations devient par la force des choses indépendant de la façon dont sont réalisées ces opérations ; par exemple la lecture d'une chaîne de caractères peut être commutée d'un lecteur de cartes à un télétype.

L'expérience montre que cette centralisation ne vient pas naturellement à l'esprit des programmeurs.

## 6.2 Découpage du texte des grands programmes

Le PL360 (comme d'ailleurs le PASCAL) conserve la définition d'un programme qu'on trouve dans ALGOL 60 : un bloc compilable en une fois. Cette définition se révèle très insuffisante, tant pour des raisons théoriques (un système n'est pas un programme séquentiel) que pour des raisons pratiques (un programme de très grande taille doit être fractionné pour être compilé).
Les fractionnements introduits tant dans le LP70 que dans le LP 10070 sont assez primitifs, dans la mesure où lors du regroupement (édition de liens) on ne peut que recalculer des adresses. Le module (unité de compilation) n'a donc pas de signification bien précise : il n'y a pas dans le langage de raison particulière pour mettre *def word* A dans un module, et *ref word* A dans un autre, plutôt que le contraire (c'est pourquoi dans le LP70 on met *word* A dans un *globaldata* dont la description est répétée dans les deux modules). Nous sommes donc arrivés aux conclusions suivantes :

a) si on admet que le découpage en modules est seulement une commodité d'utilisation du compilateur, l'emploi de *commun* est acceptable, à condition qu'il existe un moyen de n'écrire le texte du *commun* qu'une seule fois (ce qui évite les erreurs de mise à jour).

b) il semble que, dans un grand nombre de cas, on puisse faire coïncider cette commodité du découpage avec une signification : il est souhaitable qu'un module soit une collection de *def procédure* ayant en commun un *certain* nombre de variables inconnues à l'extérieur du module, et protégées de ce fait. Seraient des modules en ce sens les allocateurs de mémoire, les dictionnaires, etc...

c) la compilation d'un module devait pouvoir utiliser le résultat de la compilation d'autres modules en tant que tels. Un exemple de cette possibilité est la directive *SYSTEM* en *METASYMBOL* 10070 qui permet d'utiliser dans un module une collection de macro-instructions préalablement compilées.

Cette possibilité a l'avantage d'assurer l'unicité d'un grand nombre de descriptions : descriptions de tables, de formats d'appels de procédures, de constantes, etc...

Inversement le compilateur doit au moins indiquer la portée de certaines modifications, sinon les répercuter.
### 6.3 Importance des services soi-disant annexes

Deux services nous ont fait particulièrement défaut :

* une mise en place automatique du texte des programmes,

* une liste alphabétique des objets crées dans chaque programme, avec tous les renseignements le concernant (nom, nature, emplacement de la déclaration, nombre d'apparitions, etc...).

Cette absence s'est traduite par un temps considérable passé à déchiffrer des programmes présentés n'importe comment, quelquefois à refaire cette présentation, et à retrouver un renseignement dans d'interminables listings.

Ce n'est pas que la programmation de ce genre de service soit difficile, mais c'est un travail ingrat et on estime toujours à tort avoir mieux à faire.

Ceci est un cas particulier de l'attitude consistant à accorder une importance disproportionnée à la traduction, au détriment de la mise en oeuvre, de la mise au point et de la documentation. (En ce qui nous concerne, la notion de chaîne de production ne s'est dégagée qu'après coup ; au départ, l'outil de construction du système était le compilateur).

Pour ce qui est de la mise au point, notre expérience nous a montré que :

* le langage doit inclure des instructions de mise au point standards soignées. Par exemple si le langage inclut des types composés, il doit y avoir la possibilité d'imprimer proprement la valeur des variables de ces types,

* il doit y avoir moyen de perfectionner les outils de mise au point, par exemple :

* en implantant une séquence de code exécutable à l'entrée et à la sortie de toute procédure pour collationner des statistiques,

* en disposant à l'exécution d'une table décrivant l'implantation des variables d'un module, de façon à pouvoir sortir en clair l'état du dit module (par impression ou interrogation).

Quant à la mise en oeuvre et à la documentation, nous ne pouvons en revanche que constater l'existence des problèmes. Leur solution semble relever entièrement de l'empirisme, et être non transmissible ce qui est corroboré par la rareté des publications consacrées à ces problèmes.
REMERCIEMENTS

La conception d'un langage et la réalisation de son compilateur sont
des travaux qui ne peuvent être menés à bien que par une équipe. Je tiens ici à
remercier J.Y. Babonneau, Bui Tuong Phong et J.J. Lévy pour leur partici-
pation au LP 10070, et M.S. Achard et M. Kronental pour leur participation
au OLP. Je tiens également à remercier C. Kaiser pour l'aide et les encoura-
gements qu'il m'a prodigués pour l'élaboration et la rédaction de cet article.
BIBLIOGRAPHIE

Bétourné C., Boulenger J., Ferrié J., Kaiser C., Krakowiak S., Mossière J.

70 : Le système ESOPE, *Congrès AFCET, Paris* (1970).

Boulenger J.

71 : Manuel d'utilisation du LP 10070, *Note interne Laboria* (1971).

Boullier P., Gros J., Jancène P., Lemaire A., Prusker F., Saltel E.

72 : METAVISU : A general purpose graphic system,
*IFIP Working Conf. on graphic languages, Vancouver* (1972).

CII

72 : Langage de programmation LP 70, manuel d'utilisation et d'opérations,
*Document 4069/FR, Louveciennes* (1972).

Ichbiah J.D., Héliard J.C., Rissen J.P.

72 : Spécifications de définition de LIS, *Carnet de notes Cii*,
*Louveciennes* (1972).

Rossiensky J.P., Tixier V.

69 : A kernel approach to system programming : SAM, in *J.T.TOU*,
*Software Engineering, Academic Press*.

Wirth N.

68 : PL360, a programming language for the 360 computers, *JACM* 15,1 (1968).

69 : On multiprogramming, machine coding and computer organization,
*CACM* 12,9 (1969).