Metafor

ULiege - Aerospace & Mechanical Engineering

User Tools

Site Tools


commit:2016:09_12

Commit 2016-09-12

Portage clang

Metafor peut maintenant être compilé avec clang (clang++ pour être plus précis). Ce nouveau compilateur est très à la mode chez les macophiles (c'est le compilateur par défaut du système Mac OS X). Pour Apple, il a l'avantage d'être sous licence FreeBSD, c'est à dire une license de type “domaine public” très peu contraignante pour des sociétés commerciales. Il est réputé également très rapide et moins “usine à gaz” que gcc au niveau du code source (c'est un projet relativement neuf en comparaison à gcc). Clang est également disponible sous Linux. C'est d'ailleurs sous Ubuntu que j'ai effectué le portage (ça me permet d'avoir accès aux touches [ et { qui sont quand même pratiques).

En ce qui nous concerne, clang nous apporte 2 bénéfices directs:

  • la possibilité de compiler Metafor sous Mac OS X. On pourra donc donner une version native à Kim et aux étudiants qui avaient trop d'argent pour acheter un laptop normal.
  • la possibilité de rendre le code encore plus respectueux de la norme. Le but à court terme est de lancer une “batterie clang” sur une machine Linux (gaston), voire un Mac quand on se sera décidé à en acheter un.

Problèmes de portage

Les problèmes de portage concernent principalement la gestion des templates. Deux aspects sont problématiques:

Les templates pourris de NURBS++

Cette lib de templates, qui nous permet de gérer les courbes et surfaces NURBS dans Metafor, nous mène la vie dure à chaque changement de compilateur. A chaque fois, on se dit qu'on va l'abandonner au profit d'une autre lib (par exemple, parasolid pourrait faire l'affaire… mais parasolid n'est pas distribuable gratuitement).

J'avais déjà bien galéré avec NURBS++ lors du portage MinGW (ici et ). Le problème vient du fait que certains templates sont “friend” avec des fonctions templates surfdéfinies… et le tout dans un namespace (on avait déjà trébuché là dessus sur le Mac de Jérémy Xhardez). Pour le portage MinGW, je m'en était sorti en supprimant toutes les instanciations explicites des classes de NURBS++ et en supprimant la DLL. Je croyais naïvement que ça allait résoudre définitivement les problèmes et permettre une compilation simple avec clang. Et bien non… néanmoins ici, je crois qu'on est face à un bug de clang. La fonction “template friend surdéfinie dans le namespace” n'est pas correctement compilée et génère un “unresolved” au link (de mtGeo puisque c'est là qu'on utilise NURBS++).

Dans ce commit, au lieu de la définir dans le namespace (dans namespace PLib { }), je l'ai sortie du namespace et compilée en la préfixant de PLib:: (Ca devrait revenir au même - d'où le bug que je suspecte au niveau de clang). J'ai aussi dû supprimer un paramètre de template int sans quoi clang ne s'en sortait toujours pas. Résultat: la fonction problématique a été dupliquée pour les 2 valeurs entières du fameux entier (2 et 3 - correspondant au cas 2D et 3D). Je conclurais bien que ce n'est pas grave parce qu'on va bientôt se débarrasser de cette lib pourrie, mais je n'en suis pas convaincu…

Quoiqu'il en soit, la version actuelle de NURBS++ compile avec MSVC, Intel, gcc, MinGW et clang. C'est plutôt un exploit, vu l'état du code source. Je crois qu'on est tranquille pour un bon moment.

L'instanciation des singletons

Pour rappel, un singleton est une classe qui ne permet qu'une seule instance de cette classe dans tout le programme. On peut voir ça comme une manière un peu tordue (disons “orientée objet”) de créer une variable globale.

Evidemment, c'est très critiqué dans la communauté C++, tout autant que les variables globales, puisque ça permet d'accéder à ces données à partir de n'importe où… Et l'accès n'est pas toujours thread-safe… Néanmoins, quand c'est bien utilisé, c'est très utile. La règle est donc de ne pas utiliser des singletons pour n'importe quoi.

Dans Metafor, leur usage est très cadré: on utilise des singletons pour stocker par exemple les factories d'éléments, de matériaux, de lois constitutives, de critères de rupture, etc. Ces singletons permettent de lister tous les éléments et d'en ajouter à la volée quand on charge des modules (mtSabca p expl).

On les utilise aussi pour stocker tous les “codes” tels que les anciennes locks TX, RE (on peut voir ça comme des sortes d'énums améliorées et dynamiques). Dans cet usage particulier, le singleton permet de lister par exemple tous ces codes où qu'on soit dans le programme et éventuellement d'enrichir cette liste.

Un troisième exemple est le système de licence qui est aussi géré par un singleton, histoire de pouvoir le questionner à partir de n'importe quelle partie du code sans le transmettre en argument.

Bref, les singletons sont bien utiles et leur suppression n'est pas à l'ordre du jour. Pour les implémenter, à l'époque, je me suis inspiré du “singleton de Meyers” qui consiste à créer un template Singleton<T>. On crée alors un nouveau singleton A en le dérivant de Singleton<A>. Et là, on imagine bien que ça peut faire un peu paniquer certains compilateurs. En effet, quand le compilateur arrive à la définition de la classe A, il a envie d'instancier la classe de base qui dépend d'une classe qui n'est pas encore définie. Ajoutons à ça qu'un Singleton contient une variable statique dont toutes les éventuelles instanciations dans les différentes unités de compilation doivent être rassemblées au link. Dernière difficulté, et pas la moindre: on veut utiliser des bibliothèques dynamiques à gogo. Autrement dit, on aimerait que le compilateur soit suffisamment malin pour nous garder une seule instance parmi toutes nos libs! Faut-il dire qu'évidemment, la plupart du temps, la classe A est elle-même un template?

Le compilateur de Microsoft s'en sort souvent très bien; mais il triche un peu parce qu'il demande au programmeur de spécifier des attributs de classe additionnels (mot clef _declspec(dllimport), _declspec(dllexport)). Finalement, avec MSVC, on peu faire un peu n'importe quoi et ça marche presque toujours (merci Bilou).

Le compilateur d'Intel est assez flexible lui aussi. On peut déclarer tout dans n'importe quel ordre et ça passe.

Pour gcc, c'est un peu plus difficile et pour éviter des “multiply defined” au link, on a décidé d'instancier explicitement tous les singletons dans des fichiers bien définis. Néanmoins, gcc reste assez flexible. En particulier, il ne force pas l'utilisateur à instancier les templates dans un ordre particulier (je parle ici des cas de singletons construits sur des templates). J'ai l'impression que gcc est capable de reconstruire l'ordre logique d'instanciation lui-même. Seul bémol: quand gcc a décidé d'instancier implicitement un template, c'est trop tard pour le modifier. Il y a donc une petit gymnastique a faire pour agencer sont fichier source correctement.

Pour MinGW, c'est encore un peu plus difficile: MinGW est un “gcc windows”, qui doit donc faire avec les DLLs windows… et il a un peu du mal. Comme je l'ai dit, gcc a tendance à instancier un template des qu'il le voit s'il en est capable (sinon, il attend la fin du fichier). Sous Linux, avec gcc, ça ne pose pas trop de problème (sauf si on veut surdéfinir le template par la suite… mais c'est encore gérable).

Le problème, c'est que sous Windows, si gcc instancie un template implicitement, il devrait ajouter un _declspec(dllexport) pour que l'instanciation soit accessible de l'extérieur de la DLL qui est en cours de compilation, … mais il ne le fait pas.

Si on essaye de le faire plus tard, MinGW nous dit que c'est trop tard: le template est déjà instancié et au final, il n'est pas non plus “exporté”. Autrement dit, tout va compiler, la DLL être linkée sans problème mais le template ne pourra pas être appelé de l'extérieur… Une solution naive serait de mettre un _declspec(dllexport) dans “Singleton.h” mais évidemment on ne peut pas: Singleton est un template qui peut potentiellement être utilisé dans une autre bibliothèque où certaines instances sont exportées et d'autres importées…

Comment avais je résolu ça, lors du portage MinGW? “Facile”: il suffit de veiller à ce que les templates ne soient jamais totalement définis quand on tombe sur la définition d'un Singleton. En pratique, c'est un peu tordu mais j'y étais arrivé. En gros, il suffit d'inclure les classes dans l'ordre logique inverse dans le fichier d'instanciation du Singleton: par exemple, si on a class A : Singleton<A>, et bien on écrit:

class A;                                           // declaration minimaliste de A
#include "Singleton.h"                             // definition du template Singleton
template class __declspec(dllexport) Singleton<A>; // instanciation explicite
#include "A.h"                                     // definition de A

L'instanciation explicite va être retardée par MinGW vu que A n'est pas connu. Par contre, le _declspec(dllexport) va être retenu et sera appliqué! Par contre, si on écrit ça:

#include "A.h"                                     // instanciation implicite!
template class __declspec(dllexport) Singleton<A>; // instanciation explicite

MinGW va nous dire d'aller nous faire voir avec notre _declspec(dllexport)

Et clang? et bien ce compilateur se comporte comme gcc mais sans la possibilité de retarder l'instanciation implicite… Autrement dit, si on écrit le code que j'avais modifié pour MinGW, il va nous dire qu'il veut instancier implicitement le template et qu'il n'y arrive pas sans qu'on inclue la définition complète de A avant (erreur de compilation)… c'est donc très ennuyeux.

Après 2 jours d'essais, j'ai réussi à trouver une astuce. C'est pas très beau mais c'est (presque) limité à l'instanciation des Singletons:

#if defined(__MINGW32__)
extern template class __declspec(dllexport) Singleton<A>;
#endif
#include "A.h"
template class SINGLETON_EXPORT Singleton<A>;

SINGLETON_EXPORT vaut _declspec(dllexport) sous Windows sauf pour MinGW où il ne vaut rien.

Ça rime à quoi?

La ligne extern template que j'ajoute au dessus est une aberration au niveau de la syntaxe mais elle permet de marquer la future instanciation du template Singleton<A> comme étant à exporter. Lorsque MinGW décide d'instancier quand même le template (alors qu'on lui a dit qu'il était “extern”), le tour est joué: il a le bon attribut! Le compilateur de Microsoft tolère la ligne extern template mais il génère un warning. J'aurais pu l'enlever mais comme c'est une bidouille MinGW et que le warning est justifié, je préfère que ça reste explicite dans le code; j'ai donc mis la ligne dans un #ifdef.

Nettoyage de warnings

J'ai essayer de supprimer un maximum de warnings.

  • Sous Windows (MSVC) il n'y en a plus un seul! (tout est vert sous Incredibuild!)
  • Sous icpc, je n'ai rien vu (à vérifier),
  • Sous gcc, il reste quelques warnings.
  • Sous MinGW, il reste quelques trucs liés aux nouvelles structures de stockage des données de contact et a l'instanciation des templates d'éléments mal exportés (visiblement ça ne pose pas de problème au runtime.
  • Sous clang, il reste pas mal de trucs que je nettoierai dans un prochain commit.

L'idée à terme serait de compiler Metafor avec l'option “warning=error”. Ce serait dejà possible avec le Visual Studio… à discuter.

boman 2016/09/12 09:22

commit/2016/09_12.txt · Last modified: 2018/05/04 15:54 by boman