Exemple/sieve.c
Le malloc(4*n)
qu'on trouve dans ce fichier devrait
être malloc(8*n)
.
Sans cette correction c'est "normal"
que ce code source provoque une erreur de segmentation en 64 bits.
C'est un bug historique datant de l'époque où ce projet était en 32 bits:
désolé!
char
n'existe pas et char*=int*
).t[i][j]
) mais une l-value
en a au plus une (c'est soit t
soit t[i]
).
Rappel: en C une l-value est ce qu'on peut trouver sous un opérateur
d'assignation ou de pre/post-incrément/décrément.
Dans l'AST C-- du projet, l'assignation est explicitement restreinte
aux l-values x
et x[e]
(où x
est une variable et e
une expression).
Pour les incréments/décréments, l'AST n'est pas précis: vous pouvez
faire la même hypothèse dans votre code.
On m'a fait remarquer à juste titre que la restriction des l-values
est un peu artificielle. En fait, si l'on lit les règles de sémantique
opérationnelle du poly en ignorant (au moins) une remarque informelle,
elles expliquent comment traiter des l-values beaucoup plus générales,
par exemple (x+3*t[i++])[j]
ou simplement t[i][j]
.
Mais cela n'est pas demandé pour votre projet.
%rax
?
C'est la convention d'appel qui doit répondre à cette question, mais je
n'arrive pas à trouver de version convaincante qui garantisse quoi que
ce soit sur les bits de %rax
au delà de la taille de la
valeur retournée. Par conséquent on ne peut faire aucune hypothèse!
Si une fonction retourne un entier 32 bits, alors %eax
est correct mais on ne sait rien sur la moitié haute des bits de
%rax
.
Cela n'est pas si choquant dans la mesure où un vrai compilo connait
les types et va en général manipuler %eax
directement
s'il sait qu'un entier 32 bits a été retourné. Mais vous ne pouvez
pas faire ça dans votre compilo, où on ne "voit" que du 64 bit.
La solution proposée est de considérer que toutes les fonctions
déclarées dans votre fichier renvoient des entiers 64 bits,
et que toutes les autres renvoie du 32 bits, sauf quelques
exceptions comme malloc
.
En fonction de cette heuristique, vous pourrez alors étendre
%eax
sur 64 bits avec l'instruction movslq
.
(Merci à Aliaume Lopez.)
Dans votre compilo la fonction main est une fonction comme une autre, elle peut être déclarée n'importe où dans le fichier, et même pas déclarée du tout. C'est à la liaison que main prend son sens spécifique en tant que point d'entrée du programme.
Par conséquent, je m'attends à ce que votre compilateur produise une sortie correcte sur un fichier sans fonction main, si on l'utilise avec l'option -E.
(Merci à Noël Nadal, pour m'avoir reposé cette question qui est beaucoup ressortie en TP.)
%rsp
sur 16 octets?Oui car cela fait partie de la convention d'appel x86-64 (voir spec, section 3.2.2, page 16)... même si ce point a été oublié dans le poly et la première version de l'aide-mémoire.
Il y a plusieurs moyens d'assurer l'alignement. N'hésitez pas à glaner des astuces sur le web ou à échanger entre vous sur ce point, comme sur les autres aspects très "cambouis" du projet.
Oui! La convention n'est pas seulement là pour pouvoir appeler les fonctions de la librairie standard. Votre objectif est de faire comme dans un vrai compilateur, qui doit produire des fonctions qu'on puisse appeler selon la convention.
stderr
, stdout
...
Si une variable globale apparait dans l'AST et qu'elle n'a pas été définie
dans le code, ce n'est pas une erreur: il faut considérer que la variable
est définie dans la librairie standard.
Typiquement, vous avez besoin de cela pour les std*
.
Pour adresser ces variables globales de la libc, deux solutions:
stderr
et stderr(%rip)
.
La seconde est un mode d'adressage "relatif" qui permet d'obtenir une
instruction assembleur prenant moins de place en mémoire.
Vous pouvez ignorer ces considérations et adopter une solution ou
l'autre.
Pas forcément, vous compilez du C-- et pas du C. La sémantique de C-- est définie en certains endroits où C laisse des comportements non définis. Voir par exemple ici.
(Merci à Laurent Prosperi.)
Aucune, ce sont des synonymes. La version avec un q est l'appelation Intel originale et l'équivalent sans q semble être introduit par GCC.
N'utilisez que jmp
, saut inconditionnel vu en cours.
Cette instruction prend en argument une adresse "absolue", en général
donnée par le biais d'un label.
Contrairement aux apparences jmpq
n'est pas un alias,
mais une variante du précédent permettant de sauter à une adresse
calculée dynamiquement.
Comme d'habitude: utilisez un débogueur, relisez-vous, simplifiez ou reformuler votre test pour mettre le doigt sur le problème, allez dormir, etc.
En dehors de ces raisons habituelles, un problème assez vicieux s'est
manifesté plusieurs fois: si vos instructions assembleur ne sont pas
dans une section text
alors le débogueur ne "voit" plus
rien, les accès aux variables globales de la libc échouent, etc.
C'est vicieux car ce problème syntaxique évident n'est jamais signalé
en tant que tel comme une erreur par le compilateur.
Une technique s'applique directement: le printf, il n'y a pas de honte. Sinon, on a deux autres outils à disposition, mais il faut compiler avec l'option de débuggage (-g, à la compilation et à la liaison). Pour cela:
make clean
puis refaites make
.
Avantage immédiat, vous pouvez maintenant obtenir une backtrace quand
votre compilateur échoue en remontant une exception. Pour cela, il
faut le lancer avec une variable d'environnement bien fichue:
OCAMLRUNPARAM=b ./mcc fichier.c
.
Notez que les backtraces ne sont pas toujours aussi détaillées que
dans d'autres langages, car OCaml optimise les appels terminaux pour
ne pas garder de stack frames inutiles.
Vous pouvez aussi utiliser le débuggeur bytecode ocaml, qui permet
de revenir dans le temps. On lance simplement: ocamldebug ./mcc
fichier.c
. Mode d'emploi ici.