Intentaré darle una descripción general de alto nivel de cómo un compilador toma el código de alto nivel que escribe y lo convierte al lenguaje de bajo nivel que luego se ejecuta en su computadora.
Un compilador se puede dividir en 2 partes, el front-end y el back-end. Estos finales son muy diferentes de lo que los desarrolladores web y de aplicaciones suelen discutir.
El front-end es responsable de tomar el código de alto nivel que usted escribe y producir el código IR. IR es la abreviatura de Representación intermedia.
- Cómo hacer un simulador de máquina de turing para realizar la suma binaria
- Criptografía: ¿Qué sucedería si alguien encuentra un algoritmo significativamente más rápido para factorizar enteros grandes?
- ¿Qué es una lista de todos los conjuntos de habilidades requeridas (matemáticas / programación / algoritmos, etc.) para poder programar juegos / escenarios de ajedrez?
- ¿Crees que una sólida formación en Matemáticas hará que un programador se destaque del resto? ¿Por qué o por qué no?
- ¿Qué es el análisis p delta?
int x, y;
y = 3 + x;
Se traducirá a algo como:
var x
var y
mov x r1
addi r1 3
mov r1 y
Este proceso se realiza mediante tres bloques principales:
- Escáner : toma un archivo de texto sin formato, por ejemplo, main.cpp , y reconoce palabras clave, puntuación, corchetes, nombres, etc., generalmente a través de una especificación de expresiones regulares que proporciona el desarrollador.
Flex es uno de esos programas populares que escanea en función de la expresión regular que se le proporciona. - Analizador : Esto toma los tokens del escáner y localiza patrones sensibles en el código. Estos “patrones” son definidos por el desarrollador que sigue los estándares del lenguaje.
Estos ‘patrones’ se llaman gramática del analizador sintáctico.
func_decl -> any_type id open_brace decl_stmt_list close_brace
- Lo anterior sería uno de esos ejemplos en los que se ha declarado un ‘patrón’ para una función.
Bison es uno de esos programas populares que analiza según las reglas que se le proporcionan. - Conversión a IR : este es un paso donde los patrones del analizador (pueden almacenarse como un árbol de sintaxis abstracta o las reglas para crear IR pueden escribirse junto con la gramática del analizador) se convierten en el IR del que hablamos. El IR depende completamente del desarrollador y no requiere el seguimiento de los estándares.
Se puede hacer alguna optimización en esta etapa.
En este punto, tiene un código que se parece al código de la máquina, pero que no funcionará en ninguna máquina y no está optimizado por completo.
El siguiente paso es pasar de:
var x
var y
var z
mov x r1
addi r1 3
mov r1 y // y = x + 3
mov x r2
addi r2 3
mov r2 z // z = x + 3
a algo como esto (código de ensamblaje en una computadora con arquitectura MIPS):
LW $ 1, addr_x
ADDI $ 1, $ 1, 3
SW $ 1, addr_y
SW $ 1, addr_z
Cue, el back-end . Este segmento es responsable de:
- Optimizador : estas optimizaciones van más allá de lo que se ha hecho en un alto nivel en la etapa de conversión a IR. Algunos ejemplos de optimizaciones serían la optimización de bucle, paralelización, eliminación de código muerto, eliminación de subexpresión común y más.
En el ejemplo anterior, verá cómo las variables y y z almacenan x + 3 pero el IR calcula los valores nuevamente. Esto no está presente en la asamblea. - Generación de código : este paso básicamente convierte el código optimizado en código de ensamblaje funcional basado en la máquina. Este paso básicamente se asigna al ensamblaje mientras se ocupa de factores como los comandos específicos del ensamblaje, los registros disponibles, etc.
Los pasos anteriores son un esquema de alto nivel y en muchos compiladores, algunos pasos pueden fusionarse, saltarse o desglosarse incluso en pequeñas partes secundarias.
La razón para diferenciar el front-end del back-end es para que pueda:
- Reutilice el back-end para varios idiomas cambiando un front-end por otro.
- Básicamente, lo que esto significa es que podría escribir un front-end para, por ejemplo, Java y C ++ y ambos crearían un IR que puede ser utilizado por el mismo back-end.
- Cambie el back-end por varias máquinas / arquitecturas para las que desea construir.
- Hay varios lenguajes de ensamblaje (MIPS, ARM, PC de IBM …) y cada uno es extremadamente diferente del otro. Algunas optimizaciones pueden aplicarse para una, pero no para la otra, y obviamente, el paso de generación de código se verá muy diferente. Por lo tanto, la capacidad de cambiar y elegir back-end.
Espero que esta respuesta ayude a desmitificar un poco el compilador.
El compilador es un software maravilloso y extremadamente complicado que tiene muchas complejidades complejas que surgen de la necesidad de compilar rápidamente (y en paralelo), optimizar el código generado y garantizar que funcione para varios idiomas en varias máquinas sin fallar.
¡Así que la próxima vez que invoques a tu compilador, piensa en todo el trabajo que se ha dedicado a ayudarte a crear algo nuevo y maravilloso!