En informática , un compilador es un programa informático que transforma el código fuente escrito en un lenguaje de programación o lenguaje informático (el lenguaje fuente ), en otro lenguaje informático (el lenguaje destino , que a menudo tiene una forma binaria conocida como código objeto o código máquina ). La razón más común para transformar el código fuente es crear un programa ejecutable .
Cualquier programa escrito en un lenguaje de programación de alto nivel debe traducirse a código objeto antes de poder ejecutarse, por lo que todos los programadores que utilizan un lenguaje de este tipo utilizan un compilador o un intérprete . Las mejoras en un compilador pueden dar lugar a una gran cantidad de funciones mejoradas en los programas ejecutables.
El compilador de calidad de producción, Compiler-Compiler , a finales de la década de 1970, introdujo los principios de organización del compilador que todavía se utilizan ampliamente en la actualidad (por ejemplo, un front-end que maneja la sintaxis y la semántica y un back-end que genera el código de máquina).
El software para las primeras computadoras se escribía principalmente en lenguaje ensamblador y, antes de eso, directamente en código máquina . Por lo general, es más productivo para un programador utilizar un lenguaje de alto nivel, y los programas escritos en un lenguaje de alto nivel se pueden reutilizar en diferentes tipos de computadoras . Aun así, los compiladores tardaron un tiempo en establecerse, porque generaban un código que no funcionaba tan bien como el ensamblador escrito a mano, eran proyectos de desarrollo abrumadores por sí mismos y la capacidad de memoria muy limitada de las primeras computadoras creó muchos problemas técnicos para las implementaciones prácticas de compiladores.
Entre 1942 y 1945, Konrad Zuse desarrolló Plankalkül ("cálculo de planos"), el primer lenguaje de alto nivel para ordenador, para el que imaginó un Planfertigungsgerät ("dispositivo de montaje de planos"), que traduciría automáticamente la formulación matemática de un programa en una película perforada legible por máquina . [1] Sin embargo, el primer compilador real para el lenguaje se implementó solo décadas después.
Entre 1949 y 1951, Heinz Rutishauser propuso Superplan , un lenguaje de alto nivel y traductor automático. [2] Sus ideas fueron refinadas posteriormente por Friedrich L. Bauer y Klaus Samelson . [3]
El primer compilador práctico fue escrito por Corrado Böhm en 1951 para su tesis doctoral, [4] [5] uno de los primeros doctorados en informática otorgados en cualquier parte del mundo.
El primer compilador implementado fue escrito por Grace Hopper , quien también acuñó el término "compilador", [6] [7] refiriéndose a su sistema A-0 que funcionaba como un cargador o enlazador , no a la noción moderna de un compilador. El primer Autocode y compilador en el sentido moderno fueron desarrollados por Alick Glennie en 1952 en la Universidad de Manchester para la computadora Mark 1. [8] [9] El equipo FORTRAN dirigido por John W. Backus en IBM presentó el primer compilador disponible comercialmente, en 1957, cuya creación llevó 18 años-persona. [10]
El primer compilador ALGOL 58 fue completado a fines de 1958 por Friedrich L. Bauer , Hermann Bottenbruch, Heinz Rutishauser y Klaus Samelson para la computadora Z22 . Bauer et al. habían estado trabajando en tecnología de compiladores para la traducción secuencial de fórmulas en los años anteriores.
En 1960, un compilador Fortran extendido, ALTAC, estaba disponible en el Philco 2000, por lo que es probable que se compilara un programa Fortran para las arquitecturas informáticas de IBM y Philco a mediados de 1960. [11] El primer lenguaje de alto nivel multiplataforma demostrado conocido fue COBOL . En una demostración en diciembre de 1960, se compiló y ejecutó un programa COBOL tanto en el UNIVAC II como en el RCA 501. [7] [12]
Al igual que cualquier otro software, existen beneficios al implementar un compilador en un lenguaje de alto nivel. En particular, un compilador puede ser autoalojado , es decir, escrito en el lenguaje de programación que compila. Construir un compilador autoalojado es un problema de arranque , es decir, el primer compilador de este tipo para un lenguaje debe ser un código de máquina escrito a mano, compilado por un compilador escrito en otro lenguaje o compilado ejecutando el código fuente del compilador sobre sí mismo en un intérprete .
Corrado Böhm desarrolló un lenguaje, una máquina y un método de traducción para compilar ese lenguaje en la máquina en su tesis doctoral presentada en 1951. [4] [5] No solo describió un compilador completo, sino que también definió por primera vez ese compilador en su propio lenguaje. El lenguaje era interesante en sí mismo, porque cada declaración (incluidas las declaraciones de entrada, las declaraciones de salida y las declaraciones de control) era un caso especial de una declaración de asignación .
El compilador internacional ALGOL del Laboratorio de Electrónica Naval o NELIAC fue una implementación de dialecto y compilador del lenguaje de programación ALGOL 58 desarrollado por el Laboratorio de Electrónica Naval en 1958. [13]
NELIAC fue una creación de Harry Huskey , entonces presidente de la ACM y un conocido científico informático (y más tarde supervisor académico de Niklaus Wirth ), y contó con el apoyo de Maury Halstead, el jefe del centro computacional de NEL. La primera versión se implementó en el prototipo de computadora USQ-17 (llamada Countess) en el laboratorio. Fue el primer compilador autocompilador del mundo: el compilador primero se codificó en forma simplificada en lenguaje ensamblador (el bootstrap ), luego se reescribió en su propio lenguaje y se compiló mediante el bootstrap, y finalmente se volvió a compilar por sí mismo, lo que hizo que el bootstrap quedara obsoleto.
Otro compilador autoalojado temprano fue escrito para Lisp por Tim Hart y Mike Levin en el MIT en 1962. [14] Escribieron un compilador de Lisp en Lisp, probándolo dentro de un intérprete de Lisp existente. Una vez que mejoraron el compilador hasta el punto en que podía compilar su propio código fuente, pasó a ser autoalojado. [15]
El compilador tal como existe en la cinta del compilador estándar es un programa en lenguaje máquina que se obtuvo haciendo que la definición de expresión S del compilador trabajara sobre sí misma a través del intérprete.
— Memorándum de la IA 39 [15]
Esta técnica sólo es posible cuando ya existe un intérprete para el mismo lenguaje que se va a compilar. Toma prestada directamente la noción de ejecutar un programa sobre sí mismo como entrada, que también se utiliza en varias demostraciones en informática teórica , como la demostración de que el problema de la detención es indecidible .
Forth es un ejemplo de un compilador autoalojado. Las características de autocompilación y compilación cruzada de Forth son sinónimos de metacompilación y metacompiladores . [16] [17] Al igual que Lisp , Forth es un lenguaje de programación extensible . Son las características de lenguaje de programación extensible de Forth y Lisp las que les permiten generar nuevas versiones de sí mismos o portarse a nuevos entornos.
Un analizador sintáctico es un componente importante de un compilador. Analiza el código fuente de un lenguaje de programación informática para crear algún tipo de representación interna. Los lenguajes de programación tienden a especificarse en términos de una gramática libre de contexto porque se pueden escribir analizadores sintácticos rápidos y eficientes para ellos. Los analizadores sintácticos se pueden escribir a mano o generar mediante un generador de analizadores sintácticos . Una gramática libre de contexto proporciona un mecanismo simple y preciso para describir cómo se construyen las construcciones del lenguaje de programación a partir de bloques más pequeños . El formalismo de las gramáticas libres de contexto fue desarrollado a mediados de la década de 1950 por Noam Chomsky . [18]
La estructura de bloques fue introducida en los lenguajes de programación informática por el proyecto ALGOL (1957-1960), que, como consecuencia, también incluyó una gramática libre de contexto para describir la sintaxis ALGOL resultante.
Las gramáticas independientes del contexto son lo suficientemente simples como para permitir la construcción de algoritmos de análisis eficientes que, para una cadena dada, determinan si se puede generar a partir de la gramática y cómo hacerlo. Si un diseñador de lenguaje de programación está dispuesto a trabajar dentro de algunos subconjuntos limitados de gramáticas independientes del contexto, es posible crear analizadores sintácticos más eficientes.
El analizador sintáctico LR (de izquierda a derecha) fue inventado por Donald Knuth en 1965 en un artículo titulado "Sobre la traducción de idiomas de izquierda a derecha". Un analizador sintáctico LR es un analizador sintáctico que lee la entrada de izquierda a derecha (como aparecería si se mostrara visualmente) y produce una derivación más a la derecha . También se utiliza el término analizador sintáctico LR( k ) , donde k se refiere a la cantidad de símbolos de entrada de búsqueda anticipada no consumidos que se utilizan para tomar decisiones de análisis sintáctico.
Knuth demostró que las gramáticas LR( k ) pueden analizarse con un tiempo de ejecución esencialmente proporcional a la longitud del programa, y que cada gramática LR( k ) para k > 1 puede transformarse mecánicamente en una gramática LR(1) para el mismo lenguaje. En otras palabras, solo es necesario tener un símbolo de anticipación para analizar cualquier gramática determinista libre de contexto (DCFG). [19]
Korenjak (1969) fue el primero en demostrar que se podían producir analizadores sintácticos para lenguajes de programación utilizando estas técnicas. [20] Frank DeRemer ideó las técnicas más prácticas Simple LR (SLR) y Look-ahead LR (LALR), publicadas en su tesis doctoral en el MIT en 1969. [21] [22] Este fue un avance importante, porque los traductores LR(k), tal como los definió Donald Knuth, eran demasiado grandes para su implementación en sistemas informáticos en los años 1960 y 1970.
En la práctica, LALR ofrece una buena solución; la potencia añadida de los analizadores LALR(1) sobre los analizadores SLR(1) (es decir, LALR(1) puede analizar gramáticas más complejas que SLR(1)) es útil y, aunque LALR(1) no es comparable con LL(1) (véase más abajo) (LALR(1) no puede analizar todas las gramáticas LL(1)), la mayoría de las gramáticas LL(1) que se encuentran en la práctica pueden analizarse con LALR(1). Las gramáticas LR(1) son más potentes que LALR(1); sin embargo, una gramática LR(1) requiere un analizador LR canónico que sería extremadamente grande en tamaño y no se considera práctico. La sintaxis de muchos lenguajes de programación está definida por gramáticas que pueden analizarse con un analizador LALR(1), y por esta razón los compiladores suelen utilizar analizadores LALR para realizar análisis de sintaxis del código fuente.
Un analizador recursivo ascendente implementa un analizador LALR utilizando funciones recursivas mutuas en lugar de tablas. Por lo tanto, el analizador se codifica directamente en el lenguaje anfitrión de manera similar al descenso recursivo . La codificación directa generalmente produce un analizador que es más rápido que su equivalente controlado por tablas [23] por la misma razón que la compilación es más rápida que la interpretación. También es posible (en principio) editar manualmente un analizador recursivo ascendente, mientras que una implementación tabular es casi ilegible para el ser humano promedio.
El ascenso recursivo fue descrito por primera vez por Thomas Pennello en su artículo "Very fast LR parsing" en 1986. [23] La técnica fue posteriormente expuesta por GH Roberts [24] en 1988 así como en un artículo de Leermakers, Augusteijn, Kruseman Aretz [25] en 1992 en la revista Theoretical Computer Science .
Un analizador LL analiza la entrada de izquierda a derecha y construye una derivación de la oración que se encuentre más a la izquierda (de ahí el nombre de LL, en lugar de LR). La clase de gramáticas que se pueden analizar de esta manera se conoce como gramáticas LL . Las gramáticas LL son una clase de gramáticas libres de contexto aún más restringida que las gramáticas LR. Sin embargo, son de gran interés para los escritores de compiladores, porque un analizador de este tipo es simple y eficiente de implementar.
Las gramáticas LL(k) pueden analizarse mediante un analizador descendente recursivo que normalmente se codifica a mano, aunque alternativamente se puede utilizar una notación como META II .
El diseño de ALGOL dio lugar a la investigación de la descendencia recursiva, ya que el lenguaje ALGOL en sí es recursivo. El concepto de análisis sintáctico por descendencia recursiva se analizó en la edición de enero de 1961 de Communications of the ACM en artículos separados de AA Grau y Edgar T. "Ned" Irons. [26] [27] Richard Waychoff y sus colegas también implementaron la descendencia recursiva en el compilador ALGOL de Burroughs en marzo de 1961, [28] los dos grupos utilizaron enfoques diferentes pero estaban en contacto al menos informal. [29]
La idea de las gramáticas LL(1) fue introducida por Lewis y Stearns (1968). [30] [31]
El descenso recursivo fue popularizado por Niklaus Wirth con PL/0 , un lenguaje de programación educativo utilizado para enseñar la construcción de compiladores en la década de 1970. [32]
El análisis LR puede manejar una gama más amplia de idiomas que el análisis LL , y también es mejor en el informe de errores (esto es discutible, se requiere REFERENCE), es decir, detecta errores sintácticos cuando la entrada no se ajusta a la gramática lo antes posible.
En 1970, Jay Earley inventó lo que se conocería como el analizador sintáctico Earley . Los analizadores sintácticos Earley son atractivos porque pueden analizar todos los lenguajes independientes del contexto con una eficiencia razonable. [33]
John Backus propuso "fórmulas metalingüísticas" [34] [35] para describir la sintaxis del nuevo lenguaje de programación IAL, conocido hoy como ALGOL 58 (1959). El trabajo de Backus se basó en el sistema postcanónico ideado por Emil Post .
Un desarrollo posterior de ALGOL condujo a ALGOL 60 ; en su informe (1963), Peter Naur denominó la notación de Backus forma normal de Backus (BNF) y la simplificó para minimizar el conjunto de caracteres utilizados. Sin embargo, Donald Knuth argumentó que BNF debería leerse más bien como forma Backus–Naur [36] y ese se ha convertido en el uso comúnmente aceptado.
A principios de los años 70, Niklaus Wirth definió la forma Backus-Naur extendida (EBNF), una versión refinada de la BNF, para PL/0. La forma Backus-Naur aumentada (ABNF) es otra variante. Tanto la EBNF como la ABNF se utilizan ampliamente para especificar la gramática de los lenguajes de programación, como entradas para los generadores de analizadores sintácticos y en otros campos, como la definición de protocolos de comunicación.
Un generador de analizador genera la parte del analizador léxico de un compilador. Es un programa que toma una descripción de una gramática formal de un lenguaje de programación específico y produce un analizador para ese lenguaje. Ese analizador puede usarse en un compilador para ese lenguaje específico. El analizador detecta e identifica las palabras y los símbolos reservados del lenguaje específico a partir de un flujo de texto y los devuelve como tokens al código que implementa la validación sintáctica y la traducción al código objeto. Esta segunda parte del compilador también puede ser creada por un compilador-compilador que use una descripción de sintaxis formal con reglas de precedencia como entrada.
El primer compilador-compilador que utilizó ese nombre fue escrito por Tony Brooker en 1960 y se utilizó para crear compiladores para la computadora Atlas en la Universidad de Manchester , incluido el compilador Atlas Autocode . Sin embargo, era bastante diferente de los compiladores-compiladores modernos, y hoy probablemente se lo describiría como algo intermedio entre un compilador genérico altamente personalizable y un lenguaje de sintaxis extensible . El nombre "compilador-compilador" era mucho más apropiado para el sistema de Brooker que para la mayoría de los compiladores-compiladores modernos, que se describen con mayor precisión como generadores de analizadores sintácticos.
A principios de la década de 1960, Robert McClure en Texas Instruments inventó un compilador-compilador llamado TMG , nombre tomado de "transmogrificación". [37] [38] [39] [40] En los años siguientes, TMG fue portado a varias computadoras mainframe UNIVAC e IBM.
El proyecto Multics , una empresa conjunta entre el MIT y Bell Labs , fue uno de los primeros en desarrollar un sistema operativo en un lenguaje de alto nivel. Se eligió PL/I como lenguaje, pero un proveedor externo no pudo proporcionar un compilador funcional. [41] El equipo de Multics desarrolló su propio dialecto de subconjunto de PL/I conocido como Early PL/I (EPL) como su lenguaje de implementación en 1964. TMG fue portado a la serie GE-600 y utilizado para desarrollar EPL por Douglas McIlroy , Robert Morris y otros.
No mucho después de que Ken Thompson escribiera la primera versión de Unix para el PDP-7 en 1969, Douglas McIlroy creó el primer lenguaje de alto nivel del nuevo sistema: una implementación del TMG de McClure. [42] TMG también fue la herramienta de definición del compilador utilizada por Ken Thompson para escribir el compilador para el lenguaje B en su PDP-7 en 1970. B fue el antecesor inmediato de C.
Uno de los primeros generadores de analizadores sintácticos LALR se llamó "TWS" y fue creado por Frank DeRemer y Tom Pennello.
XPL es un dialecto del lenguaje de programación PL/I , utilizado para el desarrollo de compiladores para lenguajes informáticos. Fue diseñado e implementado en 1967 por un equipo formado por William M. McKeeman, James J. Horning y David B. Wortman en la Universidad de Stanford y la Universidad de California en Santa Cruz . Se anunció por primera vez en la Conferencia Conjunta de Informática de Otoño de 1968 en San Francisco. [43] [44]
XPL incluía un sistema de escritura de traductores relativamente simple, denominado ANALYZER , basado en una técnica de análisis de precedencia de compilador ascendente denominada MSP (precedencia de estrategia mixta). XPL se implementó a través de Burroughs Algol en la computadora IBM System/360 . (Algunas versiones posteriores de XPL utilizadas en proyectos internos de la Universidad de Toronto utilizaron un analizador SLR(1), pero esas implementaciones nunca se distribuyeron).
Yacc es un generador de analizadores sintácticos (vagamente, compilador-compilador ), que no debe confundirse con lex , que es un analizador léxico que Yacc utiliza con frecuencia como primera etapa. Yacc fue desarrollado por Stephen C. Johnson en AT&T para el sistema operativo Unix . [45] El nombre es un acrónimo de " Yet Another Compiler Compiler ". Genera un compilador LALR(1) basado en una gramática escrita en una notación similar a la forma Backus–Naur.
Johnson trabajó en Yacc a principios de los años 1970 en Bell Labs . [46] Estaba familiarizado con TMG y su influencia se puede ver en Yacc y en el diseño del lenguaje de programación C. Debido a que Yacc era el generador de compiladores predeterminado en la mayoría de los sistemas Unix, se distribuyó y utilizó ampliamente. Derivados como GNU Bison todavía se utilizan.
El compilador generado por Yacc requiere un analizador léxico . Los generadores de analizadores léxicos, como lex o flex, están ampliamente disponibles. El estándar IEEE POSIX P1003.2 define la funcionalidad y los requisitos tanto para Lex como para Yacc.
Coco/R es un generador de analizadores sintácticos que genera analizadores sintácticos LL(1) en Modula-2 (con complementos para otros lenguajes) a partir de gramáticas de entrada escritas en una variante de EBNF. Fue desarrollado por Hanspeter Mössenböck en el Instituto Federal Suizo de Tecnología de Zúrich (ETHZ) en 1985.
ANTLR es un generador de analizadores sintácticos que genera analizadores sintácticos LL(*) en Java a partir de gramáticas de entrada escritas en una variante de EBNF. Fue desarrollado por Terence Parr en la Universidad de San Francisco a principios de los años 90 como sucesor de un generador anterior llamado PCCTS.
Los metacompiladores se diferencian de los generadores de analizadores sintácticos en que toman como entrada un programa escrito en un metalenguaje . Su entrada consiste en una fórmula de análisis gramatical combinada con operaciones de transformación integradas que construyen árboles de sintaxis abstracta, o simplemente generan cadenas de texto reformateadas que pueden ser código de máquina de pila.
Muchos pueden programarse en su propio metalenguaje, lo que les permite compilarse a sí mismos y convertirse en compiladores de lenguaje extensibles y autoalojados.
Muchos metacompiladores se basan en el trabajo de Dewey Val Schorre . Su compilador META II , publicado por primera vez en 1964, fue el primer metacompilador documentado. Capaz de definir su propio lenguaje y otros, META II aceptó fórmulas sintácticas con salida incorporada (producción de código) . También se tradujo a una de las primeras instancias de una máquina virtual . El análisis léxico se realizó mediante funciones de reconocimiento de tokens incorporadas: .ID, .STRING y .NUMBER. Las cadenas entre comillas en fórmulas sintácticas reconocen lexemas que no se conservan. [47]
TREE-META , un metacompilador de Schorre de segunda generación, apareció alrededor de 1968. Amplió las capacidades de META II, añadiendo reglas de desanálisis que separaban la producción de código del análisis gramatical. Las operaciones de transformación de árboles en la fórmula sintáctica producen árboles sintácticos abstractos sobre los que operan las reglas de desanálisis. La coincidencia de patrones de árboles de desanálisis proporcionó la capacidad de optimización de mirillas .
CWIC , descrito en una publicación de ACM de 1970, es un metacompilador Schorre de tercera generación que agregó reglas de análisis léxico y operadores de retroceso al análisis gramatical. LISP 2 se combinó con las reglas de desanálisis de TREEMETA en el lenguaje generador CWIC. Con el procesamiento de LISP 2, CWIC puede generar código completamente optimizado. CWIC también proporcionó generación de código binario en secciones de código con nombre. Se podían implementar compilaciones de una o varias pasadas utilizando CWIC.
CWIC compilado en instrucciones de código de máquina direccionables por bytes de 8 bits, diseñadas principalmente para producir código IBM System/360.
Las generaciones posteriores no están documentadas públicamente. Una característica importante sería la abstracción del conjunto de instrucciones del procesador de destino, generando un conjunto de instrucciones de pseudomáquina, macros, que podrían definirse por separado o asignarse a las instrucciones de una máquina real. Las optimizaciones que se aplican a las instrucciones secuenciales podrían luego aplicarse a la pseudoinstrucción antes de su expansión al código de la máquina de destino.
Un compilador cruzado se ejecuta en un entorno pero produce código objeto para otro. Los compiladores cruzados se utilizan para el desarrollo integrado, donde la computadora de destino tiene capacidades limitadas.
Un ejemplo temprano de compilación cruzada fue AIMICO, donde se utilizó un programa FLOW-MATIC en un UNIVAC II para generar lenguaje ensamblador para el IBM 705 , que luego se ensambló en la computadora IBM. [7]
El compilador ALGOL 68C generaba una salida ZCODE , que luego podía compilarse en el código de máquina local mediante un traductor ZCODE o ejecutarse interpretado. ZCODE es un lenguaje intermedio basado en registros. Esta capacidad de interpretar o compilar ZCODE alentó la adaptación de ALGOL 68C a numerosas plataformas informáticas diferentes.
La optimización del compilador es el proceso de mejorar la calidad del código objeto sin cambiar los resultados que produce.
Los desarrolladores del primer compilador FORTRAN se propusieron generar código que fuera mejor que el ensamblador promedio codificado a mano, de modo que los clientes pudieran realmente usar su producto. En uno de los primeros compiladores reales, a menudo lo lograron. [48]
Los compiladores posteriores, como el compilador Fortran IV de IBM , dieron más prioridad a los buenos diagnósticos y a una ejecución más rápida, a expensas de la optimización del código objeto . No fue hasta la serie IBM System/360 que IBM proporcionó dos compiladores independientes: un verificador de código de ejecución rápida y otro más lento y optimizador.
Frances E. Allen , trabajando sola y en conjunto con John Cocke , introdujo muchos de los conceptos para la optimización. El artículo de Allen de 1966, Program Optimization, [49] introdujo el uso de estructuras de datos de gráficos para codificar el contenido del programa para la optimización. [50] Sus artículos de 1970, Control Flow Analysis [51] y A Basis for Program Optimization [52] establecieron los intervalos como el contexto para el análisis y la optimización eficiente y eficaz del flujo de datos. Su artículo de 1971 con Cocke, A Catalogue of Optimizing Transformations , [53] proporcionó la primera descripción y sistematización de las transformaciones de optimización. Sus artículos de 1973 y 1974 sobre el análisis del flujo de datos interprocedimental extendieron el análisis a programas completos. [54] [55] Su artículo de 1976 con Cocke describe una de las dos principales estrategias de análisis utilizadas en la optimización de compiladores en la actualidad. [56]
Allen desarrolló e implementó sus métodos como parte de los compiladores para el IBM 7030 Stretch - Harvest y el sistema de computación avanzada experimental . Este trabajo estableció la viabilidad y la estructura de los optimizadores modernos independientes de la máquina y del lenguaje. Luego estableció y dirigió el proyecto PTRAN sobre la ejecución paralela automática de programas FORTRAN. [57] Su equipo PTRAN desarrolló nuevos esquemas de detección de paralelismo y creó el concepto del gráfico de dependencia del programa, el método de estructuración principal utilizado por la mayoría de los compiladores paralelizadores.
El libro Lenguajes de programación y sus compiladores de John Cocke y Jacob T. Schwartz , publicado a principios de 1970, dedicó más de 200 páginas a los algoritmos de optimización e incluyó muchas de las técnicas que hoy conocemos, como la eliminación de código redundante y la reducción de la fuerza . [58]
En 1972, Gary A. Kildall [59] introdujo la teoría del análisis del flujo de datos que hoy se utiliza para optimizar compiladores [60] (a veces conocida como el método de Kildall ).
La optimización por mirilla es una técnica de optimización sencilla pero eficaz. Fue inventada por William M. McKeeman y publicada en 1965 en CACM. [61] Se utilizó en el compilador XPL que McKeeman ayudó a desarrollar.
Capex Corporation desarrolló el "Optimizador COBOL" a mediados de los años 70 para COBOL . Este tipo de optimizador dependía, en este caso, del conocimiento de las "debilidades" del compilador estándar IBM COBOL, y en realidad reemplazaba (o parcheaba ) secciones del código objeto con código más eficiente. El código de reemplazo podía reemplazar una búsqueda de tabla lineal con una búsqueda binaria, por ejemplo, o, a veces, simplemente reemplazar una instrucción relativamente "lenta" con una instrucción conocida más rápida que, por lo demás, era funcionalmente equivalente dentro de su contexto. Esta técnica ahora se conoce como " Reducción de fuerza ". Por ejemplo, en el hardware IBM System/360, la instrucción CLI era, dependiendo del modelo particular, entre dos y cinco veces más rápida que una instrucción CLC para comparaciones de un solo byte. [62] [63]
Los compiladores modernos generalmente proporcionan opciones de optimización para permitir a los programadores elegir si ejecutar o no un pase de optimización.
Cuando se le asigna a un compilador un programa sintácticamente incorrecto, resulta útil contar con un mensaje de error claro y claro. Desde la perspectiva del autor del compilador, esto suele ser difícil de lograr.
El compilador Fortran WATFIV fue desarrollado en la Universidad de Waterloo , Canadá, a finales de los años 60. Fue diseñado para proporcionar mejores mensajes de error que los compiladores Fortran de IBM de la época. Además, WATFIV era mucho más fácil de usar, porque combinaba la compilación, el enlace y la ejecución en un solo paso, mientras que los compiladores de IBM tenían tres componentes separados para ejecutar.
PL/C fue un lenguaje de programación informática desarrollado en la Universidad de Cornell a principios de los años 70. Aunque PL/C era un subconjunto del lenguaje PL/I de IBM, se diseñó con el objetivo específico de ser utilizado para la enseñanza de la programación. Los dos investigadores y profesores académicos que diseñaron PL/C fueron Richard W. Conway y Thomas R. Wilcox. Presentaron el famoso artículo "Diseño e implementación de un compilador de diagnóstico para PL/I", publicado en Communications of ACM en marzo de 1973. [64]
PL/C eliminó algunas de las características más complejas de PL/I y agregó amplias funciones de depuración y recuperación de errores. El compilador PL/C tenía la inusual capacidad de no dejar de compilar ningún programa, mediante el uso de una corrección automática extensiva de muchos errores de sintaxis y convirtiendo los errores de sintaxis restantes en instrucciones de salida.
La compilación Just-in-time (JIT) es la generación de código ejecutable sobre la marcha o lo más cerca posible de su ejecución real, para aprovechar las métricas de tiempo de ejecución u otras opciones que mejoran el rendimiento.
La mayoría de los compiladores modernos tienen un analizador léxico y un analizador sintáctico que producen una representación intermedia del programa. La representación intermedia es una secuencia simple de operaciones que puede ser utilizada por un optimizador y un generador de código que produce instrucciones en el lenguaje de máquina del procesador de destino. Debido a que el generador de código utiliza una representación intermedia, el mismo generador de código puede utilizarse para muchos lenguajes de alto nivel diferentes.
Existen muchas posibilidades para la representación intermedia. El código de tres direcciones , también conocido como cuádruple o cuadruple , es una forma común en la que hay un operador, dos operandos y un resultado. Los códigos de dos direcciones o triples tienen una pila en la que se escriben los resultados, a diferencia de las variables explícitas del código de tres direcciones.
La asignación única estática (SSA) fue desarrollada por Ron Cytron, Jeanne Ferrante , Barry K. Rosen, Mark N. Wegman y F. Kenneth Zadeck, investigadores de IBM en la década de 1980. [65] En SSA, a una variable se le asigna un valor solo una vez. Se crea una nueva variable en lugar de modificar una existente. SSA simplifica la optimización y la generación de código.
Un generador de código genera instrucciones en lenguaje máquina para el procesador de destino.
El algoritmo de Sethi-Ullman o numeración de Sethi-Ullman es un método para minimizar la cantidad de registros necesarios para almacenar variables.
{{cite book}}
: |work=
ignorado ( ayuda ){{cite book}}
: |work=
ignorado ( ayuda )