#define Size 819000
int sieve ( int N ) {
int64_t i , k , prime , count , n ; char flags [ Size ];
for ( n = 0 ; n < N ; n ++ ) {
count = 0 ;
for ( i = 0 ; i < Size ; i ++ )
flags [ i ] = 1 ;
for ( i = 0 ; i < Size ; i ++ )
if ( flags [ i ]) {
prime = i + i + 3 ;
for ( k = i + prime ; k < Size ; k += prime )
flags [ k ] = 0 ;
count ++ ;
}
}
return count ;
}
void ex100 ( void ) {
printf ("sieve (100) = %d", sieve (100));
}
m_sieve : module
export sieve
sieve : func i32, i32:N
local i64:iter, i64:count, i64:i, i64:k, i64:prime, i64:temp, i64:flags
alloca flags, 819000
mov iter, 0
loop : bge fin, iter, N
mov count, 0; mov i, 0
loop2 : bge fin2, i, 819000
mov u8:(flags, i), 1; add i, i, 1
jmp loop2
fin2 : mov i, 0
loop3 : bge fin3, i, 819000
beq cont3, u8:(flags,i), 0
add temp, i, i; add prime, temp, 3; add k, i, prime
loop4 : bge fin4, k, 819000
mov u8:(flags, k), 0; add k, k, prime
jmp loop4
fin4 : add count, count, 1
cont3 : add i, i, 1
jmp loop3
fin3 : add iter, iter, 1
jmp loop
fin : ret count
endfunc
endmodule
m_ex100 : module
format : string "sieve (10) = %dn"
p_printf : proto p:fmt, i32:result
p_sieve : proto i32, i32:iter
export ex100
import sieve, printf
ex100 : func v, 0
local i64:r
call p_sieve, sieve, r, 100
call p_printf, printf, format, r
endfunc
endmodule
func
décrit la signature de la fonction (en prenant un argument entier signé de 32 bits et renvoyant une valeur entière signée de 32 bits) et l'argument de fonction N
qui sera une variable locale de type entier signé de 64 bits;
string
décrit les données sous forme de chaîne Cexport
décrit les fonctions ou les données du module qui sont visibles en dehors du module actuelimport
décrit les fonctions ou les données du module qui doivent être définies dans d'autres modules MIRproto
décrit des prototypes de fonctions. Sa syntaxe est la même que la syntaxe func
call
sont des instructions MIR pour appeler des fonctions MIR_load_external
m1
et m2
sont les modules m_sieve
et m_e100
, func
est la fonction ex100
, sieve
est la fonction sieve
) : /* ctx is a context created by MIR_init / MIR_init2 */
MIR_load_module ( ctx , m1 ); MIR_load_module ( ctx , m2 );
MIR_load_external ( ctx , "printf" , printf );
MIR_link ( ctx , MIR_set_interp_interface , import_resolver );
/* or use MIR_set_gen_interface to generate and use the machine code */
/* or use MIR_set_lazy_gen_interface to generate function code on its 1st call */
/* use MIR_gen (ctx, func) to explicitly generate the function machine code */
MIR_interp ( ctx , func , & result , 0 ); /* zero here is arguments number */
/* or ((void (*) (void)) func->addr) (); to call interpr. or gen. code through the interface */
binfmt_misc
Le binaire mir-bin-run
est prêt à être utilisé depuis binfmt_misc
avec la ligne suivante (exemple) :
line=:mir:M::MIR::/usr/local/bin/mir-bin-run:P
echo $line > /proc/sys/fs/binfmt_misc/register
Adaptez le chemin binaire mir-bin-run à votre système, c'est celui par défaut
Et cours avec
c2m your-file.c -o your-file
chmod +x your-file
./your-file your args
L'exécutable est "configurable" avec des variables d'environnement :
MIR_TYPE
définit l'interface pour l'exécution du code : interp
(pour l'interprétation), jit
(pour la génération) et lazy
(pour la génération paresseuse, par défaut) ;MIR_LIBS
(liste séparée par deux points) définit une liste de bibliothèques supplémentaires à charger ;MIR_LIB_DIRS
ou LD_LIBRARY_PATH
(liste séparée par deux points) définit une liste supplémentaire de répertoires dans lesquels rechercher les bibliothèques.En raison de la nature liée de
mir-bin-run
avecbinfmt_misc
, il peut être un peu étrange d'appeler directementmir-bin-run
. L'indicateurP
sur binfmt_misc transmet un argument supplémentaire avec le chemin complet vers le binaire MIR.
Pipeline d'optimisation très court pour la vitesse et la légèreté
Seule l'utilisation d'optimisation la plus précieuse :
Différents niveaux d'optimisation pour ajuster la vitesse de compilation par rapport aux performances du code généré
La forme SSA de MIR est utilisée avant l'attribution du registre
Simplicité de mise en œuvre des optimisations par rapport aux performances extrêmes du code généré
Plus de détails sur le pipeline complet du compilateur JIT :
Simplifier : baisser le MIR
Inline : appels MIR intégrés
Build CFG : création d'un Control Flow Graph (blocs de base et bords CFG)
Construire SSA : Création d'un formulaire d'affectation statique unique en ajoutant des nœuds phi et des arêtes SSA aux opérandes
Transformation d'adresse : supprimer ou modifier les instructions MIR ADDR
Numérotation des valeurs globales : suppression des insn redondants via GVN. Cela inclut une propagation constante et des éliminations de charges redondantes
Propagation de copie : propagation de copie SSA et suppression des instructions d'extension redondantes
Élimination des magasins morts : suppression des magasins redondants
Dead Code Elimination : suppression des insns avec des sorties inutilisées
Soulagement de la pression : déplacement des inns pour diminuer la pression du registre
SSA combine : combiner des adresses et des paires d'instructions de comparaison et de branchement
Hors SSA : suppression des nœuds phi et des bords SSA
Options de saut : Différentes optimisations de saut
Machinize : exécutez du code dépendant de la machine en transformant MIR pour les appels ABI, 2-op insns, etc.
Trouver des boucles : trouver des boucles naturelles et créer un arbre de boucles
Build Live Info : calcul des live in et live out pour les blocs de base
Build Register Conflicts : construction d'une matrice de conflits pour les registres impliqués dans les mouvements. Il est utilisé pour la fusion des registres
Coalesce : coalescence agressive du registre
Register Allocator (RA) : RA à balayage linéaire basé sur les priorités avec division de plage en direct
Build Live Ranges : calcul des plages de points du programme pour les registres
Attribuer : RA rapide pour -O0
ou RA à balayage linéaire basé sur les priorités pour -O1
et supérieur
Réécriture : transformer MIR en fonction de l'attribution en utilisant les enregistrements durs réservés
Combiner (sélection de code) : fusionner les insn dépendant des données en un seul
Dead Code Elimination : suppression des insns avec des sorties inutilisées
Generate Machine Insns : exécutez du code dépendant de la machine en créant des insns de machine
c2m
. Voir README.mdmir.h
et mir.c
contiennent le code API majeur, y compris l'entrée/sortie du binaire MIR et la représentation textuelle MIR.mir-dlist.h
, mir-mp.h
, mir-varr.h
, mir-bitmap.h
, mir-hash.h
, mir-htab.h
, mir-reduce.h
contiennent du code générique correspondant pour les listes, pools de mémoire, tableaux de longueur variable, bitmaps, calculs de hachage, tables de hachage et compression/décompression de données. Le fichier mir-hash.h
est une fonction de hachage générale, simple et de haute qualité utilisée par les tables de hachage.mir-interp.c
contient du code pour l'interprétation du code MIR. Il est inclus dans mir.c
et n'a jamais été compilé séparémentmir-gen.h
, mir-gen.c
, mir-gen-x86_64.c
, mir-gen-aarch64.c
, mir-gen-ppc64.c
, mir-gen-s390x.c
et mir-gen-riscv64.c
contient du code pour le compilateur MIR JITmir-gen-x86_64.c
, mir-gen-aarch64.c
, mir-gen-ppc64.c
, mir-gen-s390x.c
et mir-gen-riscv64.c
sont le code dépendant de la machine du compilateur JIT.mir-.c
contiennent du code simple dépendant de la machine, commun à l'interpréteur et au compilateur JIT.mir-.h
contiennent des déclarations communes à l'interpréteur et au compilateur JIT.mir2c/mir2c.h
et mir2c/mir2c.c
contiennent du code pour le compilateur MIR vers C. Le code généré n'est peut-être pas portablec2mir/c2mir.h
, c2mir/c2mir.c
, c2mir/c2mir-driver.c
et c2mir/mirc.h
contiennent du code pour le compilateur C vers MIR. Les fichiers dans les répertoires c2mir/x86_64
et c2mir/aarch64
, c2mir/ppc64
, c2mir/s390x
et c2mir/riscv64
contiennent respectivement x86_64, aarch64, ppc64le, s390x et riscv, du code dépendant de la machine pour le compilateur C vers MIR.mir-bin-run.c
contient le code pour mir-bin-run
décrit ci-dessusmir-bin-driver.c
avec l'utilitaire b2ctab
peut être utilisé de manière portable pour générer des fichiers binaires à partir de fichiers binaires MIR.mir-utils
contient différents utilitaires pour travailler avec MIR, par exemple transformer un MIR binaire en MIR textuel et vice versa.adt-tests
, mir-tests
, c-tests
et c-benchmarks
contient du code pour tester et comparer MIR et c2m
make bench
et make test
Intel i5-13600K avec 64 Go de mémoire sous FC37 avec GCC-12.3.1
Générateur MIR | Interprète MIR | gcc-O2 | gcc-O0 | |
---|---|---|---|---|
compilation [1] | 1.0 (249us) | 0,09 (22us) | 109 (27,1 ms) | 105 (26,1 ms) |
exécution [2] | 1,0 (1,74 s) | 13,7 (23,8 s) | 0,92 (1,6 s) | 2,28 (3,97 s) |
taille du code [3] | 1.0 (557 Ko) | 0,43 (240 Ko) | 58 (32,2 Mo) | 58 (32,2 Mo) |
LOC [4] | 1,0 (23,4 Ko) | 0,48 (11,3K) | 103 (2420 Ko) | 103 (2402K) |
[1] est basé sur le temps de compilation du code du tamis C (sans aucun fichier d'inclusion et en utilisant le système de fichiers mémoire pour GCC) et le code du tamis MIR correspondant par l'interpréteur MIR et le générateur MIR avec niveau d'optimisation 2
[2] est basé sur le meilleur temps de mur de 10 passages avec le niveau 2 d'optimisation du générateur MIR utilisé.
[3] est basé sur des tailles supprimées de cc1 pour le noyau GCC et MIR et un interpréteur ou un générateur pour MIR
[4] mon estimation basée uniquement sur les fichiers requis pour le compilateur GNU C x86-64 et les fichiers MIR pour un programme minimal permettant de créer et d'exécuter du code MIR
Intel i5-13600K avec 64 Go de mémoire sous FC37 avec GCC-12.3.1
c2m -O2 -eg (générateur) | c2m -ei (interprète) | gcc-O2 | gcc-O0 | |
---|---|---|---|---|
compilation [1] | 1.0 (336us) | 1,0 (337us) | 80 (27,1 ms) | 77 (26,1 ms) |
exécution [2] | 1,0 (1,74 s) | 13,7 (23,8 s) | 0,92 (1,6 s) | 2,28 (3,97 s) |
taille du code [3] | 1.0 (961 Ko) | 1.0 (961 Ko) | 34 (32,2 Mo) | 34 (32,2 Mo) |
LOC [4] | 1,0 (54,8 Ko) | 1,0 (54,8 Ko) | 44 (2420 Ko) | 44 (2420 Ko) |
[1] est basé sur le temps passé à compiler le code du tamis C (sans aucun fichier d'inclusion et avec l'utilisation du système de fichiers mémoire pour GCC)
[2] est basé sur le meilleur temps de mur de 10 passages avec le niveau 2 d'optimisation du générateur MIR utilisé.
[3] est basé sur les tailles supprimées de cc1 pour GCC et C2MIR, le noyau MIR, l'interpréteur et le générateur pour MIR.
[4] est basé sur tous les fichiers sources à l'exclusion des tests
Voici les performances du code généré liées à GCC -O2 pour différents compilateurs C sur 15 petits benchmarks C (à partir du répertoire c-benchmarks
) sur la même machine où
Moyenne | Géoméenne | |
---|---|---|
gcc-O2 | 1h00 | 1h00 |
gcc-O0 | 0,63 | 0,57 |
c2m -par exemple | 0,96 | 0,91 |
c2m-eb | 0,92 | 0,85 |
chibi | 0,38 | 0,30 |
bruit -O2 | 1.12 | 1.09 |
analyseur -O3 | 1.02 | 0,98 |
cproc | 0,68 | 0,65 |
lacc-O3 | 0,47 | 0,39 |
pcc -O | 0,80 | 0,78 |
tcc | 0,54 | 0,50 |
emcc -O2/eau | 0,60 | 0,55 |
wasi -O2/wasmer grue | 0,60 | 0,54 |
wasi -O2/wasmer LLVM | 0,78 | 0,72 |
wasi -O2/wasmer passe unique | 0,45 | 0,36 |
wasi -O2/temps d'eau | 0,92 | 0,87 |
c2m
) vers une autre cible pendant 1 à 2 mois.