L'assembleur LLVM est la représentation textuelle du code intermédiaire du compilateur LLVM. Vous trouverez les spécifications complètes du langage sur le site du projet : http://llvm.org/docs/LangRef.html.
Les fichiers contenants du code assembleur LLVM doivent porter l'extension ".ll"
L'utilitaire llc permet de transformer du code assembleur LLVM en code natif (assembleur x86 sur les machines intel).
Par exemple pour transformer hello_world.ll en assembleur natif :Le code généré est écrit dans le fichier hello_world.s que vous pouvez ensuite compiler avec le compilateur de votre choix (gcc par exemple).
L'assembleur LLVM permet la définition de fonctions avec un type de retour et une liste de paramètres.
Syntaxe basique des déclarations de fonctions :<ResultType> est le type de retour de la fonction et <argument list> est la liste des paramètres de la fonction. Ces paramètres sont exprimés de la même manière qu'en C.
Exemple :Les types de base en assembleur LLVM sont :
En LLVM, les identifiants commencent toujours par % ou @ en fonction de leur visibilité. Les identifiants dont la visibilité est globale (comme les fonctions ou les variables globales) commencent par un @, alors que les identifiants locaux commencent par le signe %. Après ce préfixe les identifiants peuvent être composés de n'importe quel caractère parmi les lettres (minuscules ou majuscules), les chiffres, le blanc souligné, le point, et le signe dollar.
Voici une liste des principales instructions du langage LLVM (vous pouvez trouver la liste complète sur : http://llvm.org/docs/LangRef.html#instruction-reference. Les types réels et entiers peuvent être également des vecteurs d'entiers ou de réels.
Instruction | Syntaxe | Exemple | Description |
add | <result> = add <ty> <op1>, <op2> | %0 = add i32 %1, %2 | Somme entière. <ty> doit être un type entier. |
sub | <result> = sub <ty> <op1>, <op2> | %0 = sub i32 %1, %2 | Soustraction entière <ty> doit être un type entier. |
fadd | <result> = fadd <ty> <op1>, <op2> | %0 = fadd float %1, %2 | Somme flottante. <ty> doit être un type réel. |
fsub | <result> = fsub <ty> <op1>, <op2> | %0 = fsub i32 %1, %2 | Soustraction flottante <ty> doit être un type réel. |
fdiv | <result> = fdiv <ty> <op1>, <op2> | %0 = fsub float %1, %2 | Division flottante <ty> doit être un type réel. |
Instruction | Syntaxe | Exemple | Description |
icmp | <result> = icmp <cond> <ty> <op1>, <op2> | %0 = icmp eq i32 %1, %2 | Comparaison
entière. <cond> donne le type
de comparaison à faire.
<cond> peut être :
|
fcmp | <result> = fcmp <cond> <ty> <op1>, <op2> | %0 = fcmp oeq float %1, 1.5 |
Comparaison flottante.
<cond> donne le type
de comparaison à faire.
<cond> peut être :
|
call | <result> = <ty> call <func> (<args>) | %0 = call i32 @add(i32 %1, i32 %2) | Appel de fonction. Les paramètres précédés de leur type. |
br | br i1 <cond>, label <iftrue>, label <iffalse> | cf code fonction max. | Branchement conditionnel |
ret |
ret <ty> <value> ret void |
ret i32 %a | Retour de fonction. <ty> est le type de retour de la fonction et <value> la valeur retournée. |
Instruction | Syntaxe | Exemple | Description |
alloca | <ptr> = alloca <ty> | %ptr = alloca i32 | Alloue de la mémoire dans la pile de la fonction courante pour une variable de type <ty>. <ptr> est donc de type <ty>* (un pointeur sur le type alloué). |
load | <result> = load <ty>* <pointer> | %a = load i32* %a_ptr | Charge la valeur à l'adresse pointée par <pointer> dans la variable <result>. |
store | store <ty> <value>, <ty>* <pointer> | store i32 %a, i32* %a_ptr | Sauvegarde la valeur <value> à l'adresse pointée par <pointer>. |
getelementptr | <result> = getelementptr <pty>* <ptrval>{, <ty> <idx>}* | cf exemple accès aux éléments d'un tableau | Permet de calculer l'adresse d'une variable (comem l'opérateur & en C). Si ty et idx sont spécifié getelementptr retourne ptrval + idx*sizeof(pty). Cette instruction permet donc de calculer l'adresse des différents éléments d'un tableau. |
L'assembleur LLVM est en forme SSA (single static assignment) c'est à dire que les variables du langage ne peuvent être affectées qu'une seule fois. Pour générer le code LLVM, vous allouerez donc les variables définies dans le code d'entrée dans la pile des fonctions (avec l'instruction alloca). Et vous accéderez à ces variables via les instructions load et store. Ainsi dans le langage LLVM que vous générerez, les variables que vous manipulerez seront des pointeurs que vous n'aurez pas besoin de modifier. Ce code sera donc conforme à la forme SSA.
Bien que le type tableau existe en assembleur LLVM l'accès à leurs éléments ne peut se faire avec l'opérateur crochet comme en C. Pour accéder à un élément d'un tableau il faut calculer sont adresse et y accéder comme vous feriez pour un scalaire.
En LLVM les variables globales sont toujours des pointeurs. Aussi dans la déclaration suivant :
Variable globale :le type de @str est [14 x i8]* (et non juste un tableau de caractères : [14 x i8]). C'est la raison pour laquelle on utilise deux indices dans l'instruction getelementptr. Pour calculer l'adresse du premier caractère de la chaîne. En C on aurait calculé sont adresse comme suit :