• Aucun résultat trouvé

Langages dédiés compilés et implémentés comme une biblio-

2.2 Partage de code

2.2.5 Langages dédiés compilés et implémentés comme une biblio-

Ainsi, toutes les solutions aux limitations des langages dédiés implémentés sous forme de bibliothèques présentent de nouvelles limitations diminuant leur applicabi- lité.

En fait, le principal inconvénient des bibliothèques – l’impossibilité de spécialiser le code à exécuter pour une abstraction et une plateforme données – est exacerbé dans notre situation (le développement d’applications Web) mais n’en est pas l’apanage. En effet, ce problème peut être généralisé à la plupart des abstractions logicielles.

La plupart des composants logiciels sont écrits pour être paramétrables ou confi- gurables. Ainsi, un même composant peut être utilisé différement selon qu’il est in- tégré dans un environnement de test ou de production. Un exemple typique est la connexion à une base de données : des URL différentes sont utilisées selon l’environ- nement (test ou production).

Cela signifie que le code du programme qui se connecte à la base de données est paramétré par la connexion effective à la base de données. Pourtant, une fois lancé, le programme se connecte toujours à la même base de données.

Partage de code 33

On observe donc une situation similaire à celle rencontrée dans l’exemple du lan- gage dédié à la définition de fragments de HTML : un composant logiciel est souvent plus général que l’utilisation qui en est faite à l’exécution, et cela se paie à l’exécution par un niveau d’indirection supplémentaire.

L’idéal serait que le code finalement exécuté ne souffre pas de ce niveau d’in- direction : une fois la connexion à la base de données connue, on aimerait pouvoir construire un programme directement relié à celle-ci.

Les systèmes de compilation à la volée (JIT ) sont capables de faire certaines de ces optimisations, au prix d’une analyse du comportement du programme à l’exécution. Cependant des travaux ont montré que les JITs ne détectent pas autant d’opportunités d’optimisations qu’ils pourraient [RSB+14].

En fait, un autre système effectuant ce genre d’optimisations existe déjà et est uti- lisé par de nombreuses personnes sans le savoir. En effet, les outils de construction de projets remplissent exactement ce rôle. Ces outils permettent de compiler le code d’un projet et de gérer son cycle de vie (exécution des tests, déploiement, etc.). La plu- part des ces outils de construction de projets sont capables de générer une partie du code source à compiler. Par exemple, dans maven, cela correspond à la phasegenerate- sources. Dans sbt, cela correspond à la tâche sourceGenerators. Dans les deux cas, les outils génèrent une partie du code source du programme à construire. Le code source du programme final est donc le produit de l’exécution d’un premier programme. Ce programme est un générateur de programmes.

Ainsi, on observe qu’il est très fréquent qu’un programme soit généré par un autre programme. Dans le cas des outils de construction de projet, la distinction est claire car il y a effectivement deux phases d’exécution : une première pour générer du code, et une seconde pour exécuter ce code. Dans le cas des composants logiciels généraux, bien qu’il n’y ait qu’un seul code source et qu’une seule phase d’exécution, on est dans une situation où un programme est obtenu à partir d’un autre programme : en effet, une partie de l’exécution du programme correspond à l’interprétation des paramètres et la seconde partie à l’exécution effective du programme.

Notons que cette observation a déjà été à l’origine de travaux en 1986 [JS86] : les auteurs introduisent le concept destaging visant à évaluer certaines parties d’un pro- gramme avant les autres selon leur fréquence d’exécution ou la disponibilité des don- nées utilisées.

Les deux approches (outils de construction de programmes et composants géné- raux) ont leurs avantages et inconvénients. Les outils de construction de programme, grâce à leur première phase d’exécution sont capables de générer du code plus spécia- lisé, et donc bénéficient de meilleures performances lors de la seconde phase d’exécu- tion. Les composants généraux sont des citoyens de première classe dans tout le reste du programme : ils peuvent être passés en paramètre d’une fonction ou retournés comme résultat, ou être combinés entre eux, ce qui donne une plus grande flexibilité pour le programmeur, au détriment d’une perte de performances à l’exécution.

Les langages dédiés compilés et implémentés sous forme de bibliothèque visent à bénéficier des avantages des deux approches en minimisant leurs inconvénients. L’idée essentielle est la suivante : les unités de langage sont fournies sous forme de bi-

Figure2.7 – Langages dédiés compilés et implémentés par des bibliothèques. Un lan- gage dédié est fourni par une bibliothèque dont l’implémentation produit une repré- sentation abstraite du programme à générer. Un générateur de code peut dériver, à partir de cette représentation abstraite, les programmes correspondant, côtés client et serveur.

bliothèques dont l’implémentation retourne le code source d’un programme optimisé. Des travaux ont introduit cette idée en 1996 [K+96, Hud96]. Une amélioration consiste à utiliser une représentation abstraite du programme à générer plutôt que directement son code source [EFDM00]. Cela permet de réaliser de nombreuses réécritures de code ou optimisations telles que l’élimination de sous-expressions communes ou du code mort. La figure 2.7 illustre cette approche sur l’exemple des définitions de fragments de HTML.

Avec cette approche, le développeur bénéficie du système linguistique du lan- gage hôte pour définir et combiner entre elles des représentations abstraites de pro- grammes obtenues avec le langage dédié à son problème.

La figure 2.8 illustre les différences entre les langages dédiés implémentés par des bibliothèques, intérprétés ou compilés. Dans le premier cas, les concepts dédiés à un problème sont implémentés par des bibliothèques qui sont traduites, par le compila- teur du langage hôte, vers du code pour les environnements client et serveur. Ce code, trop général, est adapté à l’exécution vers les spécificités des environnements client et serveur. Dans le second cas, les concepts dédiés à un problème sont également implé- mentés par des bibliothèques, mais cette fois-ci c’est leur exécution qui produit le code pour les environnements client et serveur. Ce code, produit par les bibliothèques plu- tôt que par le compilateur du langage, traduit chaque concept vers une représentation spécifique dans chaque environnement cible.

Partage de code 35

Figure2.8 – Différence entre les langages dédiés implémentés par des bibliothèques, interprétés (à gauche) ou compilés (à droite).

Documents relatifs