Bonjour,
J'ai déjà déposé ce post sur le forum Visual C++ puisque le problème évoqué porte sur l'optimiseur du Visual; mais le contexte étant orienté assembleur, il n'a pas vraiment trouvé d'écho. J'espère que ça parlera plus aux habitués du forum asm...
Rapidement, ma config: VC++ 6.0 SP5 et PP5, sous XP SP2.
Donc, mon problème est relativement simple pour qui a déjà fait de l'assembleur inline dans le Visual 6 ou .NET: je travaille actuellement sur une application de video temps réel, application pour laquelle je dois optimiser en assembleur un certain nombre de fonctions critiques. Pour ce faire je souhaiterais pouvoir utiliser le maximum de registres processeur à ma disposition, c'est à dire 7 (pas de SSE, 3DNow etc. ici), ce qui inclut EBP; or il se trouve que ce registre fait l'objet d'une utilisation un peu particulière de la part des compilateurs x86, puisqu'il sert fréquemment de pointeur de frame de pile. Pour ceux qui l'ignorent, la frame de pile sert notamment à stocker temporairement les variables automatiques (locales) d'une fonction C/C++, et le pointeur de frame est donc là pour les référencer. Mais il existe par ailleurs une option de compilation qui permet de libérer le registre EBP et oblige le compilateur à utiliser directement ESP à la place pour accéder aux variables locales: c'est moins pratique sur ce plan là, mais ça présente l'intérêt de disposer d'un registre supplémentaire pour implémenter la fonction, ce qui n'est pas du luxe sous IA-32. Ce mode est souvent dénommé FPO (comme Frame Pointer Omission, ou omission du pointeur de frame en bon françois).
Voilà pour la théorie, j'en reviens donc plus concrètement à mon problème: dans le Visual, lorsqu'on active le FPO, le prologue qui initialise EBP et l'épilogue qui le restaure disparaissent bien, sauf que dès qu'on utilise EBP à des fins personnelles dans un bloc __asm de la fonction, ils reviennent aussi sec, et les accès aux variables se font à nouveau par EBP ! J'ai fait le test en essayant toutes les combinaisons possibles de paramètres d'optimisation, mais le problème est resté entier: impossible dans un bloc asm inline d'à la fois avoir accès aux variables locales C/C++, et de pouvoir utiliser librement EBP. Pourtant, si on observe le code asm généré par le compilateur pour des fonctions purement C/C++, avec les mêmes options d'optimisation, on s'aperçoit que le registre EBP est utilisé à outrance comme on voudrait pouvoir le faire dans les blocs asm, et que tous les accès aux variables locales se font directement par ESP...
Comme un exemple vaut 1000 explications, voici une fonction totalement bidon, mais qui a le mérite de mettre en évidence le problème.
Code source de la fonction d'exemple:
Compilée en mode debug standard (sans FPO), ça donne:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 void fpo_test (int titi) { int toto, tutu; __asm { mov eax, titi mov toto, eax mov tutu, eax } }
On a bien un pointeur de frame créé en début de fonction et affecté à EBP.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 push ebp mov ebp, esp sub esp, 8 mov eax, DWORD PTR [ebp+8] ; mov eax, titi mov DWORD PTR [ebp-8], eax ; mov toto, eax mov DWORD PTR [ebp-4], eax ; mov tutu, eax mov esp, ebp pop ebp ret 0
Même fonction, compilée cette fois avec FPO:
Là encore le résultat est conforme aux attentes: EBP a bien disparu et les accès à la pile se font directement au travers de ESP.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 sub esp, 8 mov eax, DWORD PTR [esp+12] ; mov eax, titi mov DWORD PTR [esp+4], eax ; mov toto, eax mov DWORD PTR [esp], eax ; mov tutu, eax add esp, 8 ret 0
Maintenant, comme le but du jeu est je le rappelle de s'approprier EBP, on ajoute une ligne au code source de la fonction initiale:
(EBP n'est volontairement pas préservé sur la pile pour éviter d'alourdir le code d'exemple).
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 void fpo_test (int titi) { int toto, tutu; __asm { xor ebp, ebp // nouvelle instruction mov eax, titi mov toto, eax mov tutu, eax } }
Résultat compilé avec FPO (avec strictement la même config que juste avant), et là, surprise:
On voit que le compilateur a remis EBP en service alors qu'on ne lui a rien demandé...
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 push ebp mov ebp, esp sub esp, 8 xor ebp, ebp mov eax, DWORD PTR [ebp+8] ; mov eax, titi mov DWORD PTR [ebp-8], eax ; mov toto, eax mov DWORD PTR [ebp-4], eax ; mov tutu, eax mov esp, ebp pop ebp ret 0
Voilà, toute idée ou suggestion sera la bienvenue.
Merci de votre aide, et désolé pour la longueur du post
Partager