Manuel de suivi dynamique Solaris

Optimisation des appels terminaux

Lorsqu'une fonction finit par en appeler une autre, le compilateur peut s'engager dans une optimisation des appels terminaux, au cours de laquelle la fonction appelée réutilise le cadre de pile du programme appelant. Cette procédure est le plus souvent utilisée dans l'architecture SPARC, où le compilateur réutilise la fenêtre d'enregistrement du programme appelant dans la fonction appelée, afin de réduire la pression dans la fenêtre d'enregistrement.

La présence de cette optimisation provoque le déclenchement de la fonction d'appel de la sonde return avant la sonde entry de la fonction appelée. Ce tri peut s'avérer confus. Par exemple, si vous souhaitez enregistrer toutes les fonctions appelées à partir d'une fonction donnée ainsi que toutes les fonctions appelées par cette même fonction, vous pouvez utiliser le script suivant :

fbt::foo:entry
{
	self->traceme = 1;
}

fbt:::entry
/self->traceme/
{
	printf("called %s", probefunc);
}

fbt::foo:return
/self->traceme/
{
	self->traceme = 0;
}

Toutefois, si foo() se termine par un appel terminal optimisé, la fonction d'appel terminal et par conséquent toutes les fonctions appelées par cette dernière ne seront pas capturées. Il est impossible d'annuler l'optimisation du noyau de manière dynamique à la volée et DTrace ne donne aucune information erronée à propos de la structure du code. Par conséquent, vous devez savoir à quel moment l'optimisation des appels terminaux peut être utilisée.

L'optimisation des appels terminaux est susceptible d'être utilisée dans un code source similaire à l'exemple suivant :

	return (bar());

ou dans un code source similaire à ce qui suit :

	(void) bar();
	return;

À l'inverse, un code source de fonction se terminant comme dans l'exemple suivant ne peut pas avoir son appel à bar() optimisé, car l'appel à bar() n'est pas un appel terminal :

	bar();
	return (rval);

Vous pouvez déterminer si un appel a fait l'objet d'une optimisation d'appels terminaux en ayant recours à la technique suivante :

En raison de l'architecture du jeu d'instructions, l'optimisation d'appels terminaux est beaucoup plus courante sur les systèmes SPARC que sur les systèmes x86. L'exemple suivant utilise mdb pour découvrir l'optimisation d'appels terminaux dans la fonction dup() du noyau :


# dtrace -q -n fbt::dup:return'{printf("%s+0x%x", probefunc, arg0);}'

Lorsque cette commande est en cours d'exécution, exécutez un programme qui effectue un dup(2), tel qu'un processus bash. La commande ci-dessus doit fournir une sortie similaire à l'exemple suivant :


dup+0x10
^C

À présent, étudiez la fonction avec mdb :


# echo "dup::dis" | mdb -k
dup:                            sra       %o0, 0, %o0
dup+4:                          mov       %o7, %g1
dup+8:                          clr       %o2
dup+0xc:                        clr       %o1
dup+0x10:                       call      -0x1278       <fcntl>
dup+0x14:                       mov       %g1, %o7

La sortie montre que dup+0x10 est un appel à la fonction fcntl() et non une instruction ret. Par conséquent, l'appel à fcntl() constitue un exemple d'optimisation d'appels terminaux.