• Aucun résultat trouvé

2.4 Optimisations d’OMicroB

2.4.2 Machine virtuelle sur mesure

La plupart des programmes compilés ne font pas usage de l’intégralité du jeu d’instructions bytecode associé au langage OCaml, mais seulement d’une sous-partie de ce dernier, qui correspond aux traits du langage réellement utilisés dans le programme. Par exemple, un programme n’utilisant pas la couche objet du langage OCaml ne contiendra pas, après compilation vers le bytecode, l’instruction GETPUBMET permettant l’appel à une méthode d’un objet. De même, puisqu’un grand nombre d’arguments est capable de tenir sur uniquement un octet, il est rare qu’il soit réellement fait usage d’instructions spécialisées pour les arguments sur quatre octets, comme l’instruction CLOSURE_4B. De ce fait, embarquer dans l’exécutable final un interprète capable de gérer de telles instructions absentes du bytecode du programme représente une perte d’espace, alors même que la mémoire d’un microcontrôleur est une ressource peu abondante. OMicroB, permet d’éviter ce gaspillage inutile de mémoire, en incluant lors de la compilation du programme un interprète de bytecode qui ne peut traîter que les instructions réellement présentes dans le bytecode du programme. Pour ce faire, des directives destinées au préprocesseur du compilateur C sont ajoutées au code de l’interprète afin d’empêcher la compilation du code lié à l’interprétation des instructions non utilisées dans le programme. Chaque opcode présent dans le programme correspond alors à une macro, définie dans le programme C généré par bc2c, qui représente une valeur constante (choisie de façon à ce que toutes les valeurs soient contiguës). La taille totale de l’exécutable transféré sur le microcontrôleur est ainsi réduite.

La figure 2.16 illustre la structure de l’interprète sur mesure sur un extrait de ce dernier : le code responsable du traitement de l’instruction BRANCHIF_4B n’est compilé qu’à condition que la macro cor-respondant à cette instruction soit définie dans le code C généré par bc2c.

# ifdef BRANCHIF_4B

case BRANCHIF_4B :

/* code d ’ interpretation de l ’ instruction */ break;

# endif

Figure 2.16 – Interprète sur mesure

D’autres optimisations qui reposeraient sur la même idée de généricité d’un programme C pourraient être envisageables. Nous pourrions en effet aller encore plus loin dans notre approche consistant à réa-liser une machine virtuelle spécifique à chaque programme, par exemple en remplaçant statiquement chaque instruction bytecode d’un programme par l’ensemble des instructions C qui lui correspondent, et ainsi se passer de l’utilisation d’un interprète de bytecode. Cette approche a été suivie par l’outil

OCamlCC [MV13], dont la première implantation transformait un bytecode OCaml en un programme C en remplaçant chaque instruction du bytecode par le code de bas niveau associé par un mécanisme de macro-expansion. Une telle approche donne de bonnes performances de vitesse, mais la taille des programmes croît rapidement compte tenu du fait que plusieurs références à une instruction bytecode particulière entraînent à chaque fois la duplication du code de bas niveau nécessaire pour l’exécuter. D’autres approches, comme CeML [Cha92] ou Camlot [Cri92] consistent à transformer du code source d’un programme écrit dans un langage ML en un code C, qui peut alors profiter des optimisations de compilateurs C. Le code généré par ces solutions est tout de même assez volumineux, et leurs biblio-thèques d’exécution ont généralement aussi une taille conséquente, car elles doivent par exemple intégrer

un mécanisme d’application générique pour construire des fermetures en cas d’application partielle de fonctions. Ces approches sont donc plutôt réservées à du matériel pour lequel les considérations liées à la consommation mémoires sont moins importantes que la vitesse d’exécution d’un programme. OMicroB nous semble être alors un bon compromis entre la portabilité apportée par l’implantation de la VM en C, et la réduction de taille des programmes issue de la représentation des programmes sous forme d’un bytecode qui factorise des séquences d’instructions de plus bas niveau. Nous suivons donc cette approche pragmatique, pour laquelle les vitesses d’exécution peuvent être un peu plus faibles, sans être particulièrement pénalisant pour les applications que nous visons.

2.4. Optimisations d’OMicroB 75

Conclusion du chapitre

L’utilisation de la machine virtuelle OMicroB constitue une approche générique et configurable pour permettre l’exécution de programmes OCaml sur des appareils très variés. En particulier, les optimisa-tions réalisées dans OMicroB permettent l’interprétation de bytecode OCaml sur des microcontrôleurs issus de gammes bénéficiant de peu de ressources matérielles. Cette machine virtuelle est capable d’exé-cuter des programmes OCaml sur des microcontrôleurs AVR dont les ressources en mémoire RAM sont fortement restreintes, comme le ATMega325p (2 ko de RAM), ATmega32u4 (2.5 ko de RAM), ou le ATmega2560 (8ko de RAM). Plusieurs projets académiques visant à porter OMicroB sur d’autres archi-tectures, comme les PIC32 ou les ARM Cortex-M0 (utilisés par les cartes de développement micro :bit, conçues par la BBC dans le but d’accompagner l’enseignement de la programmation à de jeunes enfants [⚓20]) sont en cours de réalisation, et les premiers résultats sont fort prometteurs : les premiers portages d’OMicroB ont en effet été réalisés sans encombre [PB19].

Le modèle de compilation des programmes OCaml considéré, qui consiste en l’utilisation du bytecode d’un langage de haut niveau, associé à un interprète générique, augmente fortement le portabilité des programmes : un même programme OCaml peut ainsi être aisément porté d’un appareil à un autre. De surcroît, cette portabilité permet de simuler facilement les programmes réalisés, en tenant compte des contraintes mémoires issues de la configuration de la machine virtuelle (comme la taille de la pile, ou celle du tas), et offre ainsi un procédé de débogage accéléré et simplifié.

Dans le but de poursuivre les efforts ayant pour cible de réduire l’empreinte des programmes OCaml sur la mémoire RAM, plusieurs techniques sont actuellement en cours d’étude. Notamment, une analyse fine permettant de détecter les valeurs constantes immutables dans un programme OCaml (comme par exemple les chaînes de caractères, ou les sprites d’un programme de jeu vidéo) permettrait de déplacer ces dernières dans la mémoire flash du programme lors de sa compilation, et ainsi de libérer la mémoire vive, plus étroite.

La table 2.1 recense les principales différences entre OMicroB et la machine virtuelle OCaml standard. Dans le chapitre 7, nous traiterons plus en détail des performances d’OMicroB, à la fois sur le plan de la vitesse d’exécution des programmes OCaml, mais également de leur empreinte mémoire.

En raison de sa richesse et son haut niveau d’expressivité, OCaml est un langage puissant pour décrire les comportements algorithmiques des programmes, et la sûreté de son typage apporte des garanties importantes pour la réalisation de programmes embarqués. Toutefois, OCaml n’est pas vraiment adapté à l’heure actuelle à la description des aspects concurrents d’un programme, ni au développement de sys-tèmes temps réel. Pourtant, les programmes embarqués que nous considérons possèdent de nombreux traits concurrents, et les systèmes critiques qu’ils contrôlent correspondent très souvent à des systèmes temps réel, pour lesquels les temps d’exécution doivent être finement contrôlés. De ce fait, nous présen-tons dans le chapitre suivant le langage OCaLustre, une extension du langage OCaml à la programmation synchrone, qui offre un modèle de concurrence léger et adapté à la nature des applications pour micro-contrôleurs. Cette extension est pleinement compatible avec OMicroB, et bénéficie ainsi des avantages d’OCaml et des optimisations de la machine virtuelle décrites dans cette section.

ZAM (ocamlrun) OMicroB

Taille des opcodes 32 bits 8 bits

Nombre d’instructions 148 148+ 46 inst. spécialisées

= 194

Taille des valeurs non configurable configurable

OCaml (dépendante de l’architecture) (16, 32, 64 bits) Taille des adresses (espace d’adressage) 32 bits (4 Go) 21 bits (2 Mo)

dans une configuration 32 bits

Représentation des Avec encapsulation Immédiate

flottants (allocation sur le tas)

Algorithme de GC Hybride Stop and Copy

(générationnel/ incrémental) ou Mark and Compact

77

3 OCaLustre : Programmation synchrone en

OCaml

OCaLustre est une extension synchrone à flot de données du langage OCaml, inspirée du langage Lustre [CPHP87, Ber86, HCRP91], permettant d’utiliser la couche d’abstraction synchrone pour pro-grammer la concurrence des programmes, et d’utiliser les traits de haut niveau du langage support, OCaml, pour développer les aspects logiques des applications. Cette extension, à l’empreinte mémoire très légère, est destinée à être exécutée en association avec la machine virtuelle OMicroB afin d’exécuter des programmes concurrents légers et sûrs sur des microcontrôleurs à faibles ressources.

Dans ce chapitre, nous décrivons en détail cette extension de langage. Nous présentons dans une première section les principaux traits et principes du langage permettant de réaliser des programmes synchrones en OCaml. Nous abordons en particulier dans cette section le système d’horloges synchrones implanté dans OCaLustre, qui permet de conditionner la présence de certaines valeurs lors de l’exécution d’un programme. Nous présentons ensuite la spécification des systèmes de types des programmes OCaLustre, qu’ils concernent à la fois les types « standards » représentant les valeurs portées par les éléments d’un programme, ainsi que les types d’horloges liés à la notion de cadencement d’un programme synchrone. Enfin, nous décrivons formellement la sémantique opérationnelle du langage, tirée de celle du langage Lustre.