El mejor libro para responder a su pregunta probablemente sería: Cooper y Torczon, "Ingeniería de un compilador", 2003. Si tiene acceso a la biblioteca de una universidad, debería poder pedir prestada una copia.
O(n2)n
O(n)O(n)
O(n)O(1)O(s)s
Luego, el árbol de análisis generalmente se "aplana" en un gráfico de flujo de control. Los nodos del gráfico de flujo de control pueden ser instrucciones de 3 direcciones (similar a un lenguaje ensamblador RISC), y el tamaño del gráfico de flujo de control generalmente será lineal en el tamaño del árbol de análisis.
O(d)dO(n)n
O(n2)tiempo en el tamaño del gráfico de flujo de todo el programa, pero esto significa que debe prescindir de la información (y las transformaciones de mejora del programa) que pueden ser costosas de probar. Un ejemplo clásico de esto es el análisis de alias, donde para algunos pares de escrituras de memoria le gustaría probar que las dos escrituras nunca pueden apuntar a la misma ubicación de memoria. (Es posible que desee hacer un análisis de alias para ver si puede mover una instrucción por encima de la otra). Pero para obtener información precisa sobre los alias, es posible que necesite analizar cada ruta de control posible a través del programa, que es exponencial en el número de ramas en el programa (y por lo tanto exponencial en el número de nodos en el gráfico de flujo de control).
Luego ingresas a la asignación de registros. La asignación de registros puede expresarse como un problema de coloración de gráficos, y se sabe que colorear un gráfico con un número mínimo de colores es NP-Hard. Por lo tanto, la mayoría de los compiladores utilizan algún tipo de heurística codiciosa combinada con derrames de registros con el objetivo de reducir la cantidad de derrames de registros lo mejor posible dentro de límites de tiempo razonables.
Finalmente entras en la generación de código. La generación de código generalmente se realiza en un bloque básico máximo en un momento en que un bloque básico es un conjunto de nodos de diagrama de flujo de control conectados linealmente con una sola entrada y una única salida. Esto puede reformularse como un problema de cobertura de gráfico donde el gráfico que está tratando de cubrir es el gráfico de dependencia del conjunto de instrucciones de 3 direcciones en el bloque básico, y está tratando de cubrir con un conjunto de gráficos que representan la máquina disponible instrucciones. Este problema es exponencial en el tamaño del bloque básico más grande (que podría, en principio, ser del mismo orden que el tamaño de todo el programa), por lo que esto se vuelve a hacer típicamente con heurística donde solo un pequeño subconjunto de las posibles cubiertas examinado.