Une structure globale par thread (existante dans la lib de threads originale réutilisée et augmentée)
Depuis le C on accède à la structure du thread courant avec les fonctions POSIX. Lorsqu'on passe du C à l'assembleur (à la naissance du thread ainsi que lors des callbacks) on passe en paramètre la structure locale à l'assembleur. L'assembleur utilise ensuite le registre r14 pour accéder à tout moment par une simple indirection aux données locales.
r14 était précédemment utilisé comme cache du pointeur d'exception, celui-ci est placé dans la structure locale au thread
Tout amd64.S a été réécrit pour utiliser directement les données locales au thread courant à la place des globales.
Le GC utilise deux structures distinctes :
Chaque thread réserve une page pour y allouer ses petits objets. La
réservation de page est protégée par un verrou (mais l'accès et la
modification du contenu des pages ne l'est pas). Ainsi la plupart du
temps, l'allocation d'un petit objet est aussi performante que dans
l'implémentation actuelle d'OCaml mais les threads peuvent allouer des
petits objets en parallèle. Les gros objets sont alloués directement
dans le tas partagé, qui est protégé en écriture par un verrou.
Une page neuve est allouée à un thread lorsqu'une allocation échoue et
qu'il reste des pages libres, ainsi qu'à sa naissance et après un GC.
S'il ne reste plus de page libres, on procède à un premier type de GC
“partiel” : le tas partagé sert de zone de destination à un Stop &
Copy (on vide les pages de tous les threads dans le tas partagé).
Lorsque le tas partagé est plein, on procède au gc “complet” : on
utilise Stop & Copy sur l'ensemble tas partagé + pages et on réalloue
éventuellement un tas partagé plus gros (on ne dispose pas encore de
réduction du tas).
Stop The World : Dans le cas général, on stoppe tous les threads à l'allocation. Lorsqu'une allocation échoue, le thread qui a échoué lance le GC, il demande alors aux autres de s'arrêter. lors des allocations suivantes des autres threads, si un GC est demandé, ils se mettent en pause (implémentation par conditions POSIX). Le cycle de GC commence lorsque tous les threads sont en pause. Une fois le GC terminé, tout repart.
Une primitive stop_the_world_wait attends la fin du GC en cours pour exécuter éventuellement une callback. Ceci permet, en plus de la pause des threads pendant le gc, d'implémenter proprement la mort des threads et les primitives de la lib de thread.
Un verrou global est utilisé pour mettre en exclusion mutuelle les parties modifiant les structures de gestion mémoire (exécution d'un GC, naissance du thread, mise en pause par les primitives de la lib de thread sans attendre l'allocation, etc.). On pourrait améliorer la granularité.
Ajout du -compact dans le startup.s : Le -compact permet de supprimmer les allocations inline mais l'option n'est pas prise en compte lors de la génération du startup.s (durant la phase de link). Nous avons juste fait en sorte que ce soit le cas, éventuellement on pourrait ajouter une option -supercompact.
Modification du emit.ml : Puisqu'on modifie l'utilisation du registre r14 dans l'amd64.S, on fait de même dans le code émit : on remplace simplement les accès à r14 par des accès indirects à r14.exception_pointer. La modification est très locale et on pourrait en faire une option du compilateur facilement.
L'ensemble des primitives de la lib de thread est implémenté.
Le module Gc change, les Weaks ne sont pas (encore) implémentés.
La version actuelle est sans bug connu mais il reste une bonne phase de nettoyage et de refactorisation.