L'IRILL , Initiative de Recherche et Innovation sur le Logiciel Libre,
organisait une rencontre LLVM mardi 25 septembre à Paris dans les locaux de
l'INRIA Place d'Italie dans le 13e arrondissement. Les organisateurs, Arnaud de
Grandmaison, Duncan Sands, Sylvestre Ledru et Tobias Grosser ont chaleureusement
accueilli environ 8 personnes dont 3 de Logilab.
L'annonce
de l'IRILL indiquait une rencontre informelle avec une potentielle Bug
Squashing Party sur Clang. Nous n'avons pas écrasé d'insectes mais avons
plutôt discuté: discussions techniques, petits trolls, échanges de connaissances,
d'outils et d'expériences accompagnés de bières, sodas, gâteaux et pizzas (et une
salade solitaire et orpheline qui n'a finie dans le ventre de personne
contrairement aux pizzas mexicaines ou quatre fromages).
LLVM (Low Level Virtual Machine) est un projet développé depuis une dizaine
d'années qui propose une collection d'outils et de modules facilement
réutilisables pour construire des logiciels orientés langages :
interpréteurs, compilateurs, optimiseurs, convertisseurs source-to-source...
Depuis l'étage d'entrée du code dans le compilateur (ou frontend), en passant
par l'optimiseur indépendant de la plate-forme, jusqu'à la génération de code
machine (backend) et ce, pour plusieurs architectures (X86, PowerPC, ARM,
etc.), le choix de son design en fait un outil tout à fait intéressant. Parmi
ces frontends, il y a le fameux Clang (C/C++, Objective-C), GHC
(Haskell). Et le projet dragonegg permet de l'utiliser comme plug-in à GCC
et donc de bénéficier des différents frontends GCC (Ada, Fortran, etc.).
De nombreux outils se sont construits autour
du framework LLVM : LLDB un débugger, vmkit une implémentation de la
machine virtuelle Java et .NET ou encore polly, un projet de recherche dont
Tobias est l'un des deux co-fondateurs, qui a pour objectif d'optimiser un
programme indépendamment du langage via des polyhedral optimizations.
Les principaux contributeurs à ce jour sont Apple, Google, des contributeurs
"individuels" dont Duncan Sands, suivis par des laboratoires de recherche et
autres. Voir l'article de Sylvestre sur "Who is in control of LLVM/Clang ?".
La clef de voûte de LLVM est sa représentation intermédiaire (IR pour
Intermediate Representation). C'est une structure de données qui représente
sous forme SSA (Single Static Assignment) les flux de données et de contrôle.
Cette forme est plus "haut-niveau" et plus lisible que du code assembleur, elle
comporte certaines informations de type par exemple. Elle constitue le pivot de
l'infrastructure LLVM : c'est ce que produisent les frontends comme
Clang, qui est ensuite passée aux optimiseurs de LLVM et est ensuite
consommée par les backends dont le rôle est de les transformer en code machine
natif.
En voici un exemple en C:
int add(int a)
{
return a + 42;
}
La représentation intermédiaire est donnée via Clang :
$> clang -S -emit-llvm add_function.c -o add_function.ll
L'extension *.ll désigne des "fichiers IR" et le résultat donne:
define i32 @add(i32 %a) nounwind uwtable {
%1 = alloca i32, align 4
store i32 %a, i32* %1, align 4
%2 = load i32* %1, align 4
%3 = add nsw i32 %2, 42
ret i32 %3
}
Cette représentation peut ensuite être bitcodée, assemblée ou compilée. Voir
les différentes commandes LLVM pour
assembler, désassembler, optimiser, linker, exécuter, etc.
Une autre utilisation intéressante de cette infrastructure est l'utilisation de
la bibliothèque Clang pour parser du code C/C++ afin de parcourir
l'Abstract Syntax Tree (AST). Ceci offre notamment de belles possibilités
d'introspection. Google a d'ailleurs rédigé un tutoriel sur les plugins Clang et ses
possibilités via l'API de Clang,
notamment la classe ASTConsumer. Il est possible de parcourir l'ensemble des
constructeurs, de connaître la propriété d'une classe (abstraite ou non),
parcourir les membres d'une classe, etc. Avant de partir, nous parlions de la
possiblité de propager un changement d'API à travers toutes les bibliothèques
qui en dépendent, soit la notion de patch sémantique.
Nous avons aussi profité pour parler un peu du langage Julia ou de Numba.
Pythonistes convaincus, nous connaissions déjà un peu Numba, projet de Travis
Oliphant, contributeur NumPy et papa de SciPy. Ce projet utilise
l'infrastructure LLVM pour compiler du byte-code Python en code machine
(Just In Time compilation), en particulier pour NumPy et SciPy. Les
exemples montrent comment passer d'une fonction Python qui traite un tableau
NumPy à une fonction compilée Just In Time. Extrait issu d'un des exemples
fournis par le projet Numba :
from numba import d
from numba.decorators import jit as jit
def sum2d(arr):
M, N = arr.shape
result = 0.0
for i in range(M):
for j in range(N):
result += arr[i,j]
return result
csum2d = jit(ret_type=d, arg_types=[d[:,:]])(sum2d)
Oui, la fonction sum2d n'est pas optimale et très "naïve". Néanmoins, la
fonction compilée va près de 250 fois plus vite ! Numba utilise les bindings
LLVM pour Python via llvm-py afin de passer du code Python, à
l'Intermediate Representation pour ensuite utiliser les fonctionnalités JIT
d'LLVM.
Entre programmeurs de langages à typage statique, nous avons aussi parlé de C et
d'Ocaml (dont il existe des bindings pour LLVM) et mentionné de beaux projets
tels que PIPS et Coccinelle.
Pour finir, nous savons maintenant prononcer Clang. On dit "klang" et non
"cilangue". Nous avons appris que gdb avait son propre parser et n'utilisait
pas le parser de GCC. Rappelons-le, l'un des grands
avantages de LLVM & Clang face à GCC est sa modularité et la possibilité
d'utiliser une des pierres qui forme l'édifice pour construire sa propre maison.
Nous finissons ce billet par une citation. David Beazley disait lors de sa présentation à Eursoscipy 2012 :
The life is too short to write C++.
Certes. Mais qu'on le veuille ou qu'on le doive, autant se servir d'outils biens
pensés pour nous faciliter la vie.
Encore merci aux organisateurs pour cette rencontre et à la prochaine.
Quelques références