Guía de seguimiento dinámico de Solaris

Optimización de llamada final

Cuando una función finaliza llamando a otra función, el compilador puede utilizar la optimización de llamada final, en la que la función a la que se está llamando reutiliza el marco de pila del emisor de la llamada. Este procedimiento se suele usar con mayor frecuencia en la arquitectura SPARC cuando el compilador reutiliza la ventana de registro del emisor de la llamada en la función a la que se está llamando para reducir al mínimo la presión de la ventana de registro.

La presencia de esta optimización provoca que el sondeoreturn de la función que realiza la llamada se active antes que el sondeo entry de la función a la que se ha llamado. Este orden puede provocar cierta confusión. Por ejemplo, si desea registrar todas las funciones a las que se ha llamado desde una determinada función y todas las funciones a las que llama esta función, debe utilizar la siguiente secuencia de comandos:

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

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

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

Sin embargo, foo() termina con una llamada final optimizada, la última función a la que se ha llamado y, por lo tanto, todas las funciones a las que ésta llama no se capturarán. No se puede anular dinámicamente la optimización del núcleo sobre la marcha; además, DTrace no desea participar en una falsedad acerca de cómo está estructurado el código. Por lo tanto, debería ser consciente de cuándo se puede utilizar la optimización de llamada final.

Es probable que la optimización de llamada final se utilice en un código fuente similar al del siguiente ejemplo:

	return (bar());

O en un código fuente similar al del ejemplo siguiente:

	(void) bar();
	return;

Por el contrario, no se pueden optimizar las llamadas a bar() del código fuente de la función que finaliza como el siguiente ejemplo, debido a que la llamada a bar() no es una llamada final:

	bar();
	return (rval);

Puede determinar si se ha efectuado una optimización de llamada final en una llamada mediante la siguiente técnica:

Debido a la arquitectura del conjunto de instrucciones, la optimización de llamada final es mucho más frecuente en los sistemas SPARC que en los sistemas x86. En el siguiente ejemplo, se utiliza mdb para detectar la optimización de llamada final en la función dup() del núcleo:


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

Mientras se ejecuta este comando, ejecute un programa que realice una operación dup(2), por ejemplo, un proceso bash. El comando anterior debería proporcionar una salida similar a la siguiente:


dup+0x10
^C

Ahora, examine la función con 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 salida muestra que dup+0x10 es una llamada a la función fcntl() y no una instrucción ret. Por lo tanto, la llamada a fcntl() es un ejemplo de optimización de llamada final.