Prácticas de Procesadores de Lenguaje 2008-2009 1
Generación de código
� Notación
� Generación de código correspondiente a la declaración de variables globales� Ubicación de la acción
� Resultado esperado de la acción
� Esquema de la acción
� Generación de la etiqueta “main:”
� Contenido del fichero ensamblador antes de la traducción de la primera sentencia
� Control de errores en tiempo de ejecución
� Aclaraciones previas a la generación de código de sentencias
� Generación de código para las expresiones de tipo identificador
� Generación de código para las constantes� Introducción
� Constantes lógicas
� Constantes enteras
� Constantes reales
Índice Marina de la CruzAlfonso Ortega
Prácticas de Procesadores de Lenguaje 2008-2009 2
Generación de código
� Generación de código para las operaciones aritméticas� Introducción
� Aritmética entera
� Aritmética real
� Reglas de la gramática para las operaciones aritméticas
� Generación de código para la operación suma con operandos enteros
� Generación de código para la operación suma con operandos reales
� Generación de código para el resto de las operaciones aritméticas binarias
� Generación de código para la negación de un operando entero
� Generación de código para la negación de un operando real
� Generación de código para las operaciones lógicas� Introducción
� Generación de código para la conjunción lógica
� Generación de código para la disyunción lógica
� Generación de código para la negación lógica
Índice
Prácticas de Procesadores de Lenguaje 2008-2009 3
Generación de código
� Generación de código para las comparaciones� Introducción
� Generación de código para las comparaciones de operandos enteros
� Generación de código para la comparación “==“de operandos enteros
� Generación de código para la comparación “!=“ de operandos enteros
� Generación de código para la comparación “<=“de operandos enteros
� Generación de código para la comparación “>=“de operandos enteros
� Generación de código para la comparación “<“ de operandos enteros
� Generación de código para la comparación “>“ de operandos enteros
� Generación de código para las comparaciones de operandos reales
� Generación de código para operaciones de acceso� Introducción
� Diseño de las acciones semánticas
� Generación de código para la liberación de memoria
� Generación de código para indexación de vectores� Introducción
� Vectores de una dimensión
� Vectores de dos dimensiones
Índice
Prácticas de Procesadores de Lenguaje 2008-2009 4
Generación de código
� Generación de código para operaciones de conjuntos� Representación de conjuntos
� Inserción de un elemento en un conjunto
� Vaciado de un conjunto
� Unión e intersección de conjuntos
� Tamaño de un conjunto
� Pertenencia a un conjunto
� Generación de código para sentencias de asignación� Introducción
� Asignación de una expresión a un identificador: esquema
� Asignación de una expresión a un identificador: ejemplo
� Asignación de una expresión al elemento de un vector
� Asignación de una expresión a una operación de acceso
� Reserva de memoria
� Asignación de la dirección de un identificador a otro identificador
Índice
Prácticas de Procesadores de Lenguaje 2008-2009 5
Generación de código
� Generación de código para entrada de datos� Introducción
� Lectura de un identificador
� Lectura de un elemento de vector
� Generación de código para salida de datos� Introducción
� Escritura de una expresión
� Escritura de una colección
� Generación de código para sentencias condicionales� Introducción
� Condicionales simples
� Condicionales compuestas
� Generación de código para sentencias iterativas� Introducción
� Sentencia WHILE
� Sentencia FOR
� Generación de código para sentencias de selección
Índice
Prácticas de Procesadores de Lenguaje 2008-2009 6
Generación de código
� Generación de código para funciones� Convenio de las llamadas
� Ejemplo
� Generación de código para las llamadas a funciones
� Generación de código inicial y final en el cuerpo de una función
� Generación de código para la localización del contenido de los parámetros
� Generación de código para la localización del contenido de las variables locales
� Generación de código para la localización de la dirección de parámetros y variables locales
� Tratamiento de identificadores
Índice
Prácticas de Procesadores de Lenguaje 2008-2009 7
Notación
� En este documento, la representación de las producciones de la gramática del lenguaje de programación ALFA se ajusta a la notación propia de Bison con las siguientes particularidades:
� Los símbolos no terminales se representan en minúsculas
� Los símbolos terminales se representan con TOK_ …
� Los símbolos terminales de un carácter se representan encerrados entre comillas simples
Prácticas de Procesadores de Lenguaje 2008-2009 8
Generación de código correspondiente a la declaración de variables globales
� Cuando termina la compilación de la sección de declaración de variables globales de un programa ALFA, todas las variables globales declaradas están almacenadas en la tabla de símbolos. En ese punto es posible generar el código ensamblador correspondiente a la declaración de las variables (segmento de datos) Por lo tanto, el punto adecuado para escribir el segmento de datos del fichero ensamblador es el indicado por el símbolo � en la producción del axioma:
programa: TOK_MAIN ‘{‘ declaraciones � funciones sentencias ‘}’
Se crea un nuevo no terminal y se añade una producción lambda para dicho símbolo en cuya acción semántica sea generar el código ensamblador correspondiente a la declaración de las variables . Por ejemplo:
programa: TOK_MAIN ‘{‘ declaraciones escritura1 funciones sentencias ‘}’
escritura1: {
/* Tarea adecuada */
}
� Esta acción semántica se puede “aprovechar” para escribir la cabecera del segmento de código, que es un texto fijo.
Ubicación de la acción
Prácticas de Procesadores de Lenguaje 2008-2009 9
Generación de código correspondiente a la declaración de variables globales
Resultado esperado de la acción
main {int x;boolean a;float ** ppe;array int [8] v8;array boolean [3 , 4] u12;set of 20 c;…
Programa ALFA
segment .bss_x resd 1_a resd 1_ppe resd 1_v8 resd 8_u12 resd 12_c resd 21
...
ProgramaNASM
Procesado de la sección declarativa
20-----CONJUNTO-variablec
4
-
-
-
-
columnas
-
-
-
-
-
capacidad
-
8
-
-
-
tamaño
variable
variable
variable
variable
variable
categoría
32-VECTORBOOLEANu12
-1-VECTORINTv8
--3PUNTEROFLOATppe
--1ESCALARBOOLEANa
--1ESCALARINTx
etcfilasdimensiónnivel de indirección
clasetipoclave
Tabla de símbolos
Reducción de la nueva regla
Prácticas de Procesadores de Lenguaje 2008-2009 10
Generación de código correspondiente a la declaración de variables globales
� Según lo visto anteriormente, la acción semántica asociada a la sección declarativa contendría:
� La escritura del segmento de datos bss.
� La escritura de la cabecera del segmento de código.
� Por razones de estructuración, se sugiere escribir una librería que contenga funciones de apoyo a la generación de código. De momento esta librería contendría:
� Una función que a partir de la tabla de símbolos escriba el segmento de datos correspondiente.
� Una función que escriba la cabecera del segmento de código, que es como sigue:
segment .text
global main
extern scan_int, scan_float, scan_boolean
extern print_int, print_float, print_boolean, print_string, print_blank, print_endofline
extern ld_float, afa_malloc, alfa_free
Esquema de la acción
PENDIENTE 1: escritura de segmento .data
Prácticas de Procesadores de Lenguaje 2008-2009 11
Generación de la etiqueta “main:”
� La etiqueta “main:” tiene que ser escrita en el fichero antes de la primera sentenciacorrespondiente al programa principal. Por lo tanto, el punto adecuado es el indicado por el símbolo � en la producción del axioma:
programa: TOK_MAIN ‘{‘ declaraciones escritura1 funciones � sentencias ‘}’
Se crea un nuevo no terminal y se añade una producción lambda para dicho símbolo cuya acción semántica sea escribir en el fichero ensamblador la etiqueta “main:”. Por ejemplo:
programa: TOK_MAIN ‘{‘ declaraciones escritura1 funciones escritura2 sentencias ‘}’
escritura2: {
/* Tarea adecuada */}
Prácticas de Procesadores de Lenguaje 2008-2009 12
Generación de la etiqueta “main:”
� Se puede añadir a la librería que contiene las funciones de apoyo a la generación de código una función que simplemente escriba la etiqueta “main:” en el fichero ensamblador.
� Posteriormente se estudiará en profundidad las tareas que debe realizar el generador de código en relación al no terminal “funciones”.
PENDIENTE 2: salvar registros como primera acción del programa ensamblador. Inicialización variables conjunto.
Prácticas de Procesadores de Lenguaje 2008-2009 13
Contenido del fichero ensamblador antes de la traducción de la primera sentencia
� Según lo visto anteriormente, el contenido del fichero ensamblador antes de la traducción de primera sentencia sería el siguiente:
segment .bss
; declaración de variables según el contenido de la tabla de símbolos
;
segment .data
; declaración de variables inicializadas
;
segment .text
global main
extern scan_int, scan_float, scan_boolean
extern print_int, print_float, print_boolean, print_string, print_blank, print_endofline
extern ld_float, afa_malloc, alfa_free
;
; código correspondiente a la compilación del no terminal “funciones”
;
main:
Prácticas de Procesadores de Lenguaje 2008-2009 14
Control de errores en tiempo de ejecución
� La gestión de errores en tiempo de ejecución requiere que en el proceso de compilación se genere el código ensamblador que comprueba dichos errores.
� El código ensamblador asociado a un control de error puede funcionar de la siguiente manera:
� comprueba la condición de error
� si se cumple la condición de error, se transfiere el flujo de ejecución a una etiqueta en la que se realizan dos acciones:
� se informa al usuario del error ocurrido
� se salta de manera incondicional al final del programa
� Para poder informar de los errores, se tienen que declarar los mensajes en el segmento .data, por ejemplo:
segment .data
mensaje_1 db “índice fuera de rango” , “0”
mensaje_2 db “Intento de inserción de un elemento en un conjunto lleno” , “0”
mensaje_3 db “División por cero” , ”0”
……….
Prácticas de Procesadores de Lenguaje 2008-2009 15
Control de errores en tiempo de ejecución
� En la parte final del programa se puede dedicar una zona para las etiquetas en las que se informa al usuario de un error de ejecución. La estructura de esta zona sería la siguiente:
error_1: push dword mensaje_1
call print_string
add esp, 4
jmp near fin
error_2: push dword mensaje_2
call print_string
add esp, 4
jmp near fin
…..
…..
fin: ret
NOTA: la estructura del código ensamblador podría ser diferente.
Prácticas de Procesadores de Lenguaje 2008-2009 16
Control de errores en tiempo de ejecución
� Obviamente la producción en la que se puede escribir la zona final del programa ensamblador es la regla del axioma.
Prácticas de Procesadores de Lenguaje 2008-2009 17
Aclaraciones previas a la generación de código de sentencias
� El compilador de ALFA genera código ensamblador para una máquina a pila.
� Cuando sea necesario apilar una variable, se apilará siempre su dirección, no su contenido (en NASM, la dirección de una variable es el nombre de la misma)
� Cuando se apile una constante, se apilará su valor.
� La generación de código implica la creación automática de etiquetas (en sentencias condicionales, bucles de control, comparaciones, etc)
Por lo tanto, es necesario un mecanismo que asegure que las etiquetas que se utilicen en el programa ensamblador son únicas.
Un posible mecanismo es usar una variable entera global (etiqueta), inicializada a un valor concreto, por ejemplo 1, y que sea incrementada cada vez que se utilice en la generación de código.
Prácticas de Procesadores de Lenguaje 2008-2009 18
Generación de código para las expresiones de tipo identificador
� La producción de la gramática correspondiente a las expresiones de tipo identificador es la siguiente:
� exp : TOK_IDENTIFICADOR
� En general, la generación de código que se realiza en esta producción consiste en apilar la dirección del identificador. Por lo tanto la evolución de la pila gráficamente sería:
� Es importante recordar que si el identificador corresponde a una variable de clase puntero, la dirección que se apila corresponde a la primera de todas las direcciones que ocupa el identificador en memoria.
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
dirección
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 19
Generación de código para las expresiones de tipo identificador
� El acceso a la dirección del identificador es diferente dependiendo de que el identificador sea:
� Variable global
� Variable local
� Parámetro de función
� Por lo tanto es necesario un mecanismo que permita saber si una variable es local o global.
� Además, si el identificador corresponde a un parámetro de una llamada a función, es necesario realizar las acciones específicas que permitan dejar en la pila el valor del identificador en lugar de su dirección (los parámetros se pasan por valor)
� Si el identificador corresponde a una variable global, el código que se debe generar consiste en apilar la dirección del identificador, que se corresponde con su nombre.
� Los demás casos los estudiaremos posteriormente, después de conocer la generación de código para funciones.
PENDIENTE 3: expresiones de tipo identificador que no son variables globales.
Prácticas de Procesadores de Lenguaje 2008-2009 20
Generación de código para las constantes
� El objetivo de la generación de código para las constantes es apilar el valor de la constante. Hay distintas alternativas para conseguir este objetivo.
� Estudiaremos los tres casos:� Constantes enteras
� Constantes reales
� Constantes lógicas
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 21
Generación de código para las constantes
� Las producciones de la gramática en las que se realiza la inserción en la pila de la constante lógica son las siguientes:
� constante_logica: TOK_TRUE
� constante_logica: TOK_FALSE
� Por ejemplo, la acción semántica de la primera producción, incorporando análisis semántico y generación de código, podría ser:
constante_logica: TOK_TRUE
{
/* análisis semántico */
$$.tipo = BOOLEAN;
$$.direcciones = 0;
/* generación de código */
fprintf(fichero_ensamblador, "; numero_linea %d\n", numero_linea);
fprintf(fichero_ensamblador, "\tpush dword 1\n");
}
Constantes lógicas
Prácticas de Procesadores de Lenguaje 2008-2009 22
Generación de código para las constantes
� La gestión de las constantes enteras tiene la siguiente particularidad: la reducción del no terminal “constante_entera” requiere un tratamiento diferente dependiendo de si aparece en una sección declarativa o si lo hace en una sección de sentencias.
� Las producciones correspondientes a una sección declarativa son las siguientes:
� clase_vector: TOK_ARRAY tipo ‘[‘ constante_entera ‘]’
� clase_vector: TOK_ARRAY tipo ‘[‘ constante_entera ‘,’ constante_entera ‘]’
� clase_conjunto: TOK_SET TOK_OF constante_entera
� Y las producciones relativas a sentencias son:
� caso_estandar: TOK_CASE constante_entera ‘:’ sentencias
� constante: constante_entera
Constantes enteras
Prácticas de Procesadores de Lenguaje 2008-2009 23
Generación de código para las constantes
� En la sección de sentencias, la aparición del no terminal “constante_entera” implica la generación de código para apilar el valor de la constante.
� En la sección declarativa, la aparición del no terminal “constante_entera”no requiere generación de código. Lo que requiere es acceder al valor de la constante, que se supone que está guardado en uno de sus atributos.
� Para poder realizar un tratamiento correcto de las constantes enteras dependiendo de la sección en la que aparezcan, es necesario diseñar un mecanismo que lo permita. Hay múltiples alternativas, una de ellas es utilizar una variable global.
Constantes enteras
Prácticas de Procesadores de Lenguaje 2008-2009 24
Generación de código para las constantes
� La acción semántica asociada a la reducción del no terminal “constante_entera” podría ser la siguiente:
constante_entera: TOK_CONSTANTE_ENTERA
{
/* análisis semántico */
$$.tipo = INT;
$$.direcciones = 0;
$$.valor_entero = $1.valor_entero;
/* generación de código */
if (! en_declaraciones)
{
fprintf(fichero_ensamblador, "; numero_linea %d\n", numero_linea);
fprintf(fichero_ensamblador, "\tpush dword %d\n", $1.valor_entero);
}
}
Constantes enteras
Variable para controlar si la reducción de la regla ocurre en una sección de declaraciones o en una de sentencias.
Esta propagación permite acceder al valor de la constante entera en las producciones de las secciones declarativas en las que aparece el no terminal constante_entera.
Prácticas de Procesadores de Lenguaje 2008-2009 25
Generación de código para las constantes
� La gestión de las constantes reales requiere un tratamiento especial debido a que en NASM sólo pueden aparecer constantes reales en el segmento de datos, y por lo tanto, no es posible apilar directamente el valor de la constante real, es decir, no pueden aparecer instrucciones del tipo “push dword 1.35”
� Para conseguir dejar en la pila el valor de la constante real en la pila, es necesario articular un mecanismo. El que se propone en este curso es el siguiente:
� El analizador morfológico, cuando encuentra en la entrada una constante real, propaga su lexema.
� En el analizador sintáctico, cuando reduce la producción:
constante_real: TOK_CONSTANTE_REAL
se realizan las siguientes tareas:� Extraer del lexema de la constante real un numerador entero y un denominador entero tales que su
división genere el valor de la constante entera. Por ejemplo, si la constante es 12.45, el numerador sería 1245, y el denominador 100.
� Generar código para apilar el valor del numerador.� Generar código para apilar el valor del denominador.� Generar código para invocar a la función de librería “ld_float”.� Generar código para restaurar el puntero de pila.� Generar código para apilar el contenido del registro eax (la función “ld_float” deposita en eax el
resultado de dividir numerador entre denominador, es decir, el valor de la constante real)
Constantes reales
Prácticas de Procesadores de Lenguaje 2008-2009 26
Generación de código para las constantes
� Por lo tanto, la acción semántica asociada a la reducción del no terminal “constante_real” es:
constante_real: TOK_CONSTANTE_REAL
{
/* análisis semántico */
$$.tipo = FLOAT;
$$.direcciones = 0;
/* generación de código */
numerador_constante_real = obtener_numerador($1.lexema);
denominador_constante_real = obtener_denominador($1.lexema);
fprintf(fichero_ensamblador, "\tpush dword %d\n", numerador_constante_real);
fprintf(fichero_ensamblador, "\tpush dword %d\n", denominador_constante_real);
fprintf(fichero_ensamblador, "\tcall ld_float\n");
fprintf(fichero_ensamblador, "\tadd esp, 8\n");
fprintf(fichero_ensamblador, "\tpush eax\n");
}
Constantes reales
Obtener el numerador
Obtener el denominador
Apilar el denominador
Apilar el numerador
Invocar a la función “ld_float”
Restaurar la pila
Apilar eax que contiene el valor de la constante real
Prácticas de Procesadores de Lenguaje 2008-2009 27
Generación de código para las operaciones aritméticas
� Las operaciones aritméticas en NASM utilizan distintos registros y distintos códigos de operación dependiendo del tipo de los operandos.
� Para el estudio de la generación de código correspondiente a las operaciones aritméticas distinguiremos dos casos:
� Aritmética entera: los dos operandos son de tipo entero.
� Aritmética real: los dos operandos son de tipo real, o uno de ellos es de tipo real y el otro de tipo entero.
� Es muy importante no olvidar que, independientemente del tipo de los operandos, la secuencia para la ejecución de una operación aritmética es la siguiente:
� Antes de realizar cualquier operación aritmética, los operandos están en la pila.
� Se extraen los operandos de la pila a los registros adecuados al tipo de los operandos.
� Se realiza la operación aritmética con el código de operación adecuado al tipo de los operandos.
� Se deposita en la pila el resultado de la operación.
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 28
Generación de código para las operaciones aritméticas
� Los registros que se utilizan para aritmética entera, son los de propósito general: eax, edx, etc.
� Los códigos de operación son, entre otros: add, imul, sub, etc. Y se detallarán más adelante.
Aritmética entera
Prácticas de Procesadores de Lenguaje 2008-2009 29
Generación de código para las operaciones aritméticas
� Los registros que se utilizan para aritmética real, son los registros de coma flotante st0, st1, etc. Estos registros se comportan como una pila, siendo st0 la cima de la misma.
� Para realizar cualquier operación de números reales es necesario cargar los operandos en los registros de coma flotante. La carga de estos registros se realiza siempre desde memoria con las siguientes instrucciones NASM.
� fld dword <fuente>Carga en la cima de la pila de reales (st0) un número real de 32 bits almacenado en memoria.
� fild dword <fuente>Lee un número entero de 32 bits almacenado en memoria, lo convierte a real, y lo carga en la cima de la pila de reales (st0)
� Como la carga de los registros de coma flotante se realiza siempre desde memoria, y los operandos siempre están en la pila, entonces, el código correspondiente a operaciones aritméticas con números reales obligatoriamente tendrá que utilizar una memoria intermedia a la que se mueven los operandos desde la pila, y desde la que se cargan los registros de coma flotante.
Aritmética real
Prácticas de Procesadores de Lenguaje 2008-2009 30
Generación de código para las operaciones aritméticas
� Una vez que los operandos están cargados en los registros de coma flotante, los códigos de operación son, entre otros: fadd, fmul, fsub, etc. Y se detallarán más adelante.En general, el resultado de la operación se almacena también en un registro de coma flotante.
� Una vez que se tiene el resultado de la operación en un registro de coma flotante, es necesario apilarlo como refutado de la operación. Para ello, es obligatorio pasar por memoria, ya que no es posible apilar directamente el valor de un registro real.
� La siguiente instrucción carga en memoria el número real que está en la cima de la pila de reales (st0)
fst dword <destino>
� Una vez que el resultado de la suma está en memoria, ya se puede apilar directamente.
� Por último, para resetear la pila de reales después de su uso, se puede utilizar el código de operación finit.
Aritmética real
Prácticas de Procesadores de Lenguaje 2008-2009 31
Generación de código para las operaciones aritméticas
� Como resumen podríamos decir que en aritmética real es obligatorio pasar los operandos por memoria para así poder cargar los registros de coma flotante. De la misma manera, para apilar el resultado de la operación se necesita primero pasar por memoria ese resultado desde los registros de coma flotante.
Aritmética real
exp2
exp1
ANTES DE LA OPERACIÓN
PILA
exp2
exp1
MEMORIA
exp2
exp1
REGISTROS COMA FLOTANTE
DESPUÉS DE LA OPERACIÓN
exp1+exp2
PILA
exp1 + exp2
MEMORIA
exp1 + exp2
REGISTROS COMA FLOTANTE
Apilar el resultadoen la pila�
Mover los operandosa memoria �
Cargar los registrosde coma flotante �
Realizaroperación
�
Mover el resultadoa memoria�
mov fild
fld
fst
fadd
fmul
......
push
Prácticas de Procesadores de Lenguaje 2008-2009 32
Generación de código para las operaciones aritméticas
� La necesidad de pasar los operandos por memoria requiere la declaración de esas posiciones de memoria, es decir, la declaración de las variables que se consideren necesarias.
� Se declararán en el segmento .data las siguientes variables:
� __auxint: esta variable se utiliza como memoria intermedia para almacenar valores de tipo entero antes de cargar dichos valores en los registros de coma flotante. (Obsérvese que el nombre de la variable comienza por 2 guiones bajos)
� __auxfloat: esta variable se utiliza como memoria intermedia para almacenar valores de tipo real antes de cargar dichos valores en los registros de coma flotante. (Obsérvese que el nombre de la variable comienza por 2 guiones bajos)
� La escritura de este segmento se puede realizar inmediatamente después de la escritura del segmento bss y antes de la escritura de la cabecera del segmento de código (ver PENDIENTE en transparencia 7)
Aritmética real
Prácticas de Procesadores de Lenguaje 2008-2009 33
Generación de código para las operaciones aritméticas
� Las reglas de la gramática relativas a las operaciones aritméticas son:� exp : exp ‘+’ exp
� exp : exp ‘-’ exp
� exp : exp ‘/’ exp
� exp : exp ‘*’ exp
� exp : ‘-’ exp
� La generación de código de todas las reglas anteriores comparten la misma estructura básica consistente en distinguir dos casos y generar código adecuado a cada uno de ellos:
� Caso 1: los dos operandos son de tipo entero.
� Caso 2: al menos alguno de los operandos es de tipo real.
Reglas de la gramática para las operaciones aritméticas
Prácticas de Procesadores de Lenguaje 2008-2009 34
Generación de código para las operaciones aritméticas
Generación de código para la operación suma con operandos enteros
GENERACIÓN DE CÓDIGO
; cargar el segundo operando en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar el primer operando en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; realizar la suma y dejar el resultado en eaxadd eax, edx
; apilar el resultadopush dword eax
exp : exp1 ‘+’ exp2
EVOLUCIÓN DE LA PILA
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
exp1+exp2
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 35
Generación de código para las operaciones aritméticas
� La generación de código se puede ubicar directamente en la acción semántica de la regla, o bien programando una función. Para la suma de expresiones de tipo entero, la función tendría el siguiente aspecto:
void gc_suma_enteros(FILE* f, ni_op1, ni_op2)
{
fprintf(f, “; cargar el segundo operando en edx\n”);
fprintf(f, “pop dword edx\n”);
if (ni_op2 > 0)
fprintf(f, “mov dword edx , [edx]\n”);
fprintf(f, “; cargar el primer operando en eax\n”);
fprintf(f,”pop dword eax\n”);
if (ni_op1 > 0)
fprintf(f, “mov dword eax , [eax]\n”);
fprintf(f, “; realizar la suma y dejar el resultado en eax \n”);
fprintf(f, “add eax,edx\n);
fprintf(f, “; apilar el resultado\n”);
fprintf(f, “push dword eax\n”);
return;
}
Generación de código para la operación suma con operandos enteros
Prácticas de Procesadores de Lenguaje 2008-2009 36
Generación de código para las operaciones aritméticas
Generación de código para la operación suma con operandos reales
; cargar el segundo operando en edxpop dword edxSI $3.direcciones>0mov edx , [edx]
; cargar el segundo operando en st0SI $3.tipo == INTmov dword [__auxint] , edxfild dword [__auxint]
SI NOmov dword [__auxfloat] , edxfld dword [__auxfloat]
; cargar el primer operando en eaxpop dword eaxSI $1.direcciones>0mov eax , [eax]
; cargar el primer operando en st0SI $1.tipo == INTmov dword [__auxint] , eaxfild dword [__auxint]
SI NOmov dword [__auxfloat] , eaxfld dword [__auxfloat]
; realizar la sumafadd st1 ; st0 = st0 + st1
; mover el resultado a memoria
fst dword [__auxfloat]
; apilar el resultadopush dword [__auxfloat]
; limpiar los registros de coma flotantefinit
; cargar el segundo operando en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar el segundo operando en st0SI $3.tipo == INTmov dword [__auxint] , edxfild dword [__auxint]
SI NOmov dword [__auxfloat] , edxfld dword [__auxfloat]
; cargar el primer operando en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; cargar el primer operando en st0SI $1.tipo == INTmov dword [__auxint] , eaxfild dword [__auxint]
SI NOmov dword [__auxfloat] , eaxfld dword [__auxfloat]
; realizar la sumafadd st1 ; st0 = st0 + st1
; mover el resultado a memoria
fst dword [__auxfloat]
; apilar el resultadopush dword [__auxfloat]
; limpiar los registros de coma flotantefinit
Prácticas de Procesadores de Lenguaje 2008-2009 37
Generación de código para las operaciones aritméticas
� Las producciones correspondientes al resto de las operaciones aritméticas binarias son las siguientes:
� exp : exp ‘-’ exp
� exp : exp ‘/’ exp
� exp : exp ‘*’ exp
� El código ensamblador correspondiente a las producciones anteriores es similar al de la regla de la suma visto anteriormente. La única diferencia es la instrucción ensamblador que se utilice y las posibles peculiaridades de la misma.
� Para la resta de operandos de tipo entero se puede utilizar la instrucción ensamblador siguiente:
sub eax,edx
que resta al contenido del registro eax el contenido del registro edx y deja el resultado en eax.
Generación de código para el resto de las operaciones aritméticas binarias
Prácticas de Procesadores de Lenguaje 2008-2009 38
Generación de código para las operaciones aritméticas
� Para la resta de operandos de tipo real se puede utilizar la instrucción ensamblador siguiente:
fsub <registro real>
que resta al contenido del registro real st0 el contenido del registro <registro real> y deja el resultado en st0.
Generación de código para el resto de las operaciones aritméticas binarias
Prácticas de Procesadores de Lenguaje 2008-2009 39
Generación de código para las operaciones aritméticas
� Para la división de operandos de tipo entero se puede utilizar la instrucción ensamblador de división entera idiv. Esta instrucción trabaja de la siguiente manera:
� Asume que el dividendo está en la composición de registros edx:eax
� El divisor es el registro que aparece en la instrucción.� El resultado de la división entera se ubica en el registro eax
� Para que el dividendo esté en la composición de registros edx:eax y realizar correctamente la división se tiene que hacer:
� Cargar el divisor en ecx
� Cargar el dividendo en eax
� Extender el dividendo a la composición de registros edx:eax (cdq)
� Realizar la división (idiv ecx)
� Se debe controlar la división por cero según se explicó en el apartado “Control de errores en tiempo de ejecución”
Generación de código para el resto de las operaciones aritméticas binarias
Prácticas de Procesadores de Lenguaje 2008-2009 40
Generación de código para las operaciones aritméticas
� Para la división de operandos de tipo real se puede utilizar la instrucción ensamblador siguiente:
fdiv <registro real>
que divide el contenido del registro real st0 entre el contenido del registro <registro real> y deja el resultado en st0.
� Se debe controlar la división por cero según se explicó en el apartado “Control de errores en tiempo de ejecución”
Generación de código para el resto de las operaciones aritméticas binarias
Prácticas de Procesadores de Lenguaje 2008-2009 41
Generación de código para las operaciones aritméticas
� Para el producto de operandos de tipo entero se puede utilizar la instrucción ensamblador de multiplicación entera imul. Esta instrucción asume que uno de los operandos está en el registro eax, y el otro operando está en el registro que aparece en la instrucción. El resultado se carga en edx:eax, y consideraremos el valor de eaxcomo resultado de la operación.
� Para el producto de operandos de tipo real se puede utilizar la instrucción ensamblador siguiente:
fmul <registro real>
que multiplica el contenido del registro real st0 por el contenido del registro <registro real> y deja el resultado en st0.
Generación de código para el resto de las operaciones aritméticas binarias
Prácticas de Procesadores de Lenguaje 2008-2009 42
Generación de código para las operaciones aritméticas
Generación de código para la negación de un operando entero
GENERACIÓN DE CÓDIGO
; cargar el operando en eaxpop dword eaxSI $2.direcciones>0mov dword eax , [eax]
; realizar la negación. El resultado en eaxneg eax
; apilar el resultadopush dword eax
exp : ‘-’ exp1
EVOLUCIÓN DE LA PILA
ANTES DE LA REDUCCIÓN
exp1
PILA
DESPUÉS DE LA REDUCCIÓN
-exp1
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 43
Generación de código para las operaciones aritméticas
� La negación de un operando de tipo real se puede implementar haciendo uso de la instrucción NASM siguiente:
fchs
que niega el contenido del registro real st0 y deja el resultado también en st0.
Generación de código para la negación de un operando real
Prácticas de Procesadores de Lenguaje 2008-2009 44
Generación de código para las operaciones lógicas
� Las producciones de la gramática correspondientes a las operaciones lógicas son las siguientes:
� exp : exp TOK_AND exp
� exp : exp TOK_OR exp
� exp : ‘!’ exp
� De la misma manera que en las operaciones aritméticas, cuando se reduce una de las anteriores producciones los operandos están en la pila porque han sido reducidos previamente por alguna de las producciones del no terminal “exp”.
� El código ensamblador correspondiente a operaciones lógicas usa la pila con la siguiente secuencia de acciones:
� Desapilar los operandos.
� Realizar la operación lógica correspondiente.
� Apilar el resultado de la operación.
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 45
Generación de código para las operaciones lógicas
Generación de código para la conjunción lógica
GENERACIÓN DE CÓDIGO
; cargar el segundo operando en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar el primer operando en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; realizar la conjunción y dejar el resultado en eaxand eax , edx
; apilar el resultadopush dword eax
exp : exp1 TOK_AND exp2
EVOLUCIÓN DE LA PILA
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
exp1 Y exp2
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 46
Generación de código para las operaciones lógicas
Generación de código para la disyunción lógica
GENERACIÓN DE CÓDIGO
; cargar el segundo operando en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar el primer operando en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; realizar la conjunción y dejar el resultado en eaxor eax , edx
; apilar el resultadopush dword eax
exp : exp1 TOK_OR exp2
EVOLUCIÓN DE LA PILA
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
exp1 O exp2
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 47
Generación de código para las operaciones lógicas
Generación de código para la negación lógica
GENERACIÓN DE CÓDIGO
; cargar el operando en eaxpop dword eaxSI $2.direcciones>0mov dword eax , [eax]
; ver si eax es 0 y en ese caso saltar a negar_falsoor eax , eaxjz near negar_falso#
; cargar 0 en eax (negación de verdadero) y saltar al finalmov dword eax,0jmp near fin_negacion#
; cargar 1 en eax (negación de falso)negar_falso#: mov dword eax,1
; apilar eaxfin_negacion#: push dword eax
exp : ‘!’ exp1
EVOLUCIÓN DE LA PILA
ANTES DE LA REDUCCIÓN
exp1
PILA
DESPUÉS DE LA REDUCCIÓN
NO exp1
PILA
UNICIDAD DE ETIQUETAS
etiqueta ++
Prácticas de Procesadores de Lenguaje 2008-2009 48
Generación de código para las comparaciones
� De la misma manera que en las expresiones aritméticas, en la generación de código ensamblador para las comparaciones se distinguirán dos casos generales:
� Comparaciones con los dos operandos de tipo entero.
� Comparaciones en las que los dos operandos son de tipo real, o uno de ellos es de tipo real y el otro de tipo entero.
� Independientemente del tipo de los operandos, la secuencia para la ejecución de la comparación es la siguiente:
� Los operandos de la comparación están en la pila.
� Se extraen los operandos de la pila a los registros adecuados al tipo de los operandos.
� Se realiza la comparación con el código de operación adecuado al tipo de los operandos.
� Se deposita en la pila el resultado de la comparación (1 si ha terminado con éxito y 0 en el caso contrario)
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 49
Generación de código para las comparaciones
� Las producciones de la gramática correspondientes a las comparaciones son las siguientes:
� comparacion: exp TOK_IGUAL exp
� comparacion: exp TOK_DISTINTO exp
� comparacion: exp TOK_MENORIGUAL exp
� comparacion: exp TOK_MAYORIGUAL exp
� comparacion: exp ‘<‘ exp
� comparacion: exp ‘>’ exp
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 50
Generación de código para las comparaciones
� En general, la implementación en ensamblador de una comparación de operandos de tipo entero se hará de la siguiente manera:
� Suponiendo que los operandos están en los registros eax y edx
� Se utiliza la instrucción:
cmp eax , edx
� Dependiendo del resultado de la comparación se redirige el flujo de ejecución mediante saltos condicionales. Las instrucciones de salto disponibles, para comparaciones de enteros con signo) son las siguientes:
je salta si eax == edx (también jz)
jne salta si eax != edx (también jnz)
jle salta si eax <= edx (también jng)
jge salta si eax >= edx (también jnl)
jl salta si eax < edx (también jnge)
jg salta si eax > edx (también jnle)
� En los destinos de los saltos se procederá a apilar un 0 o un 1 dependiendo del éxito de la comparación.
Generación de código para las comparaciones de operandos enteros
Prácticas de Procesadores de Lenguaje 2008-2009 51
Generación de código para las comparaciones
Generación de código para la comparación “==“ de operandos enteros
GENERACIÓN DE CÓDIGO
; cargar la segunda expresión en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar la primera expresión en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; comparar y apilar el resultadocmp eax, edxje near igual#push dword 0jmp near fin_igual#
igual#: push dword 1fin_igual#:
comparacion : exp1 TOK_IGUALexp2
EVOLUCIÓN DE LA PILA
UNICIDAD DE ETIQUETAS
etiqueta ++
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
0 ó 1
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 52
Generación de código para las comparaciones
Generación de código para la comparación “!=“de operandos enteros
GENERACIÓN DE CÓDIGO
; cargar la segunda expresión en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar la primera expresión en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; comparar y apilar el resultadocmp eax, edxjne near distinto#push dword 0jmp near fin_distinto#
distinto#: push dword 1fin_igual#:
comparacion : exp1 TOK_DISTINTOexp2
EVOLUCIÓN DE LA PILA
UNICIDAD DE ETIQUETAS
etiqueta ++
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
0 ó 1
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 53
Generación de código para las comparaciones
Generación de código para la comparación “<=“de operandos enteros
GENERACIÓN DE CÓDIGO
; cargar la segunda expresión en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar la primera expresión en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; comparar y apilar el resultadocmp eax, edxjle near menorigual#push dword 0jmp near fin_menorigual#
menorigual#: push dword 1fin_menorigual#:
comparacion : exp1 TOK_MENORIGUALexp2
EVOLUCIÓN DE LA PILA
UNICIDAD DE ETIQUETAS
etiqueta ++
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
0 ó 1
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 54
Generación de código para las comparaciones
Generación de código para la comparación “>=“de operandos enteros
GENERACIÓN DE CÓDIGO
; cargar la segunda expresión en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar la primera expresión en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; comparar y apilar el resultadocmp eax, edxjge near mayorigual#push dword 0jmp near fin_mayorigual#
mayorigual#: push dword 1fin_mayorigual#:
comparacion : exp1 TOK_MAYORIGUALexp2
EVOLUCIÓN DE LA PILA
UNICIDAD DE ETIQUETAS
etiqueta ++
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
0 ó 1
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 55
Generación de código para las comparaciones
Generación de código para la comparación “<“de operandos enteros
GENERACIÓN DE CÓDIGO
; cargar la segunda expresión en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar la primera expresión en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; comparar y apilar el resultadocmp eax, edxjl near menor#push dword 0jmp near fin_menor#
menor#: push dword 1fin_menor#:
comparacion : exp1 ‘<‘exp2
EVOLUCIÓN DE LA PILA
UNICIDAD DE ETIQUETAS
etiqueta ++
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
0 ó 1
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 56
Generación de código para las comparaciones
Generación de código para la comparación “>“de operandos enteros
GENERACIÓN DE CÓDIGO
; cargar la segunda expresión en edxpop dword edxSI $3.direcciones>0mov dword edx , [edx]
; cargar la primera expresión en eaxpop dword eaxSI $1.direcciones>0mov dword eax , [eax]
; comparar y apilar el resultadocmp eax, edxjg near mayor#push dword 0jmp near fin_mayor#
mayor#: push dword 1fin_mayor#:
comparacion : exp1 ‘>‘exp2
EVOLUCIÓN DE LA PILA
UNICIDAD DE ETIQUETAS
etiqueta ++
exp2
exp1
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
0 ó 1
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 57
Generación de código para las comparaciones
� Las comparaciones de operandos reales requieren que los operandos estén cargados en los registros reales, de la misma manera que se estudió para las operaciones aritméticas con números reales.
� La carga de los operandos en los registros reales se hace utilizando posiciones de memoria auxiliares como se explicó en las operaciones aritméticas con operandos reales.
� La comparación se hará de la siguiente manera:� Suponiendo que los operandos están ya cargados en los registros st0 y st1
� Se utiliza la instrucción:
fcomp st1 (compara st0 con st1)
La instrucción fcomp no modifica directamente el registro de flags sino que modifica algunos bits del registro de estado del coprocesador. Como los saltos condicionales utilizan el registro de flags, es necesario realizar una transferencia entre ambos registros. Para ello se pueden utilizar las siguientes instrucciones:
fstsw ax carga el registro de estado del coprocesador en ax
sahf carga el registro ah en el registro de flags
Generación de código para las comparaciones de operandos reales
Prácticas de Procesadores de Lenguaje 2008-2009 58
Generación de código para las comparaciones
� Una vez realizada la comparación y actualizado el registro de flags, se pueden utilizar las siguientes instrucciones de salto condicional:
je salta si eax == edx (también jz)
jne salta si eax != edx (también jnz)
jbe salta si eax <= edx (también jna)
jae salta si eax >= edx (también jnb)
jb salta si eax < edx (también jnae)
ja salta si eax > edx (también jnbe)
� En los destinos de los saltos se procederá a apilar un 0 o un 1 dependiendo del éxito de la comparación.
� Por último, se procede a resetear la pila de reales con la instrucción finit.
Generación de código para las comparaciones de operandos reales
Prácticas de Procesadores de Lenguaje 2008-2009 59
Generación de código para operaciones de acceso
� Una operación de acceso significa acceder a una dirección de memoria “apuntada”.
� En términos de generación de código, “acceder” a una dirección de memoria significa apilar dicha dirección.
� El operador de acceso de ALFA es “*”.
� Sólo es correcto aplicar el operador “*” a variables que tengan un nivel de indirección superior a 1.
� El usuario de ALFA es el responsable de realizar la reserva de memoria para las variables declaradas con nivel de indirección mayor que 1, utilizando la palabra reservada malloc.
� El compilador de ALFA no tiene que controlar los casos en los que se declare una variable con nivel de indirección mayor que 1 y se le aplique el operador de acceso sin haber reservado previamente la memoria.
Introducción
4
pe dir
dir
MEMORIA
main{
int * pe;
pe = malloc;*pe = 4;
…
El código ensamblador para la operación de acceso *pe sería:push dword [_pe]
Prácticas de Procesadores de Lenguaje 2008-2009 60
Generación de código para operaciones de acceso
� Ya que el objetivo en términos de generación de código es apilar la dirección de memoria a la que se realiza el acceso, en esta producción, la dirección que se apila es la que está almacenada en la dirección de memoria que ocupa la variable representada por TOK_IDENTIFICADOR. Por lo tanto la instrucción NASM sería:
push dword [_$2.lexema]
Diseño de las acciones semánticas
acceso: ‘*’ TOK_IDENTIFICADOR
_$2.lexema dir
dir
MEMORIA
ANTES DE LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
dir
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 61
Generación de código para operaciones de acceso
� En la producción relativa a la definición recursiva de la operación de acceso, hay que tener en cuenta que en la cima de la pila se encuentra la dirección correspondiente a la reducción del no terminal acceso2. Por lo tanto la generación de código sería:
pop dword eaxpush dword [eax]
Diseño de las acciones semánticas
acceso: ‘*’ accesod
Prácticas de Procesadores de Lenguaje 2008-2009 62
Generación de código para la liberación de memoria
� La generación de código correspondiente a la liberación de memoria requiere la invocación de la función alfa_free de la librería alfalib de la siguiente manera:
� Apilar la dirección de memoria que se quiere liberar, es decir, apilar el contenido de la variable representada por TOK_IDENTIFICADOR.
push dword [_$2.lexema]
� Invocar a la función.
call alfa_free
� Restaurar el puntero de pila.
add esp, 4
liberacion: TOK_FREE TOK_IDENTIFICADOR
Prácticas de Procesadores de Lenguaje 2008-2009 63
Generación de código para indexación de vectores
� Hay dos producciones de la gramática relativas a la operación de indexación de vectores, una para vectores de una dimensión y otra para vectores de dos dimensiones:
� elemento_vector: TOK_IDENTIFICADOR ‘[‘ exp ‘]’
� elemento_vector: TOK_IDENTIFICADOR ‘[‘ exp ‘,’ exp ‘]’
� Los objetivos básicos de la generación de código de las producciones anteriores son:� Generar código para comprobar, en tiempo de ejecución, que los índices están dentro de
los rangos permitidos según el tamaño del vector según se explicó en el apartado “Control de errores en tiempo de ejecución”
� Generar código para dejar en la cima de la pila la dirección del elemento indexado.
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 64
Generación de código para indexación de vectores
Vectores de una dimensión
GENERACIÓN DE CÓDIGO
; Carga del valor del índice en eaxpop dword eaxSI ($3.direcciones>0)mov dword eax , [eax]
; Si el índice es menor que 0, error en tiempo de ejecucióncmp eax,0jl near mensaje_1; Si el índice es mayor de lo permitido , error en tiempo de ejecucióncmp eax, <tamaño del vector –1>jl near mensaje_1
; EN ESTE PUNTO EL ÍNDICE ES CORRECTO...
elemento_vector: TOK_IDENTIFICADOR ‘[’ exp ‘]’
EVOLUCIÓN DE LA PILA
ANTES DE LA REDUCCIÓN
exp
PILA
PUNTO INTERMEDIOEN LA REDUCCIÓN
PILA
� El primer paso consiste en comprobar que el índice está dentro del límite permitido, es decir, entre 0 y el tamaño del vector menos 1 (ambos inclusive)
Prácticas de Procesadores de Lenguaje 2008-2009 65
Generación de código para indexación de vectores
� El código correspondiente a la etiqueta mensaje_1 sería:
mensaje_1:
push dword mensaje_1
call print_string
add esp, 4
jmp near fin
� La etiqueta fin está situada al final del programa, justo antes de la última instrucción ret.
Vectores de una dimensión
Prácticas de Procesadores de Lenguaje 2008-2009 66
Generación de código para indexación de vectores
� El segundo paso consiste en generar código para dejar en la cima de la pila la dirección del elemento indexado. Para ello hay que tener en cuenta las siguientes consideraciones:
� En el segmento de datos .bss se reserva un bloque de memoria para almacenar todos los elementos del vector, y por lo tanto es necesario definir la disposición de dichos elementos dentro del bloque. Lo más razonable es ubicar los elementos por orden creciente de índice, es decir, primero el elemento 0, después el 1, y así sucesivamente hasta el elemento (tamaño-1).
� Una vez que se ha elegido una disposición concreta, las operaciones de indexado deben de ser coherentes con la elección.
� Cada elemento ocupa 4 bytes (porque así se ha hecho la reserva de memoria)
� La dirección de inicio del vector es accesible mediante el nombre del vector.
� Si se ubican los elementos por orden creciente de índice, la dirección del elemento [i] sería la siguiente:
dirección elemento [i] = dirección_inicio_vector + i*4
Vectores de una dimensión
Prácticas de Procesadores de Lenguaje 2008-2009 67
Generación de código para indexación de vectores
� Por ejemplo, la siguiente declaración de un vector de 5 posiciones en un programa ALFA:
array int [5] v;
se traduce en la declaración ensamblador siguiente:
_v resd 5
que reserva en memoria un bloque contiguo de 20 bytes, que se puede interpretar como 5 posiciones de 4 bytes cada una de ellas. Y si se considera que los elementos del vector se disponen dentro del bloque por orden creciente de índice, la memoria tendría el siguiente contenido:
Vectores de una dimensión
v[0]_v
MEMORIA
v[1]_v +4
v[2]
v[3]
_v +8
_v +12
v[4]_v +16
Prácticas de Procesadores de Lenguaje 2008-2009 68
Generación de código para indexación de vectores
Vectores de una dimensión
GENERACIÓN DE CÓDIGO
OPCIÓN 1 OPCIÓN 2
; Cargar en edx la dirección de inicio del vector ; Cargar 4 en edx (nº de bytes de cada elemento del vector)mov dword edx _$1.lexema mov dword edx, 4; Cargar en eax la dirección del elemento indexado ; eax = eax*edx, es decir, eax = eax*4lea eax, [edx + eax*4] imul edx; Apilar la dirección del elemento indexado ; Cargar en edx la dirección de inicio del vectorpush dword eax mov dword edx _$1.lexema
; Cargar en eax la dirección del elemento indexadoadd eax, edx; Apilar la dirección del elemento indexadopush dword eax
EVOLUCIÓN DE LA PILA
PUNTO INTERMEDIOEN LA REDUCCIÓN
PILA
DESPUÉS DE LA REDUCCIÓN
dirección
PILAEl registro eax contiene el valor del índice como consecuencia de la comprobación de la
validez del valor del índice.
Prácticas de Procesadores de Lenguaje 2008-2009 69
Generación de código para indexación de vectores
Vectores de dos dimensiones
elemento_vector: TOK_IDENTIFICADOR ‘[’ exp1 ‘,’ exp2 ‘]’
� La generación de código para la indexación de vectores de dos dimensiones es similar a la correspondiente a vectores de una dimensión.
� En primer lugar se genera código para comprobar que los índices están dentro de los rangos permitidos. En el caso de vectores de una dimensión sólo es necesario comprobar la validez de un índice mientras que si el vector es de dos dimensiones, la comprobación se extiende a los dos índices.
� La idea básica del segundo paso, generación de código para dejar en la cima de la pila la dirección del elemento indexado, es la siguiente: en el segmento de datos .bssse reserva un bloque de memoria para almacenar todos los elementos del vector, y por lo tanto es necesario definir la disposición de dichos elementos dentro del bloque para que las operaciones de indexación sean correctas. Las dos disposiciones más generales son:
� Disposición por filas: en el bloque se sitúan primero los elementos de la primera fila, después los de la segunda, y así sucesivamente.
� Disposición por columnas: dentro del bloque, se almacenan en primer lugar los elementos de la primera columna, después los de la segunda, y así sucesivamente.
Una vez que se ha elegido una disposición concreta, las operaciones de indexado deben de ser coherentes con la elección.
Prácticas de Procesadores de Lenguaje 2008-2009 70
Generación de código para indexación de vectores
� Por ejemplo, si se utiliza una disposición por filas, el elemento [f,j] donde f es la fila, y cla columna, está en la siguiente dirección de memoria:
Dirección elemento [f,c] = dirección_inicio_vector
+
(f * numero_columnas + c) * 4
� Si se utiliza una disposición por columnas, el elemento [f,j] donde f es la fila, y c la columna, está en la siguiente dirección de memoria:
Dirección elemento [f,c] = dirección_inicio_vector
+
(c* numero_filas + f) * 4
Vectores de dos dimensiones
Prácticas de Procesadores de Lenguaje 2008-2009 71
Generación de código para indexación de vectores
� Por ejemplo, la siguiente declaración de un vector de dos dimensiones, de 3 filas y 4 columnas en un programa ALFA:
array int [3,4] z;
se traduce en la declaración ensamblador siguiente:
_z resd 12
que reserva en memoria un bloque contiguo de 48 bytes, que se puede interpretar como 12 posiciones de 4 bytes cada una de ellas. Si se considera una disposición por filas de los elementos del vector, la memoria tendría el contenido que se muestra en la figura adjunta.
Vectores de dos dimensiones
v[0,0]_v
MEMORIA
v[0,1]_v +4
v[0,2]
v[0,3]
_v +8
_v +12
v[1,0]_v +16
v[1,1]_v +20
v[1,2]_v +24
v[1,3]_v +28
v[2,0]_v +32
v[2,1]_v +36
v[2,2]_v +40
v[2,3]_v +44
1ª fila
2ª fila
3ª fila
Prácticas de Procesadores de Lenguaje 2008-2009 72
Generación de código para operaciones de conjuntos
� Una variable de tipo conjunto se puede representar en el segmento .bss como un bloque de memoria con tantas posiciones de 32 bits como capacidad máxima tenga el conjunto más una posición extra en la que se almacena el número de elementos del conjunto.
� Por ejemplo, la declaración ALFA set of 4 c se transforma en una declaración en el segmento .bbs del tipo _c resd 5 que representa un bloque de memoria de 5 posiciones de 32 bits.
Representación de conjuntos
# de elementos_c
MEMORIA
elemento 1_c + 4
elemento 2
elemento 3
_c + 8
_c + 12
elemento 4_c + 16
� En la primera posición del bloque se almacena el número de elementos del conjunto. El acceso a esta primera posición se hace a través del nombre de la variable.
� En la segunda posición del bloque se almacena el primer elemento del conjunto, en la tercera posición el segundo, y así sucesivamente.
Prácticas de Procesadores de Lenguaje 2008-2009 73
Generación de código para operaciones de conjuntos
� El número de elementos del conjunto varía en tiempo de ejecución. Inicialmente vale 0 y se incrementa en uno con cada operación de inserción de un elemento nuevo en el conjunto (operación add).
� Para cada variable de tipo conjunto que se declare en un programa ALFA, es necesario inicializar a 0 la posición de memoria en la que se almacena su número de elementos. Esta inicialización se debe hacer antes de la primera sentencia del programa. Se puede realizar inmediatamente después de la etiqueta “main”, en la acción semántica del nuevo no terminal escritura2 (ver PENDIENTE 2)
� Para implementar la inicialización descrita en el punto anterior, se puede escribir una función que recorra la tabla de símbolos y que para cada variable de tipo conjunto genere la instrucción ensamblador que mueve un 0 a la posición de memoria que almacena el número de elementos del conjunto.Por ejemplo:
set of 45 a; � mov dword [_a] , 0
set of 20 b; � mov dword [_b] , 0
Representación de conjuntos
Prácticas de Procesadores de Lenguaje 2008-2009 74
Generación de código para operaciones de conjuntos
� La producción de la gramática relativa a la operación de inserción de un elemento en un conjunto es la siguiente:
� operacion_conjunto: TOK_ADD ‘(’ exp ‘,’ TOK_IDENTIFICADOR ‘)’
� Los objetivos básicos de la generación de código de la producción anterior son:� Generar código para comprobar, en tiempo de ejecución, que el conjunto dispone de
espacio para insertar un nuevo elemento según se explicó en el apartado “Control de errores en tiempo de ejecución”
� Generar código para hacer la inserción efectiva, es decir, mover el valor de la expresión a la primera posición libre del conjuto.
Inserción de un elemento en un conjunto
Prácticas de Procesadores de Lenguaje 2008-2009 75
Generación de código para operaciones de conjuntos
� La producción de la gramática relativa a la operación de vaciado de un conjunto es la siguiente:
� operacion_conjunto: TOK_CLEAR ‘(’ TOK_IDENTIFICADOR ‘)’
� La generación de código asociada a la operación de vaciado de un conjunto consiste simplemente en establecer a 0 el número de elementos del conjunto. Recordar que el número de elementos del conjunto se almacena en la primera posición de memoria del bloque que ocupa el conjunto, y que el acceso a esta primera posición se realiza directamente con el nombre del conjunto.
Vaciado de un conjunto
Prácticas de Procesadores de Lenguaje 2008-2009 76
Generación de código para operaciones de conjuntos
� Las producciones de la gramática relativas a las operaciones de unión e intersección de conjuntos son las siguientes:
� operacion_conjunto: TOK_UNION ‘(’ TOK_IDENTIFICADOR ‘,‘ TOK_IDENTIFICADOR ‘,’
TOK_IDENTIFICADOR ‘)’
� operacion_conjunto: TOK_INTERSECTION ‘(’ TOK_IDENTIFICADOR ‘,‘ TOK_IDENTIFICADOR ‘,’
TOK_IDENTIFICADOR ‘)’
� La generación de código asociada a cada una de las dos operaciones tiene como objetivo realizar de manera efectiva la operación concreta.
� Por ejemplo, para la unión de conjuntos, el código ensamblador tiene que:� Almacenar en el bloque de memoria asignado al conjunto destino la unión de los otros dos
conjuntos que aparecen en la instrucción, entendiendo como unión la definición habitual de unión de conjuntos en matemáticas. Evidentemente el contenido previo del conjunto destino se pierde.
� Actualizar el tamaño del conjunto destino para que sea acorde con su número de elementos.
Unión e intersección de conjuntos
Prácticas de Procesadores de Lenguaje 2008-2009 77
Generación de código para operaciones de conjuntos
� La producción de la gramática relativa al tamaño de un conjunto es la siguiente:
� exp: TOK_SIZE ‘(’ TOK_IDENTIFICADOR ‘)’
� La generación de código asociada al tamaño de un conjunto consiste en depositar en la pila el tamaño del conjunto. Recordar que el número de elementos del conjunto se almacena en la primera posición de memoria del bloque que ocupa el conjunto, y que el acceso a esta primera posición se realiza directamente con el nombre del conjunto.
Tamaño de un conjunto
Prácticas de Procesadores de Lenguaje 2008-2009 78
Generación de código para operaciones de conjuntos
� La producción de la gramática relativa a la función de pertenencia a un conjunto es la siguiente:
� exp: TOK_CONTAINS ‘(’ exp ‘,’ TOK_IDENTIFICADOR ‘)’
� La generación de código asociada a la función de pertenencia a un conjunto consiste en recorrer el conjunto buscando el valor de la expresión, y depositar en la pila el valor 1 (true) si se ha encontrado el valor, o 0 (false) si no se ha encontrado.
� Es importante recordar que antes de reducirse esta producción, en la cima de la pila se encuentra lo que se haya depositado en la reducción del no terminal exp.
Pertenencia a un conjunto
Prácticas de Procesadores de Lenguaje 2008-2009 79
Generación de código para sentencias de asignación
� Las producciones de las sentencias de asignación son: � asignacion : TOK_IDENTIFICADOR ‘=’ exp
� asignacion : elemento_vector ‘=’ exp
� asignacion : acceso ‘=’ exp
� asignacion : TOK_IDENTIFICADOR ‘=’ TOK_MALLOC
� asignacion : TOK_IDENTIFICADOR ‘=’ ‘&’ TOK_IDENTIFICADOR
� Básicamente, la generación de código de las producciones anteriores tiene como objetivo escribir el código ensamblador que hace efectiva la asignación. Es decir, si por ejemplo la sentencia es una asignación de una constante a un identificador, la generación de código deberá producir las instrucciones en ensamblador que cargan el valor de la constante en la posición de memoria que ocupa el identificador.
� Las características de las partes izquierda y derecha de la asignación, determinan el conjunto de instrucciones en ensamblador que realizan de manera efectiva la asignación.
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 80
Generación de código para sentencias de asignación
Asignación de una expresión a un identificador: esquema
GENERACIÓN DE CÓDIGO
; Cargar en eax la parte derecha de la asignaciónpop dword eax
; Nivel de indirección igual a 1 en la parte izquierda y; 0 en la parte derecha
mov dword [_$1.lexema] , eax
; Nivel de indirección igual a ambos lados de la; asignación
mov dword eax, [eax]mov dword [_$1.lexema] , eax
asignacion: TOK_IDENTIFICADOR ‘=’ exp
EVOLUCIÓN DE LA PILA
ANTES DE LA REDUCCIÓN
exp
PILA
DESPUÉS DE LA REDUCCIÓN
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 81
Generación de código para sentencias de asignación
Asignación de una expresión a un identificador: ejemplo
...int ** ppe1, ppe2;...ppe1 = ppe2;...
Valor entero
dir2 dir3
dir3
dir2ppe1
MEMORIA
Valor entero
dir4 dir5
dir5
dir4ppe2
ANTES DE LA REDUCCIÓN
ppe2
PILA
eax
eax
ppe2
eax
dir4
Valor entero
dir2 dir3
dir3
dir4ppe1
MEMORIA
Valor entero
dir4 dir5
dir5
dir4ppe2
1
2
3
PROCESO DE REDUCCIÓN EN 3 PASOS
1. pop eax2. mov dword eax, [eax]3. mov dword [_$1.lexema] , eax
Prácticas de Procesadores de Lenguaje 2008-2009 82
Generación de código para sentencias de asignación
Asignación de una expresión al elemento de un vector
GENERACIÓN DE CÓDIGO
; Cargar en eax la parte derecha de la asignaciónpop dword eax
; Cargar en edx la parte izquierda de la asignaciónpop dword edx
SI ($3.direcciones == 1)mov dword eax, [eax]
mov dword [edx] , eax
asignacion: elemento_vector ‘=’ exp
EVOLUCIÓN DE LA PILA
DESPUÉS DE LA REDUCCIÓN
PILA
ANTES DE LA REDUCCIÓN
elem_vect
PILA
exp
� En este tipo de asignación, la parte izquierda siempre tiene nivel de indirección igual a 1 (recordar que en ALFA no están permitidos vectores de punteros)
Prácticas de Procesadores de Lenguaje 2008-2009 83
Generación de código para sentencias de asignación
Asignación de una expresión a una operación de acceso
GENERACIÓN DE CÓDIGO
; Cargar en eax la parte derecha de la asignaciónpop dword eax
; Cargar en edx la parte izquierda de la asignaciónpop dword edx
SI ($3.direcciones >0)mov dword eax, [eax]
mov dword [edx] , eax
asignacion: acceso ‘=’ exp
EVOLUCIÓN DE LA PILA
DESPUÉS DE LA REDUCCIÓN
PILA
ANTES DE LA REDUCCIÓN
dir_acceso
PILA
exp
� Se distinguen dos casos en función de los niveles de indirección de las partes izquierda y derecha de la asignación.
Prácticas de Procesadores de Lenguaje 2008-2009 84
Generación de código para sentencias de asignación
Reserva de memoria
asignacion: TOK_IDENTIFICADOR ‘=’ TOK_MALLOC
� Una vez realizadas las comprobaciones semánticas oportunas, que consisten en verificar que el identificador está declarado y que es de clase puntero, la generación de código de esta sentencia de asignación consiste básicamente en:
� Realizar la reserva de memoria invocando a la función alfa_malloc de la librería alfalib. Esta función no tiene parámetros de entrada y devuelve en “eax” la dirección de memoria reservada.
call alfa_malloc
� Hacer efectiva la asignación moviendo a la posición de memoria del identificador la nueva dirección reservada (contenido de eax)
mov dword [_$1.lexema] , eax
� El contenido de la pila es irrelevante para el planteamiento de la generación de código de esta producción de asignación.
Prácticas de Procesadores de Lenguaje 2008-2009 85
Generación de código para sentencias de asignación
Asignación de la dirección de un identificador a otro identificador
asignacion: TOK_IDENTIFICADOR ‘=’ ‘&’ TOK_IDENTIFICADOR
� Una vez realizadas las comprobaciones semánticas oportunas, que básicamente consiste en comprobar que los identificadores que aparecen a ambos lados de la asignación corresponden a variables declaradas con las siguientes características:
� Son del mismo tipo.
� Ninguna de las dos partes de la asignación es de clase vector, conjunto o de categoría función.
� El nivel de indirección del identificador de la parte izquierda es mayor que 1 y además es igual a 1 más el nivel de indirección del identificador de la parte derecha
� La generación de código consiste en hacer efectiva la asignación moviendo a la posición de memoria del identificador de la parte izquierda la dirección del identificador de la parte derecha.
mov dword [_$1.lexema] , _$4.lexema
� El contenido de la pila es irrelevante para el planteamiento de la generación de código de esta producción de asignación.
Prácticas de Procesadores de Lenguaje 2008-2009 86
Generación de código para entrada de datos
� El lenguaje de programación ALFA tiene dos sentencias para la entrada de datos. Las producciones de la gramática para dichas sentencias son:
� lectura: TOK_SCANF TOK_IDENTIFICADOR
� lectura: TOK_SCANF elemento_vector
� En las acciones semánticas de las anteriores producciones, además de realizar las comprobaciones semánticas oportunas, se hace uso de la librería alfalib de la siguiente manera:
� Si la lectura corresponde a un dato de tipo entero, se invoca a la función scan_int.
� Si la lectura corresponde a un dato de tipo real, se invoca a la función scan_float.
� Si la lectura corresponde a un dato de tipo lógico, se invoca a la función scan_boolean.
� Las funciones scan_int, scan_float y scan_boolean tienen como parámetro de entrada la dirección de memoria que será destino del dato leído, por lo tanto, antes de invocar a una de ellas es necesario apilar la dirección adecuada.
� Después de la invocación se restaura la pila (manipulando el puntero de pila o desapilando el parámetro apilado previamente)
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 87
Generación de código para entrada de datos
Lectura de un identificador
lectura: TOK_SCANF TOK_IDENTIFICADOR
� En primer lugar, se prepara la pila para la llamada a la función correspondiente de la librería alfalib, apilando la dirección de memoria que será destino del dato leído:
push dword _$2.lexema
� El segundo paso consiste en invocar a la rutina de lectura adecuada al tipo del identificador:
call scan_int ; si el identificador es de tipo entero
call scan_float ; si el identificador es de tipo real
call scan_boolean ; si el identificador es de tipo lógico
� Por último se restaura el puntero de pila:
add esp, 4
PENDIENTE 4: el identificador puede ser variable local, global o parámetro.
Prácticas de Procesadores de Lenguaje 2008-2009 88
Generación de código para entrada de datos
Lectura de un elemento de vector
lectura: TOK_SCANF <elemento_vector>
� La única diferencia entre la generación de código de esta producción y la anterior es que cuando se reduce esta regla, en la cima de la pila se encuentra la dirección del elemento del vector, y por lo tanto, no es necesario apilarla.
� La invocación de la rutina de lectura adecuada al tipo del vector y la restauración de la pila después de la invocación, se realiza de manera similar a la producción anterior.
Prácticas de Procesadores de Lenguaje 2008-2009 89
Generación de código para salida de datos
� El lenguaje de programación ALFA tiene una sentencia para escritura de expresiones. La producción correspondiente es:
� escritura: TOK_PRINTF exp
� También hay una sentencia para escribir datos de tipo colección, y su regla es la siguiente:
� escritura: TOK_CPRINTF TOK_IDENTIFICADOR
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 90
Generación de código para salida de datos
� En la acción semántica de la producción correspondiente a la escritura de una expresión, además de comprobar que la expresión tiene nivel de indirección 0 ó 1, para la generación de código, se hace uso de la librería alfalib de la siguiente manera:
� Si la expresión es de tipo entero, se invoca a la función print_int.
� Si la expresión es de tipo real, se invoca a la función print_float.
� Si la expresión es de tipo lógico, se invoca a la función print_boolean.
� Las funciones print_int, print_float y print_boolean tienen un único parámetro que corresponde al VALOR de salida, por lo tanto, antes de invocar a una de ellas es necesario apilar dicho valor.
� Después de la invocación se restaura la pila (manipulando el puntero de pila o desapilando el parámetro apilado previamente)
� Una vez escrito el valor de salida, es conveniente realizar un salto de línea. La librería alfalibtambién proporciona la función print_endofline que escribe un salto de línea en la salida estándar. Esta función no tiene parámetros.
� Antes de reducir la producción, en la cima de la pila se encuentra el resultado de la reducción del no terminal exp.
Escritura de una expresión
Prácticas de Procesadores de Lenguaje 2008-2009 91
Generación de código para salida de datos
Escritura de una expresión
escritura: TOK_PRINTF exp
GENERACIÓN DE CÓDIGO
; Acceso al valor de exp si es distinto de constanteSI $2.direcciones == 1pop dword eaxmov dword eax , [eax]push dword eax
; Si la expresión es de tipo enterocall print_int; Si la expresión es de tipo realcall print_float; Si la expresión es de tipo lógicocall print_boolean
; Restauración del puntero de pilaadd esp, 4
; Salto de líneacall print_endofnile
EVOLUCIÓN DE LA PILA
ANTES DE LA REDUCCIÓN
exp
PILA
DESPUÉS DE LA REDUCCIÓN
PILA
Prácticas de Procesadores de Lenguaje 2008-2009 92
Generación de código para salida de datos
� En la descripción de la semántica del lenguaje ALFA, se especifica el formato para la escritura de colecciones.
� La generación de código de la producción correspondiente a la escritura de una colección hace uso de las siguientes funciones de la librería alfalib:
� Si el tipo básico de la colección es entero, para imprimir cada uno de los elementos se invoca a la función print_int. No olvidar que los conjuntos por defecto son de tipo básico entero, mientras que los elementos de los vectores pueden ser de cualquiera de los tres tipos básicos.
� Si el tipo básico de la colección es float, para imprimir cada uno de los elementos se invoca a la función print_float.
� Si el tipo básico de la colección es boolean, para imprimir cada uno de los elementos se invoca a la función print_boolean.
� Para separar los elementos de la colección se utiliza la función print_blank. Esta función no tiene parámetros.
� Para separar las filas de los vectores de dos dimensiones se utiliza la función print_endofline. Esta función tampoco tiene parámetros.
� Una vez finalizada la escritura de la colección, es conveniente realizar un salto de línea con la función print_endofline.
Escritura de una colección
Prácticas de Procesadores de Lenguaje 2008-2009 93
Generación de código para sentencias condicionales
� El lenguaje de programación ALFA tiene dos formatos de sentencia condicional descritos en las siguientes producciones:
� condicional : TOK_IF ‘(‘ exp ‘)’ ‘{‘ sentencias ‘}’� condicional : TOK_IF ‘(‘ exp ‘)’ ‘{‘ sentencias ‘}’ TOK_ELSE ‘{‘ sentencias ‘}’
� La generación de código de las sentencias condicionales tiene una característica particular que hasta ahora no se ha presentado en el desarrollo del compilador. La bifurcación del flujo de ejecución en función del valor de la condición requiere “recordar” las etiquetas de salto. En concreto, en la primera producción, una vez procesado el no terminal “exp”, si su valor es VERDADERO, el flujo de ejecución continúa por las instrucciones correspondientes a la traducción del no terminal “sentencias”, pero si su valor es FALSO, el flujo debe saltar a una etiqueta inmediatamente posterior a dicho bloque de instrucciones. El punto adecuado para escribir en ensamblador la instrucción de salto se encuentra antes “sentencias”, pero la escritura de la propia etiqueta a la que se salta sólo se puede hacer después de “sentencias”. Por lo tanto, es necesario un mecanismo para asegurar que se usa el mismo nombre de etiqueta en los dos puntos en los que se hace uso de ella.
� Un posible mecanismo para “recordar” etiquetas consiste en:� Modificar la gramática de manera adecuada.� Añadir un nuevo atributo semántico que permita propagar etiquetas.
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 94
Generación de código para sentencias condicionales
Condicionales simples
exp
PILA
pop eaxmov eax, [eax] ; si nivel de indirección exp>0cmp eax, 0je near fin_si#
;; exp;
;; sentencias;
fin_si#:
Estos dos bloques de código ensamblador se generan en momentos diferentes pero hacen referencia a una misma etiqueta (fin_si#)
condicional : TOK_IF ‘(’ exp ‘)’ ‘{’ sentencias ‘}’
Prácticas de Procesadores de Lenguaje 2008-2009 95
MODIFICACIÓN DE LA GRAMÁTICA
if_exp: TOK_IF ‘(’ exp ‘)’ ‘{’
condicional: if_exp sentencias ’}’
Generación de código para sentencias condicionales
Condicionales simples
Generación de código
fin_si#: ; # es $1.etiqueta
Comprobaciones semánticasComprobar que $3.tipo == BOOLEANComprobar que $3.direcciones == 0/1
Reserva de etiqueta (#)$$.etiqueta = etiqueta++
Generación de códigopop eaxmov eax , [eax] ; Si nivel de indirección de exp == 1cmp eax, 0je near fin_si# ; # es $$.etiqueta
condicional : TOK_IF ‘(’ exp ‘)’ ‘{’ sentencias ‘}’
Prácticas de Procesadores de Lenguaje 2008-2009 96
pop eaxmov eax, [eax] ; si nivel de indirección exp>0cmp eax, 0je near fin_si#
;; exp;
;; sentencias;
jmp near fin_sino#fin_si#:
fin_sino#:
;; sentencias;
condicional: TOK_IF ‘(’ exp ‘)’ ‘{’ sentencias ‘}’ TOK_ELSE ‘{’ sentencias ‘}’
Generación de código para sentencias condicionales
Condicionales compuestas
exp
PILA
Estos tres bloques de código ensamblador se generan en momentos diferentes pero hacen referencia a las mismas etiquetas (fin_si# y fin_sino#)
Prácticas de Procesadores de Lenguaje 2008-2009 97
Generación de código para sentencias condicionales
Condicionales compuestas
Propagación de atributos$$.etiqueta = $1.etiqueta
Generación de código
jmp near fin_sino# ; # es $1.etiquetafin_si#: ; # es $1.etiqueta
Comprobaciones semánticasComprobar que $3.tipo == BOOLEANComprobar que $3.direcciones == 0/1
Reserva de etiqueta (#)$$.etiqueta = etiqueta++
Generación de códigopop eaxmov eax , [eax] ; Si nivel de indirección de exp == 1cmp eax, 0je near fin_si# ; # es $$.etiqueta
MODIFICACIÓN DE LA GRAMÁTICA
if_exp: TOK_IF ‘(’ exp ‘)’ ‘{’
if_exp_sentencias : if_exp sentencias
condicional : if_exp_sentencias TOK_ELSE ‘{‘ sentencias ‘}’
Generación de código
fin_sino#: ; # es $1.etiqueta
condicional: TOK_IF ‘(’ exp ‘)’ ‘{’ sentencias ‘}’ TOK_ELSE ‘{’ sentencias ‘}’
Prácticas de Procesadores de Lenguaje 2008-2009 98
Generación de código para sentencias iterativas
� El lenguaje de programación ALFA tiene dos sentencias iterativas cuya sintaxis se describe en las siguientes producciones:
� bucle: TOK_WHILE ‘(‘ exp ‘)’ ‘{’ sentencias ‘}’
� bucle: TOK_FOR ‘(’ TOK_IDENTIFICADOR '=‘ exp ‘;’ exp ‘)’ ‘{’ sentencias ‘}‘
� La traducción a ensamblador de las sentencias iterativas tiene características similares a la traducción de sentencias condicionales.La idea básica es la necesidad de implementar un mecanismo que permita “recordar” etiquetas.
Introducción
Prácticas de Procesadores de Lenguaje 2008-2009 99
bucle: TOK_WHILE ‘(’ exp ‘)’ ‘{’ sentencias ‘}’
Generación de código para sentencias iterativas
Sentencia WHILE
exp
PILA
Estos tres bloques de código ensamblador se generan en momentos diferentes pero hacen referencia a las mismas etiquetas (inicio_while# y fin_while#)
pop eaxmov eax, [eax] ; si nivel de indirección exp>0cmp eax, 0je near fin_while#
;; exp;
;; sentencias;
jmp near inicio_while#fin_while#:
inicio_while#:
Prácticas de Procesadores de Lenguaje 2008-2009 100
bucle: TOK_WHILE ‘(’ exp ‘)’ ‘{’ sentencias ‘}’
Generación de código para sentencias iterativas
Sentencia WHILE
Reserva de etiqueta (#)$$.etiqueta = etiqueta++
Generación de código
inicio_while#: ; # es $$.etiqueta
Comprobaciones semánticasComprobar que $2.tipo == BOOLEANComprobar que $2.direcciones == 0/1
Propagación de atributos$$.etiqueta = $1.etiqueta
Generación de códigopop eaxmov eax , [eax] ; Si nivel de indirección de exp == 1cmp eax, 0je near fin_while# ; # es $$.etiqueta
MODIFICACIÓN DE LA GRAMÁTICA
while : TOK_WHILE ‘(’
while_exp : while exp ‘)’ ‘{’
bucle : while_exp sentencias ‘}’
Generación de código
jmp near inicio_while# ; # es $1.etiquetafin_while#: ; # es $1.etiqueta
Prácticas de Procesadores de Lenguaje 2008-2009 101
Generación de código para sentencias iterativas
� La generación de código de esta sentencia se propone como trabajo del alumno.
� La producción de la gramática que describe la sintaxis de la sentencia FOR es:
� bucle: TOK_FOR ‘(’ TOK_IDENTIFICADOR '=‘ exp ‘;’ exp ‘)’ ‘{’ sentencias ‘}‘
� En el enunciado de la última práctica se describe detalladamente el funcionamiento de la sentencia. Es imprescindible conocer dicho funcionamiento para implementar de manera correcta la generación de código correspondiente, y se podría resumir en los siguientes puntos:
� El grupo de sentencias situado entre las llaves sólo se ejecuta si el valor de la segunda expresión es mayor o igual al valor de la primera expresión.
� En cada iteración se incrementa en 1 el valor de la variable de control del bucle.
� Cuando termina la ejecución del bucle, el identificador contiene el valor de la segunda expresión.
� Habría que añadir al enunciado de la práctica que en el caso en el que no se ejecuten las sentencias porque el valor de la segunda expresión es mayor o igual al valor de la primera expresión, inmediatamente después de la sentencia FOR, el valor del identificador coincidecon el de la primera expresión. Es decir, la asignación inicial en la que se asigna al identificador el valor de la primera expresión, se realiza siempre independientemente de si se ejecutan o no las sentencias.
Sentencia FOR
Prácticas de Procesadores de Lenguaje 2008-2009 102
Generación de código para sentencias de selección
� El lenguaje de programación ALFA tiene una sentencia de selección cuya sintaxis se describe en las siguientes producciones:
� seleccion: TOK_SWITCH ‘(’ exp ‘)’ ‘{’casos_seleccion ‘}’
� casos_seleccion: casos_estandar caso_defecto
� casos_estandar: caso_estandar
� casos_estandar: casos_estandar caso_estandar
� caso_estandar: TOK_CASE constante_entera ‘:’ sentencias
� caso_defecto: TOK_DEFAULT sentencias
� La generación de código de esta sentencia se propone como trabajo del alumno.
� En el enunciado de la última práctica se describe detalladamente el funcionamiento de la sentencia. Es imprescindible conocer dicho funcionamiento para implementar de manera correcta la generación de código correspondiente.
Prácticas de Procesadores de Lenguaje 2008-2009 103
Generación de código
� Generación de código para funciones� Convenio de las llamadas
� Ejemplo
� Generación de código para las llamadas a funciones
� Generación de código inicial y final en el cuerpo de una función
� Generación de código para la localización del contenido de los parámetros
� Generación de código para la localización del contenido de las variables locales
� Generación de código para la localización de la dirección de parámetros y variables locales
� Tratamiento de identificadores
Índice
Prácticas de Procesadores de Lenguaje 2008-2009 104
Generación de código para funciones
� Para que las llamadas a funciones trabajen de manera correcta, es necesario establecer lo que conoce como convenio de llamadas que debe ser especificado explícitamente al diseñar el compilador.
� El convenio de llamadas tiene que dar respuesta a las siguientes preguntas:� ¿Cómo se comunican los argumentos desde el programa llamante a la función llamada?
� ¿Cómo se comunican los resultados desde la función llamada hasta el programa llamante?
� Se recomienda utilizar el siguiente convenio de llamadas:� Para comunicar los argumentos desde el programa llamante a la función llamada:
� Se utilizará la pila.
� El programa llamante dejará en la pila el valor de los argumentos de la llamada en el mismo ordenen el aparecen en la definición de la función.
� Tras la llamada, el programa llamante tiene la responsabilidad de eliminar los argumentos de la pila.
� Para comunicar los resultados desde la función llamada:� Se utilizará el registro eax.
Convenio de las llamadas
Prácticas de Procesadores de Lenguaje 2008-2009 105
Generación de código para funciones
� Explicando más detalladamente el proceso consideremos el siguiente programa ALFA.
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 106
Generación de código para funciones
� Imaginemos el código NASM equivalente. Comencemos por el programa principal.
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 107
Generación de código para funciones
� Las secciones .bss y .data junto con la cabecera del segmento de código tendrían el contenido que se muestra en la figura. Posteriormente estudiaremos la zona correspondiente al código de las funciones.
segment .bss
_z resd 1
segment .data
.....
segment .text
global main
extern ...
;
; código de las
; funciones
;
main:
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 108
Generación de código para funciones
� La primera instrucción del programa principal es una sentencia de lectura que se traduce en NASM en una llamada a la función “scan_int”. Antes de llamar a esta función, se apila su parámetro, que es la dirección donde debe depositar el dato.
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
···
main:
push dword _z
call lee_entero
add esp, 4
El programa llamante introduce en la pila los argumentos de la
función “scan_int”
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 109
Generación de código para funciones
� Después de introducir en la pila el parámetro de la llamada, se invoca a la función.
···
main:
push dword _z
call scan_int
add esp, 4
Realiza la llamada a la función
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 110
Generación de código para funciones
� La función “scan_int” realiza su trabajo, y desde el principal se restaura la pila.
···
main:
push dword _z
call scan_int
add esp, 4El programa llamante restaura la pila manipulando el puntero de
pila
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 111
Generación de código para funciones
� La segunda sentencia del programa principal ALFA es una instrucción de escritura, en la cual, antes de la propia escritura, se procesa la llamada a la función. Esta llamada implica realizar las mismas acciones que con la llamada a “scan_int”
···
main:
push dword _z
call scan_int
add esp, 4
push dword [_z]
call _doble
add esp, 4
De la misma manera hay que hacer con una llamada a una
función definida por el programador de ALFA
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 112
Generación de código para funciones
� Siguiendo el convenio de llamadas previamente definido, la función deja el retorno en el registro eax. Por lo tanto, después de restaurar la pila, será necesario apilar en ella el contenido del registro eax (ya que la llamada a una función es una “exp”)
···
main:
push dword _z
call scan_int
add esp, 4
push dword [_z]
call _doble
add esp, 4
push dword eaxEl retorno de la función se
encuentra en eax
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 113
Generación de código para funciones
� El código NASM correspondiente a la traducción de la sentencia de escritura “printf” y también el código del final del programa no son relevantes en la explicación actual y por ello se omiten en la figura.
···
main:
push dword _z
call scan_int
add esp, 4
push dword [_z]
call _doble
add esp, 4
push dword eax
;
; resto de código
;
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 114
Generación de código para funciones
� Para comprender el código que hay que generar para la función imaginemos cómo está la pila (donde se tiene que almacenar toda la información) en una llamada si z contiene el valor 3.
···
main:
push dword _z
call scan_int
add esp, 4
push dword [_z]
call _doble
add esp, 4
push dword eax
;
; resto de código
;
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 115
Generación de código para funciones
� Recuérdese que la pila de 32 bits tiene su puntero almacenado en el registro esp recuerde, también, que la pila “crece” hacia posiciones de memoria de valor menor, se representará gráficamente con crecimiento “hacia abajo”.
3 esp
Se introduce en la pila el valor de z
···
main:
push dword _z
call scan_int
add esp, 4
push dword [_z]
call _doble
add esp, 4
push dword eax
;
; resto de código
;
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 116
Generación de código para funciones
� La instrucción “call” introduce en la pila la dirección de retorno a la que hay que regresar cuando termine la función.
3
dir ret.
La instrucción call introduce en la pila la dirección de retorno
esp
Ejemplo
···
main:
push dword _z
call scan_int
add esp, 4
push dword [_z]
call _doble
add esp, 4
push dword eax
;
; resto de código
;
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 117
Generación de código para funciones
� Esta es la pila que se encuentra la función cuando comienza su ejecución.� Está claro dónde se encuentra el argumento de la función (arg)
� Se tiene que utilizar también la pila para guardar las variables locales (auxArg)
� Debemos conocer qué expresión, en función de los registros de gestión de la pila, sirve para localizar en ella argumentos y variables locales.
� Obsérvese que el compilador tendrá que generar el código necesario para el cuerpo de la función.
� Por las decisiones de diseño tomadas, se utilizará la pila, por lo tanto, el valor del registro esp cambiará.
� NASM proporciona otro registro ebp específicamente para poder solucionar esta circunstancia.
� Para ello, hay que hacer lo siguiente:� Guardar el valor de ebp (también en la pila)
� Utilizar ebp para mantener una copia del valor de esp en el momento de la entrada
� Utilizar ebp para localizar los parámetros y las variables locales
� Recuperar los valores de los dos registros antes de salir de la función
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 118
Generación de código para funciones
3
dir ret.
_doble:
push ebp
ebp esp
Se guarda el valor deebp en la pila
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
� La primera acción en la generación de código del cuerpo de una función es salvar en la pila el registro ebp, ya que se va a utilizar como puntero base para localizar en la pila los argumentos y las variables locales de la función.
Prácticas de Procesadores de Lenguaje 2008-2009 119
Generación de código para funciones
3
dir ret.
_doble:
push ebp
mov ebp, esp
ebp esp ebp
Se guarda en ebp el valor de esp
Ejemplo
� La segunda acción es guardar el registro ebp el valor del puntero de pila, para poder gestionar el acceso a los parámetros y las variables locales (se verá posteriormente)
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 120
Generación de código para funciones
� Recuérdese que usamos un tamaño de dato de 32 bits, que son 4 bytes. Así es fácil saber que arg se encuentra separado de ebp por 2 dword, es decir + 8 bytes
3
dir ret.
_doble:
push ebp
mov ebp, esp
ebp esp ebp
ebp+8
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 121
Generación de código para funciones
� Ahora sólo queda localizar (reservar espacio en la pila y saber como acceder posteriormente) las variables locales (auxArg). Para ello se utiliza las posiciones libres bajo la cima de la pila (como con push, restamos a esp lo necesario)
3
dir ret.
_doble:
push ebp
mov ebp, esp
sub esp, 4 ebp ebp
ebp+8
esp
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Espacio reservado para la variable local
Prácticas de Procesadores de Lenguaje 2008-2009 122
Generación de código para funciones
� Por lo tanto, auxArg se encontrará siempre 4 bytes por debajo de ebp es decir, en ebp-4
3
dir ret.
_doble:
push ebp
mov ebp, esp
sub esp, 4 ebp ebp
ebp+8
esp ebp-4
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 123
Generación de código para funciones
� Ahora se puede generar el código para el cuerpo de la función (aquí se genera “a mano” y es diferente de lo generado automáticamente por el compilador)
3
dir ret.
_doble:
push ebp
mov ebp, esp
sub esp, 4
mov dword eax, [ebp+8]
mov dword [ebp-4], eax
mov dword edx, 2
imul edx
ebp ebp
ebp+8
3 esp ebp-4
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 124
Generación de código para funciones
� Antes de salir de la función se debe “recuperar” los valores iniciales de ebp y esp. Recuérdese que en ebp se guardó el valor de esp, y por lo tanto recuperar el valor de esp consiste en un simple movimiento.
3
dir ret.
_doble:
push ebp
mov ebp, esp
sub esp, 4
mov dword eax, [ebp+8]
mov dword [ebp-4], eax
mov dword edx, 2
imul edx
mov esp, ebp
ebp ebp esp
ebp+8
Se copia en esp su valor que está guardado en ebp. Obsérvese que es como si se hicieran los pop necesarios para eliminar las variables locales
3 ebp-4
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 125
Generación de código para funciones
� Para recuperar el valor que tenía el registro ebp al inicio de la función, simplemente se accede a la cima de la pila, ya que se apiló previamente.
3
dir ret.
_doble:
push ebp
mov ebp, esp
sub esp, 4
mov dword eax, [ebp+8]
mov dword [ebp-4], eax
mov dword edx, 2
imul edx
mov esp, ebp
pop ebp
ebp ebp
ebp+8
3 ebp-4
Se recupera (desde la pila) el antiguo valor de ebp. Obsérvese
que, tras modificar en la instrucción anterior esp la cima de la pila apunta ahora a la
posición que guarda la copia de ebp
esp
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 126
Generación de código para funciones
� Como última instrucción de la función, la instrucción “ret”, que coge de la pila la dirección de retorno que apiló la instrucción “call”
3
dir ret.
_doble:
push ebp
mov ebp, esp
sub esp, 4
mov dword eax, [ebp+8]
mov dword [ebp-4], eax
mov dword edx, 2
mul edx
mov esp, ebp
pop ebp
ret
esp
Y ya se puede devolver el control al programa llamante mediante la instrucción ret. Esta instrucción elimina de la pila la dirección de
retorno
Ejemplo
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 127
Generación de código para funciones
� Retornamos, por lo tanto, al código del programa principal que se encuentra la pila como la tenía antes de la ejecución de la instrucción call. Obsérvese que la pila todavía contiene los argumentos de la función, el programa debe “limpiar” la pila.
3 esp
Ejemplo
···
main:
push dword _z
call scan_int
add esp, 4
push dword [_z]
call _doble
add esp, 4
push dword eax
;
; resto de código
;
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Prácticas de Procesadores de Lenguaje 2008-2009 128
Generación de código para funciones
� Para restaurar la pila se manipula su puntero, y se posicionará en el lugar donde estaba antes de producirse la inserción de los parámetros de la llamada a la función.
3
esp
···
main:
push dword _z
call scan_int
add esp, 4
push dword [_z]
call _doble
add esp, 4
push dword eax
;
; resto de código
;
main {
int z;
function int doble(int arg)
{
int auxArg;
auxArg = arg;
return 2*arg ;
}
scanf z;
printf doble(z)
}
Ejemplo
Prácticas de Procesadores de Lenguaje 2008-2009 129
Generación de código para funciones
� Lo observado en el ejemplo anterior puede generalizarse para cualquier función con cualquier número de variables locales y cualquier número de argumentos.
� El programa llamante debe primero copiar los argumentos de la función en la pila en el mismo orden en el que aparecen en la declaración de la función
� Las producciones relacionadas con los argumentos de llamada a una función son las siguientes:
exp: TOK_IDENTIFICADOR ‘(‘ lista_expresiones ‘)’
lista_expresiones: exp resto_lista_expresiones
lista_expresiones:
resto_lista_expresiones: ‘,’ exp resto_lista_expresiones
resto_lista_expresiones:
push dword <1er argumento>···
push dword <último argumento>
Generación de código para las llamadas a funciones
Prácticas de Procesadores de Lenguaje 2008-2009 130
Generación de código para funciones
� Como puede observarse en las producciones anteriores, los parámetros de llamada a una función son una lista de no terminales “exp”.
� Por lo tanto, para cumplir el convenio de llamadas en el que se dice que los parámetros se pasan por valor, es necesario controlar que las reducciones del no terminal “exp” que ocurran dentro de una lista de parámetros de llamada dejen en la pila un valor y nunca una dirección.
� Por ejemplo, la producción siguiente:
exp: TOK_IDENTIFICADOR
si se reduce dentro de una lista de parámetros de llamada, debe dejar en la pila el valor del identificador y no su dirección.
� Para implementar esta característica del convenio de llamadas, basta con diseñar un mecanismo que permita saber en las reducciones del no terminal “exp” si está ocurriendo dicha reducción dentro de una lista de parámetros de llamada a función o no, y en caso afirmativo dejar en la pila el valor y no la dirección.
Generación de código para las llamadas a funciones
Prácticas de Procesadores de Lenguaje 2008-2009 131
Generación de código para funciones
� De todas las reglas del no terminal “exp”, solamente hay tres que dejen de manera habitual en la pila una dirección en lugar de un valor, y son las siguientes:
exp: TOK_IDENTIFICADOR
exp: acceso
exp: elemento_vector
� Por lo tanto, sólo en estas producciones hay que controlar si la reducción ocurre dentro de una lista de parámetros de llamada a función.
� Las demás producciones del no terminal “exp” siempre dejan un valor en la pila y por lo tanto su reducción siempre trabaja de la misma manera. Estas reglas son:
exp: exp ‘+’ exp (y todas las demás reglas de operaciones aritmético-lógicas)
exp: constante
exp: ‘(‘ comparacion ‘)’
exp: TOK_SIZE ‘(‘ TOK_IDENTIFICADOR ‘)’
exp: TOK_CONTAINS ‘(‘ exp ‘,’ TOK_IDENTIFICADOR ‘)’
exp: TOK_IDENTIFICADOR ‘(‘ lista_expresiones ‘)’
Generación de código para las llamadas a funciones
Prácticas de Procesadores de Lenguaje 2008-2009 132
Generación de código para funciones
� El programa llamante, después de depositar en la pila los parámetros de la llamada a la función debe llamar a la función:
� Posteriormente restaurar la pila.
� Como la función, atendiendo al convenio de llamadas, deja el resultado en eax, y siempre la llamada a una función aparece en el lugar de una expresión, es necesario, después de que la función termine, apilar el la pila el contenido de eax.
� La producción adecuada para realizar estas tres acciones se muestra en la siguiente figura, en la que también se incluyen acciones relacionadas con el análisis semántico.
call _<nombre función>
add esp, <4 * nº argumentos de la función>
Generación de código para las llamadas a funciones
push dword eax
Prácticas de Procesadores de Lenguaje 2008-2009 133
Generación de código para funciones
Generación de código para las llamadas a funciones
¿número de parámetros formales = actuales?
Comprobación semánticaBuscar en la tabla de símbolos el identificador $1.lexema
Mostrar mensaje de error semánticoy terminar con error
NOSI
exp: fn_name ‘(’ lista_expresiones ’)’
Cálculo de atributos
$$.tipo = tipo del elemento buscado en la tabla$$.direcciones = 0
Generación de código
call _$1.lexema
add esp, 4* global_num_parametros_llamada_actual push dword eax
Actualización de variables globales
en_explist = 0
La variable “en_explist” es la variable global que permite controlar si el
proceso de compilación se encuentra dentro de una lista de parámetros de
una llamada a función
Recuerde que para la gestión de los
identificadores de función, se modificó la gramática creando el símbolo nuevo
“fn_name”
Prácticas de Procesadores de Lenguaje 2008-2009 134
Generación de código para funciones
� Las primeras sentencias NASM del cuerpo de cualquier función deben ser las siguientes:
� El punto adecuado para generar las primeras líneas de código de la función sería el indicado con el símbolo � en la producción. Dicho punto también se identificó en el estudio del análisis semántico como lugar adecuado para la realización de acciones. Por lo tanto, la gramática ya está modificada.
(22) funcion : TOK_FUNCTION tipo TOK_IDENTIFICADOR
‘(‘ parametros_funcion ‘)’ ‘{‘ declaraciones_funcion �
sentencias ‘}’
_<nombre_funcion>:push ebp
mov ebp, esp
sub esp, <4*nº variables locales>
Código inicial y final en el cuerpo de una función
Prácticas de Procesadores de Lenguaje 2008-2009 135
Generación de código para funciones
� Las últimas instrucciones en el cuerpo de una función deben ser las siguientes:
� La producción adecuada para escribir estas últimas instrucciones del cuerpo de una función se muestra en la siguiente figura, en la que también se incluyen acciones relacionadas con el análisis semántico.
mov esp, ebp
pop ebp
ret
Código inicial y final en el cuerpo de una función
Prácticas de Procesadores de Lenguaje 2008-2009 136
Generación de código para funciones
Código inicial y final en el cuerpo de una función
Generación de código
pop dword eax
SI $2.direcciones>0
mov eax , [eax]
mov dword esp, ebp
pop dword ebp
ret
retorno_funcion : TOK_RETURN exp
¿OK ?
Comprobación de tipo y direcciones
NOSI
Mostrar mensaje de errory terminar con error
exp
PILA
Actualización de variables globales
fn_return ++
Se accede a la información de la función en la tabla de
símbolos para comprobar que “exp” tiene el mismo tipo que el
retorno de la función.
Las direcciones de exp siempre tienen
que ser 0 ó 1.
La variable fn_return permite controlar que siempre haya al menos
una instrucción de retorno en el cuerpo de una función
Prácticas de Procesadores de Lenguaje 2008-2009 137
Generación de código para funciones
� En el código del cuerpo de la función, cuando se quiera acceder al valor de los parámetros se utilizará la expresión
� Esta expresión de acceso es válida siempre que el primer parámetro tenga asignada la posición 0.
� Recuerde que, el primer 4 es para “saltar” la dirección de retorno.
� Por ejemplo:� El valor del último argumento será [ebp+8].
� El valor del penúltimo argumento será [ebp+12].
� ···� El valor del i-esimo argumento será [ebp+<4+4*(numero de parámetros – i)>].
[ebp + <4 + 4*(numero de parámetros -posición del parámetro en declaración)>]
Localización del contenido de los parámetros
Prácticas de Procesadores de Lenguaje 2008-2009 138
Generación de código para funciones
� En el código del cuerpo de la función, cuando se quiera acceder al valor de las variables locales se utilizará la expresión
� Esta expresión de acceso es válida siempre que la primera variable local tenga asignada la posición 1.
� Por ejemplo:� El valor de la primera variable local será [ebp-4].
� El valor de la segunda variable local será [ebp-8].
� ···� El valor de la i-esima variable local será [ebp-<4*i>].
[ebp - <4*posición de la variable en declaración>]
Localización del contenido de las variables locales
Prácticas de Procesadores de Lenguaje 2008-2009 139
Generación de código para funciones
� Sin embargo, como se ha indicado en la generación de código de otras construcciones de ALFA (por ejemplo en el de las expresiones que contienen identificadores), a veces es necesario utilizar “la dirección” y no “el contenido“
� Recuérdese, por ejemplo, que para la variable global ALFA z, � La expresión para referirse al contenido es
� Pero, la expresión para la dirección es
� Las funciones no utilizan nombres explícitos para los parámetros y las variables locales, por lo que la expresión equivalente para su dirección (ebp+<desplazamiento> y ebp-<desplazamiento>, respectivamente) son incorrectas en NASM.
� NASM proporciona la instrucción lea para gestionar esta cuestión.
[_z]
Localización de la dirección de parámetros y variables globales
_z
Prácticas de Procesadores de Lenguaje 2008-2009 140
Generación de código para funciones
� En este curso se va a utilizar la siguiente sintaxis para la instrucción lea de NASM
� Donde� <registro>, es el nombre de un registro (por ejemplo eax)
� <expresión>, puede ser, por ejemplo ebp+12.
lea <registro> [<expresión>]
Localización de la dirección de parámetros y variables globales
Prácticas de Procesadores de Lenguaje 2008-2009 141
Generación de código para funciones
� Como resumen de estos últimos puntos, considérese el identificador ALFA z. Se utilizará las siguientes expresiones NASM para
� Acceder a su contenido� Si es una variable global
� Si es el argumento i-ésimo de una función (el primer argumento es el número 0)
� Si es la variable local i-ésima de una función (la primera variable local es la número 1)
[_z]
[ebp+<4+4*(total argumentos – i)>]
[ebp-<4*i>]
Tratamiento de identificadores
Prácticas de Procesadores de Lenguaje 2008-2009 142
Generación de código para funciones
� Acceder a su dirección� Si es una variable global
� Si es el argumento i-ésimo de una función, lo siguiente deja en el registro eax la dirección
� Si es la variable local i-ésima de una función
_z
lea eax, [ebp+<4+4*(número parámetros –i)>]
lea eax, [ebp-<4*i>]
Tratamiento de identificadores
Prácticas de Procesadores de Lenguaje 2008-2009 143
Generación de código para funciones
� Es muy importante que el alumno propague este tratamiento a todos los lugares donde pueda aparecer un identificador, exceptuando:
� Identificadores de vectores y conjuntos porque nunca pueden ser variables locales o parámetros.
� Identificadores en operaciones de acceso, reserva o liberación de memoria porque no puede haber variables locales o globales que sean punteros, y por lo tanto nunca se va a presentar el caso.
� Evidentemente identificadores de funciones.
� Por lo tanto, la propagación del tratamiento se realizará en las siguientes producciones:
asignacion: TOK_IDENTIFICADOR ‘=’ exp
lectura: TOK_SCANF TOK_IDENTIFICADOR
exp: TOK_IDENTIFICADOR
bucle: for ‘(’ TOK_IDENTIFICADOR ‘=‘ exp ‘;’ exp ‘)’ ‘{‘ sentencias ‘}’
Tratamiento de identificadores
Prácticas de Procesadores de Lenguaje 2008-2009 144
Generación de código para funciones
� En resumen, en las producciones anteriormente indicadas, se tiene que determinar para el identificador que aparece:
� Si es � una variable global
� una variable local de una función
� un argumento de una función
� Si el código que se necesita generar accederá� A su contenido
� A su dirección
Ya que cada opción necesita un tratamiento diferente.
Tratamiento de identificadores
Top Related