6.1 Generación de código de referencias de estructuras de datos Como se ha visto antes, se puede generar código intermedio para asignaciones aritméticas simples, en esas ocasiones los valores básicos eran constantes o variables simples. La traducción al código objeto, requiere que los nombres de variables o de constantes se reemplacen por direcciones de memoria absoluta o bien de registros de activación. En el momento que se genera el código intermedio se pueden insertar estas direcciones. Pero en situaciones donde se requiere de realizar cálculos de dirección para encontrar la dirección real en cuestión, los cálculos deben ser expresados directamente incluso tratándose del código intermedio. Los cálculos se presentan en subíndices de arreglos, campos de registro y referencias de apuntador. Como se puede entender, el dominio del lenguaje de programación es un requisito indispensable para la correcta manipulación de direcciones y apuntadores. 6.2 Generación de código de sentencias de control y expresiones lógicas Si supones que el generador de cogido genera una secuencia con nombres tales como L1, L2, y así sucesivamente. Para la sentencia: If (E) S1 else S2 Se generé el siguiente patrón de código: <code to evaluate E to t!> If_else t1 goto L1 <code for S1> Goto L2 Label L1 <code for S2> Label L2 De la misma manera una sentencia while de la forma while (E) S provocaría que fuera generado el siguiente patrón de código de tres direcciones: label L1 <codeto evaluate E to t1> If_fase t1 goto L2 <code for S> goto L1 label L2 El Código P para la sentencia de control sería del siguiente modo: if ( E ) S1 else S2 se genera el siguiente patrón de código P: - 64 - <code to evaluate E> Fjp L1 <code for S1> Ujp L2 Lab L1 <code for S2> Lab L2 Y para una sentencia como: while ( E ) S se genera el código P: lab L1 <code to evaluate E> Fjp L2 <code for S> Ujp L1 Lab L2 Hace falta notar que todas estas secuencias de código finalizan en la declaración de una etiqueta, a la que bien se le puede llamar etiqueta de salida. Muchos lenguajes de programación proporcionan una construcción adecuada para salir de los ciclos, por ejemplo, el lenguaje C proporciona la sentencia “breack”. 6.3 Generación de código de llamadas de procedimientos y funciones Los requerimientos para las sentencias de representaciones de código de llamas de funciones pueden describirse en términos generales como sigue: En primer lugar, existen dos mecanismos que requieren describirse: la definición de función/procedimiento, conocida también como “declaración” y la llamada de función / procedimiento. Una definición crea un nombre de función, parámetros y código, pero la función no se ejecuta en ese punto. Una llamada crea valores reales para los parámetros o argumentos para la llamada, y realiza un salto un salto hacia el código de la función, el cual se ejecuta entonces y regresa. El ambiente de ejecución en el que ésta tiene lugar no se conoce cuando se crea el código para la función, excepto en su estructura general. Este ambiente de ejecución es construido en parte por el elemento que llama y en parte por el código de la función llamada; esta responsabilidad y la división de ella, forma parte de la secuencia de llamada. El código intermedio para una definición debe incluir una instrucción que marque el inicio o punto de entrada del código para la función y una instrucción que marque el final o punto de retorno de la función. Lo anterior se puede esquematizar de la siguiente forma: instrucción de entrada <código para el cuerpo de la función> instrucción de retorno De la misma manera, una llamada de función debe tener una instrucción que indique el principio del cálculo de los argumentos, en preparación para la llamada, y luego una instrucción de llamada real que indique el punto en que los argumentos han sido construidos y el salto real hacia el código de la función puede tener lugar: - 65 - Instrucción de comienzo de cálculos de argumento <código para calcular los argumentos> Instrucción de llamada. Para diferentes versiones de código intermedio proporcionan diferentes versiones de las cuatro instrucciones agrupadas. En particular a la cantidad de información acerca del ambiente, los parámetros y la función misma que es parte de cada instrucción. 6.4 Generación de código en compiladores comerciales: dos casos de estudio. Los casos de estudio a los que hace referencia el plan de estudios se refiere específicamente al compilador para C de Borland, versión 3.9 para procesador Intel 80X86. El segundo es el compilador para C de Sun versión 2.0 para SparcStation. Para ambos se intenta la salida de ensamblador para los mismos ejemplos de código de tres direcciones y el código P. Utilicemos el ejemplo de la expresión: (x=x+3)+4 Supongamos que la variable X en esta expresión está almacenada localmente en el marco de la pila. El código ensamblador para esta expresión tal como se produce mediante el compilador de Borland V.3.0 para la arquitectura 80X86 es de la siguiente manera: MOV ADD MOV ADD ax , Word [prt bp-2] ax, 3 word ptr [bp-2], ax ax, 4 En el código, el registro de acumulador ax se utiliza como la ubicación temporal principal para el calculo. La ubicación de la variable local x es bp-2, lo que refleja el uso de registros bp (base pointer) como el apuntador de marco y el hecho de que las variables enteras ocupan dos bytes en esta máquina. Para el caso del compilador C de Sun 2.0 para la Sun SparcStations con referencia al mismo ejemplo: (x=x+3)+4 Suponemos que la variable x en esta versión es almacenada localmente en el marco de la pila. El compilador C de Sun produce código ensamblador que es muy similar al del código de Borland: ld add st ld add [%fp+-0x4],%o1 %o1,0x3,%o1] %o1,[%fp+-0x4] [%fp+-0x4], %o2 %o2,0x4,%o3 - 66 - En este código, los nombres de registros comienzan con el símbolo de porcentaje y las constantes comienzan con los caracteres 0x (x=hexadecimal), de manera que por ejemplo, 0x4 es el hexadecimal 4 (lo mismo que el decimal 4). La primera instrucción mueve el valor de x (en la localidad fp-4, porque los enteros son de cuatro bytes de longitud) al registro o1. Hay que notar que las localidades fuente están a la izquierda y las ubicaciones objetivo a la derecha, de modo opuesto a la conversión de la arquitectura 80x86. La segunda instrucción agrega 3 a o1, mientras que la tercera almacena o1 en la localidad de x. Finalmente, de vuelve a cargar el calor de x, esta vez en el registro o2, y se le agrega 4. Colocando el resultado en el registro o3, donde se deja como el valor final de la expresión. - 67 -