Descripción general de la canalización de compilación extendida de GCC, incluidos programas especializados como el preprocesador, ensamblador y enlazador.
GCC sigue la 3 etapa de la arquitectura típica de la multi-idioma y multi-CPU compiladores. Todos los árboles de programa se convierten en una representación abstracta común en el «extremo medio», lo que permite que la optimización de código y las instalaciones de generación de código binario sean compartidas por todos los idiomas.,
la interfaz externa de GCC sigue las convenciones de Unix. Los usuarios invocan un programa de controlador específico del idioma (gcc
para C, g++
para C++, etc.), que interpreta argumentos de comando, llama al compilador real, ejecuta el ensamblador en la salida, y luego opcionalmente ejecuta el enlazador para producir un binario ejecutable completo.
cada uno de los compiladores de lenguaje es un programa separado que lee código fuente y genera código máquina. Todos tienen una estructura interna común., Un front end por idioma analiza el código fuente en ese idioma y produce un árbol de sintaxis abstracta («árbol» para abreviar).
Estos son, si es necesario, convertidos a la representación de entrada del extremo medio, llamada forma genérica; el extremo medio luego transforma gradualmente el programa hacia su forma final. Las optimizaciones del compilador y las técnicas de análisis de código estático (como FORTIFY_SOURCE, una directiva del compilador que intenta descubrir algunos desbordamientos de búfer) se aplican al código., Estos trabajan en múltiples representaciones, principalmente la representación GIMPLE independiente de la arquitectura y la representación RTL dependiente de la arquitectura. Finalmente, el código máquina se produce utilizando la coincidencia de patrones específicos de la arquitectura originalmente basada en un algoritmo de Jack Davidson y Chris Fraser.
GCC fue escrito principalmente en C a excepción de partes del Front-end de Ada. La distribución incluye las bibliotecas estándar para Ada, C++ y Java cuyo código está escrito principalmente en esos lenguajes., En algunas plataformas, la distribución también incluye una biblioteca de tiempo de ejecución de bajo nivel, libgcc, escrita en una combinación de C independiente de la máquina y código de máquina específico del procesador, diseñada principalmente para manejar operaciones aritméticas que el procesador de destino no puede realizar directamente.
GCC utiliza muchas herramientas estándar en su compilación, incluidas Perl, Flex, Bison y otras herramientas comunes. Además, actualmente requiere tres bibliotecas adicionales para estar presentes con el fin de construir: GMP, MPC, y MPFR.
en mayo de 2010, el Comité Directivo del CCG decidió permitir el uso de un compilador de C++ para compilar el CCG., El compilador estaba destinado a ser escrito principalmente en C más un subconjunto de características de C++. En particular, esto se decidió para que los desarrolladores de GCC pudieran usar los destructores y las características genéricas de C++.
en agosto de 2012, el Comité Directivo del CCG anunció que el CCG ahora utiliza c++ como lenguaje de implementación. Esto significa que para construir GCC a partir de fuentes, se requiere un compilador de C++ que entienda el estándar ISO/IEC C++03.,
Front endsEdit
los Front ends consisten en preprocesamiento, análisis léxico, análisis sintáctico (parsing) y análisis semántico. Los objetivos de los front ends del compilador son aceptar o rechazar programas candidatos de acuerdo con la gramática y semántica del lenguaje, identificar errores y manejar representaciones válidas del programa para etapas posteriores del compilador. Este ejemplo muestra los pasos de lexer y parser realizados para un programa simple escrito en C.,
cada interfaz utiliza un analizador para producir el árbol de sintaxis abstracta de un archivo fuente dado. Debido a la abstracción del árbol de sintaxis, los archivos fuente de cualquiera de los diferentes lenguajes soportados pueden ser procesados por el mismo back-end. GCC comenzó usando analizadores LALR generados con Bison, pero gradualmente cambió a analizadores de descenso recursivo escritos a mano para C++ En 2004, y para C y Objective-C En 2006. A partir de 2021 todos los front ends utilizan analizadores de descenso recursivo escritos a mano.
hasta GCC 4.,0 la representación en árbol del programa no era totalmente independiente del procesador al que se dirigía. El significado de un árbol era algo diferente para los front-ends de diferentes idiomas, y los front-ends podían proporcionar sus propios códigos de árbol. Esto se simplificó con la introducción de GENERIC y GIMPLE, dos nuevas formas de árboles independientes del lenguaje que se introdujeron con la llegada de GCC 4.0. Genérico es más complejo, basado en el GCC 3.x representación intermedia de Java front end. GIMPLE es un genérico simplificado, en el que varias construcciones se reducen a múltiples instrucciones de GIMPLE., Los front-ends de C, C++ y Java producen genéricos directamente en el front-end. Otros front ends en cambio tienen diferentes representaciones intermedias después de analizar y convertirlas a genéricas.
en cualquier caso, el llamado «gimplifier» luego convierte esta forma más compleja en la forma de GIMPLE basada en SSA más simple que es el lenguaje común para un gran número de optimizaciones globales (de alcance de función) independientes del lenguaje y la arquitectura.,
GENERIC y GIMPLEEdit
GENERIC es un lenguaje de representación intermedio utilizado como un «extremo medio» mientras compila código fuente en binarios ejecutables. Un subconjunto, llamado GIMPLE, está dirigido por todos los front ends de GCC.
la etapa intermedia de GCC hace todo el análisis y optimización de código, trabajando independientemente tanto del lenguaje compilado como de la arquitectura de destino, comenzando por la representación genérica y expandiéndola al lenguaje de transferencia de registro (RTL)., La representación genérica contiene solo el subconjunto de las construcciones de programación imperativas optimizadas por el extremo medio.
en la transformación del código fuente a GIMPLE, las expresiones complejas se dividen en un código de tres direcciones utilizando variables temporales. Esta representación se inspiró en la representación simple propuesta en el compilador McCAT por Laurie J. Hendren para simplificar el análisis y la optimización de programas imperativos.,
OptimizationEdit
la optimización puede ocurrir durante cualquier fase de compilación; sin embargo, la mayor parte de las optimizaciones se realizan después del análisis sintáctico y semántico del front-end y antes de la generación de código del back-end; por lo tanto, un nombre común, aunque algo contradictorio, para esta parte del compilador es el «middle end».»
el conjunto exacto de optimizaciones de GCC varía de una versión a otra a medida que se desarrolla, pero incluye los algoritmos estándar, como la optimización de bucle, el subproceso de salto, la eliminación de subexpresión común, la programación de instrucciones, etc., Las optimizaciones RTL son de menor importancia con la adición de optimizaciones globales basadas en SSA en árboles de GIMPLES, ya que las optimizaciones RTL tienen un alcance mucho más limitado y tienen menos información de alto nivel.
algunas de estas optimizaciones realizadas en este nivel incluyen eliminación de código muerto, eliminación de redundancia parcial, numeración de valores globales, propagación constante condicional dispersa y reemplazo escalar de agregados. También se realizan optimizaciones basadas en la dependencia de matrices, como la vectorización automática y la paralelización automática. La optimización guiada por perfil también es posible.,
Back endEdit
El back-end del GCC está especificado en parte por macros y funciones preprocesadoras específicas de una arquitectura de destino, por ejemplo, para definir su endiandad, tamaño de palabra y convenciones de llamada., La parte frontal del back-end los usa para ayudar a decidir la generación de RTL, por lo que aunque el RTL de GCC es nominalmente independiente del procesador, la secuencia inicial de instrucciones abstractas ya está adaptada al objetivo. En cualquier momento, las instrucciones RTL reales que forman la representación del programa deben cumplir con la descripción de la máquina de la arquitectura de destino.
el archivo de descripción de la máquina contiene patrones RTL, junto con restricciones de operando y fragmentos de código para generar el ensamblaje final., Las restricciones indican que un patrón RTL en particular solo podría aplicarse (por ejemplo) a ciertos registros de hardware, o (por ejemplo) permitir compensaciones inmediatas de operando de solo un tamaño limitado (por ejemplo, 12, 16, 24,… compensación de bits, etc.). Durante la generación de RTL, se comprueban las restricciones para la arquitectura de destino dada. Para emitir un fragmento dado de RTL, debe coincidir con uno (o más) de los patrones RTL en el archivo de descripción de la máquina, y satisfacer las restricciones para ese patrón; de lo contrario, sería imposible convertir el RTL final en código máquina.,
hacia el final de la compilación, el RTL válido se reduce a una forma estricta en la que cada instrucción se refiere a registros de máquina reales y un patrón del archivo de descripción de máquina del Destino. Formar RTL estricto es una tarea complicada; un paso importante es la asignación de registros, donde se eligen registros de hardware reales para reemplazar los pseudo-registros asignados inicialmente. Esto es seguido por una fase de» recarga»; cualquier pseudo-registro al que no se le haya asignado un registro real de hardware es ‘derramado’ a la pila, y se genera RTL para realizar este derramamiento., Del mismo modo, las compensaciones que son demasiado grandes para caber en una instrucción Real deben ser divididas y reemplazadas por secuencias RTL que obedecerán las restricciones de desplazamiento.
en la fase final, el código máquina se construye llamando a un pequeño fragmento de código, asociado con cada patrón, para generar las instrucciones reales del conjunto de instrucciones del destino, utilizando los registros finales, compensaciones y direcciones elegidas durante la fase de recarga. El fragmento de generación de ensamblado puede ser solo una cadena, en cuyo caso se realiza una sustitución de cadena simple de los registros, desplazamientos y/o direcciones en la cadena., El fragmento de generación de ensamblado también puede ser un bloque corto de código C, realizando algún trabajo adicional, pero finalmente devolviendo una cadena que contiene el código de ensamblado válido.
otras característicaseditar
algunas características de GCC incluyen:
- La optimización del tiempo de enlace optimiza los límites de los archivos objeto para mejorar directamente el binario vinculado. La optimización del tiempo de enlace se basa en un archivo intermedio que contiene la serialización de alguna representación de Gimple incluida en el archivo objeto. El archivo se genera junto con el archivo objeto durante la compilación de código fuente., Cada compilación de origen genera un archivo objeto separado y un archivo auxiliar en tiempo de enlace. Cuando los archivos objeto están vinculados, el compilador se ejecuta de nuevo y utiliza los archivos auxiliares para optimizar el código en los archivos objeto compilados por separado.
- Los Plugins pueden extender el compilador GCC directamente. Los complementos permiten que un compilador de stock se adapte a necesidades específicas mediante código externo cargado como complementos. Por ejemplo, los complementos pueden agregar, reemplazar o incluso eliminar pases de extremo medio que operan en representaciones de Gimple., Varios plugins GCC ya han sido publicados, notablemente el plugin GCC Python, que enlaza con libpython, y permite invocar scripts Python arbitrarios desde el interior del compilador. El objetivo es permitir que los plugins GCC se escriban en Python. El complemento MELT proporciona un lenguaje Lisp de alto nivel para extender GCC.
- memoria transaccional de c++ al compilar con-fgnu-tm.
- a partir de GCC 10, los identificadores permiten la codificación UTF-8 (Unicode), es decir, el código fuente de C utiliza la codificación UTF-8 de forma predeterminada.