UNIVERSIDAD DE SAN CARLOS DE GUATEMALA FACULTAD DE INGENIERIA ORGANIZACIÓN DE LENGUAJES Y COMPILADORES 2 ING. BAYRON LOPEZ AUX. BYRON BOBADILLA SECCION A
REPORTE
OTRAS FORMAS DE CODIGO INTERMEDIO RESPECTO AL CODIGO DE TRES DIRECCIONES Y SUS COMPARACIONES
PABLO SERGIO ROMERO VELIZ CARNE NO. 200512059 GUATEMALA 14 DE NOVIEMBRE DE 2008
FORMAS DE CODIGO INTERMEDIO
INTRODUCCION Durante el proceso de compilación, una de las etapas de la fase de síntesis es la Generación de Código intermedio. El código intermedio es un código que se genera a partir del código fuente original, y debe cumplir un objetivo: debe ser fácil de traducir desde el análisis sintáctico ser fácil de traducir a código objeto. Esta es la razón por la cual existe el código intermedio. Es un paso más en el proceso de compilación. Es decir, un paso más hacia el código objeto. Por lógica, el código intermedio debe ser un código de nivel mas bajo que el código fuente, y cabe mencionar que en algunos compiladores, dicho código no es generado, sino se genera directamente el código máquina. Uno de los códigos intermedios más utilizados es el código de tres direcciones, que no se describirá en este documento, sino mas bien, se irá a la búsqueda de otros tipos de código intermedio, que se pueden utilizar y que, de hecho, utilizan algunos compiladores para algunos lenguajes de programación. Dicho código intermedio pasará luego a ser optimizado, en la fase de Optimización de Código, en la cual se eliminará código que esté de más, o que no sea óptimo y que retrase el proceso de compilación, o que ocupe mucho espacio en la memoria. Cabe mencionar que el código intermedio, no pertenece a ningún lenguaje en especial. Es el hecho de que cumpla el objetivo lo que lo hace código intermedio. En el caso de el código de tres direcciones, no es que deba ser escrito en algún lenguaje específico, puede estar en C, C++, pascal, etc., siempre y cuando cumpla con ser código intermedio (en este caso 3 direcciones) no importará si es de algún lenguaje específico ya conocido, o inventado por el diseñador el compilador.
2
FORMAS DE CODIGO INTERMEDIO
OBJETIVOS OBJETIVO GENERAL: Encontrar algunas otras formas de Código Intermedio aparte del muy conocido código de tres direcciones.
OBJETIVOS ESPECIFICOS: Ir a la búsqueda de por lo menos dos tipos de código intermedio, y tener una visión más amplia de la generación de código intermedio aparte del código de tres direcciones. Dados los códigos intermedios encontrados, investigar sus propiedades características y compararlas con las del código de tres direcciones. Encontrar compiladores de lenguajes que generen los códigos intermedios investigados. Identificar la complejidad de cada uno de los códigos intermedios que se han investigado con respecto al código de tres direcciones.
3
FORMAS DE CODIGO INTERMEDIO
PLANTEAMIENTO DEL PROBLEMA Luego de pasar la fase de análisis (Análisis del Léxico, Sintáctico y Semántico), el proceso de compilación procede a realizar la generación de código intermedio. El código más utilizado es el código de tres direcciones, por su versatilidad, su simplicidad y su utilidad para posteriormente optimizarlo. El investigador desea compartir acerca de por lo menos dos tipos de código intermedio y cuáles son sus características principales, algunos compiladores que los utilizan e implementación (si es posible implementarlos en una gramática igual que el código de tres direcciones). Este documento no pretende ser un exhaustivo manual de cómo implementar todos los tipos de código intermedio que existen y la forma en que se genera en una gramática, sino mas bien pretende responder a la pregunta de si existen más tipos de código intermedio diferente al de 3 direcciones, sus características comparadas con dicho código y algunos lenguajes que le utilicen. En este caso nos centraremos en describir brevemente tres tipos de Codigo Intermedio: Uno del tipo gráfico (se representa gráficamente pero se implementa con estructuras de datos), otro que ha sido estandarizado y por último un tipo de código de máquina virtual. MARCO TEORICO Tipos de Código Intermedio ¿Qué es Código Intermedio? El código intermedio es un código que puede generar un compilador y que es un punto de acercamiento al código objeto al que se desea llegar. Los códigos intermedios pueden ser códigos de alto nivel, nivel medio o bajo nivel dependiendo de cómo se haya diseñado dicho código y a qué código objeto se desea llegar al final. Pero lo que sí debe de cumplir el código intermedio es ser un código de más bajo nivel que el código fuente original. Recordemos que la definición de compilador es un programa que se encarga de transformar un código fuente de alto nivel a otro código objeto de más bajo nivel. A continuación se describen 3 tipos de códigos intermedios: Uno de tipo gráfico (Representación gráfica, no implementación gráfica) Un código estandarizado (Originalmente diseñado por Microsoft) Un código de máquina virtual (De un lenguaje compilado y posteriormente interpretado)
4
FORMAS DE CODIGO INTERMEDIO
Arboles Sintácticos Abstractos Los árboles sintácticos abstractos o AST (Abstract Syntax Trees por sus siglas en inglés) es un tipo de representación intermedia, construida en forma de árbol. En este árbol, todos los operadores vienen representados por nodos, y sus hijos son los operandos. Trabajar con un árbol sintáctico abstracto es una forma fácil y efectiva de manejar lo esencial del lenguaje, pues se abstraen las instrucciones y se eliminan todos los elementos que vienen de sobra o extras. Se trabaja con una estructura del tipo árbol, ya que esta estructura es la que se utiliza en el análisis sintáctico, y es la que resulta más fácil de construir utilizando una gramática. Por ejemplo a la hora de analizar las expresiones aritméticas, teniendo una línea de código como la siguiente: 2 + 3 * (7 + 5). El árbol resultante para dicha expresión sería el siguiente:
EE+E |E–E |E*E |E/E | (E) | num | id Gramática para Expresiones Aritméticas
* + 2
+ 3
7
5
AST resultante para la expresión 2 + 3 * (7 + 5)
En un árbol sintáctico abstracto, los nodos vienen representados por los símbolos no terminales. Entonces el AST se construye en base a una definición dirigida por la sintaxis, es decir, es necesario hacer una gramática para generarlo, tal y como se hace con el código de tres direcciones. Ejemplo de Generación de AST: Generar un Árbol de Sintaxis Abstracto para el siguiente código:
5
FORMAS DE CODIGO INTERMEDIO
a:double; if (a>2) then write a; else write g(5, a) + 8; endif write f(a * 2);
Ejemplo de generación de un AST para código fuente. Fuente: http://www.di.uniovi.es/procesadores/Apuntes/Sintactico/Sintactico%20Clase%202.pdf
Una forma de implementar un árbol a la hora de generar uno, es la utilización de estructuras de datos, éstas pueden ser de tipo string y que en dichas cadenas se guarde el código que se vaya leyendo para su posterior recorrido y optimización. Entre los compiladores que generan un AST como representación intermedia, están los de los lenguajes del .net Framework (C#, Visual Basic, J#, etc). Ya que el compilador de dichos lenguajes hace uso de esta técnica para la generación de código intermedio y posteriormente se genera un código llamado CIL. Algo muy importante es que un AST es solamente una representación intermedia, y no se puede trabajar una optimización directa sobre él. Puede resultar muy útil para abstraer el código fuente y eliminar los “adornos” del lenguaje a compilar, y obtener lo esencial del código original. Luego se puede recorrer el árbol para obtener el código y generar otro para optimizarlo o ir directamente a un código ensamblador por ejemplo.
6
FORMAS DE CODIGO INTERMEDIO
Código CIL CIL es un código intermedio de bajo nivel y su nombre viene dado por las siglas Common Intermediate Language (Lenguaje Intermedio Común). Básicamente, el CIL es un lenguaje ensamblador orientado a objetos, y fue originalmente desarrollado por Microsoft. Es por ello que muchos le conocen como MSIL, pero mas tarde fue estandarizado para poder utilizarlo en otros lenguajes. El código CIL trabaja en base a pilas y es utilizado por los lenguajes del .NET Framework como código intermedio antes de pasarlo a lenguaje objeto. El código CIL es un lenguaje completo, como se describió anteriormente fue diseñado como un lenguaje ensamblador orientado a objetos, y con su estandarización ha sido utilizado como código intermedio para otras aplicaciones que no son de Microsoft, como el proyecto MONO de software libre. CIL trabaja en base a pilas, es así como por ejemplo para mandar a ejecutar una función o procedimiento, sus parámetros son colocados en la pila para poder llamarlos desde ahí. Esto es exactamente lo que hace el código de tres direcciones, por su similitud con el código ensamblador que trabaja directamente con la memoria de la computadora. Entonces el proceso de traducción de código fuente a código CIL, es prácticamente el mismo que se utiliza para la traducción a código de tres direcciones, solamente varía el código y el Lenguaje Intermedio Común es un lenguaje definido y tiene una sintaxis definida, y su estructura es de más bajo nivel, siendo prácticamente un lenguaje ensamblador. Por ejemplo, para las expresiones aritméticas, suponiendo que se quiere hacer la siguiente suma: 2 + 3. Se introducen a la pila los dos valores (2 y 3), y con la directiva “add” del lenguaje, toma esos dos valores de la pila y los suma, los saca y apila el valor del resultado. Para las estructuras de control (if, while, for, etc.) se hace uso de los saltos y etiquetas, como en el lenguaje ensamblador, validando cada una de las condiciones y dando los saltos respectivos (como el goto de código de tres direcciones). Código Intermedio de Java El lenguaje de programación Java, utiliza un código intermedio de máquina virtual, llamado Java bytecode. Éste código lo genera el compilador de Java, que posteriormente será interpretado. El lenguaje Bytecode, es un lenguaje que se compone de instrucciones que tienen un tamaño de un Byte, esto le da su nombre al código. Este código se compone de un código de operación que va desde el cero hasta el doscientos cincuenta y cinco (0 - 255) y dicho código va seguido de parámetros como las direcciones de memoria, nombres de los registros, etc. (como el código ensamblador).
7
FORMAS DE CODIGO INTERMEDIO
A continuación se muestra una traducción de código Java a Código Bytecode de ejemplo: public class X { public static void main(String[] args) { add(1, 2); } public static int add(int a, int b) { return a+b; } } Traducción: public static void main(java.lang.String[]); Code: 0: iconst_1 1: iconst_2 //Method add:(II)I 2: invokestatic #2; 5: pop 6: return public static int add(int,int); Code: 0: iload_0 1: iload_1 2: iadd 3: ireturn Ejemplo de traducción a Bytecode de Java. Fuente: http://www.efn.uncor.edu/escuelas/computacion//files/Introducci%C3%B3n%20a%20JOP.pdf
Cabe aclarar que Java es un lenguaje que es compilado e interpretado. Al inicio, se compila normalmente, pero no se genera directamente un código máquina, sino un código de un nivel más abstracto para luego ser interpretado por un intérprete, que suele llamarse Máquina Virtual de Java (Java Virtual Machine). Éste proceso es llamado generación de código intermedio de máquina virtual, que es otro tipo de código intermedio, y es utilizado por muchos compiladores de distintos lenguajes como Pearl, PHP o Ruby. En realidad lo que hace este proceso es una pre-compilación para luego realizar una interpretación con intérpretes llamados Just in time, que van procesando todas las líneas de código conforme las va leyendo para una mejor ejecución (más óptima).
8
FORMAS DE CODIGO INTERMEDIO
CONCLUSIONES Existe una diversidad de códigos intermedios, la mayoría de ellos diseñados por el mismo creador del compilador para determinado lenguaje, y varía su complejidad ya que existen de tres tipos: de alto nivel, de medio nivel y de bajo nivel. Existen casi tantos códigos intermedios como compiladores de lenguajes. Esto porque quien diseña un compilador puede crear su propio código intermedio que le resulte agradable y sencillo para el objetivo final de un compilador: generar un código de menor nivel que el código fuente original. No por nada el código de tres direcciones es uno de los más utilizados y más famosos, dado que es muy práctico y sencillo de aprender, y está basado en un principio sencillo y concreto: cada sentencia debe de apuntar a tres direcciones de memoria o menos. Además es muy estándar y no es tan complejo como varios lenguajes intermedios que son muy parecidos al código ensamblador que puede resultar muy complejo y engorroso para la mayoría de programadores modernos. Existe un tipo de código intermedio muy interesante, que es el código intermedio de máquina virtual. Este es un código que se genera para luego ser interpretado por alguna otra aplicación, comúnmente llamada máquina virtual. Java es un lenguaje que utiliza este tipo de compilación, pero también hay otros lenguajes como Ruby o PHP que llevan este mismo proceso. El árbol sintáctico abstracto es una representación intermedia de código que abstrae las estructuras esenciales de cualquier lenguaje, pero no se puede aplicar una optimización en él, así que en este caso es recomendable utilizar código como el de tres direcciones que de hecho sí se puede optimizar.
9
FORMAS DE CODIGO INTERMEDIO
RECOMENDACIONES Al lector de este documento que se haya interesado por los códigos intermedios, le recomiendo documentarse sobre el código intermedio de máquina virtual. Obtener una más amplia información acerca del código Bytecode de Java e investigar acerca de el código-P que en su tiempo utilizaron los compiladores de pascal en los años 70’s y parte de los 80’s. En este documento se explico la estructura de los árboles sintácticos abstractos, pero dejo como recomendación investigar acerca de los grafos dirigidos acíclicos, que se basan prácticamente en la misma estructura pero tienen una diferencia con los ASTs. Recomiendo también el estudio del código intermedio común o código CIL, que fue originalmente creado por Microsoft Corporation (por eso también llamado MSIL), pero que fue posteriormente estandarizado y ahora utilizado por el proyecto MONO de software libre que contiene compiladores de varios lenguajes de programación, entre ellos C#.
10
FORMAS DE CODIGO INTERMEDIO
FUENTES BILIOGRAFICAS
Alfred V. Aho, Ravi Sethi, Jeffrey D Ullman Compiladores, principios técnicas y herramientas 1998, Editorial Pearson (Capítulo VIII) Kenneth C. Louden Construccion de compiladores: Principios y prácticas 2004, Editorial Thompson Fuentes Web: http://www.di.uniovi.es/procesadores/Apuntes/GeneracionCodigo/trans.LRI.pdf http://ccia.ei.uvigo.es/docencia/PL/T8.pdf http://rua.ua.es/dspace/bitstream/10045/3848/1/cil-para-pl.pdf http://www.uco.es/~ma1fegan/pl/practicas/ANTLR/Arboles-de-sintaxis-abstracta.pdf http://www.dsic.upv.es/~jsilva/uned/compiladores/Apuntes06.pdf
11