¿Por qué LLVM tiene un IR tipo ensamblado en lugar de un IR tipo árbol? O: ¿por qué los proyectos se dirigen a LLVM IR en lugar de AST de clang?

14

¿Por qué la representación intermedia de LLVM (LLVM IR) es similar a un ensamblado en lugar de un árbol?

Alternativamente, ¿por qué las implementaciones de lenguaje se dirigen a LLVM IR en lugar de AST de clang?

No estoy tratando de hacer dos preguntas diferentes a la vez si parece así. Para mí, simplemente parece que tanto los programadores de cliente como de biblioteca han llegado al consenso de que la API de LLVM, nada más y nada menos, es obviamente un buen diseño de software y mi pregunta es "¿por qué?".

La razón por la que pregunto es que parece que LLVM podría proporcionar más funcionalidad a las interfaces si su IR era similar a AST porque entonces las herramientas basadas en AST de clang podrían usarse para cualquier interfaz. Alternativamente, los idiomas que se dirigen a LLVM IR podrían obtener más funcionalidad si se dirigen a AST de clang.

Clang tiene clases y funciones para crear y trabajar con AST y es el único proyecto frontend que está fuertemente vinculado al proyecto LLVM, entonces, ¿por qué la funcionalidad AST de clang es externa a LLVM?

Fuera de mi cabeza, sé que Rust (rustc), D (ldc) y Haskell (GHC) pueden usar LLVM como back-end, pero no usan Clang AST (que yo sepa, podría estar equivocado). No conozco todos los detalles internos de estos compiladores, pero al menos Rust y D ciertamente parecen que podrían compilarse para AST de clang. Quizás Haskell también podría, pero estoy mucho menos seguro de eso.

¿Esto se debe a razones históricas (LLVM originalmente era una "máquina virtual de bajo nivel" y el sonido metálico surgió más tarde)? ¿Es esto porque otras interfaces quieren tener el mayor control posible sobre lo que alimentan a LLVM? ¿Existen razones fundamentales por las que el AST de clang no es apropiado para lenguajes "que no sean C"

No pretendo que esta pregunta sea un ejercicio de lectura mental. Solo quiero que sea útil para aquellos de nosotros que tenemos curiosidad sobre el diseño del compilador, pero que aún no lo somos. Dado que los proyectos LLVM y clang se desarrollan en público, espero que alguien familiarizado con el desarrollo de estos proyectos pueda responder o que la respuesta sea lo suficientemente obvia para algunos nerds de compilación que se sientan lo suficientemente seguros como para responder.


Para evitar algunas respuestas obvias pero insatisfactorias:

Sí, tener un IR similar a un ensamblaje le da más control a quien crea el IR (tal vez X lang tenga una mejor base de código y formato AST que clang), pero si esa es la única respuesta, la pregunta es "¿por qué LLVM solo tiene un ensamblado? como IR en lugar de un IR de árbol de alto nivel y un IR de montaje de bajo nivel ".

Sí, no es tan difícil analizar un lenguaje de programación en un AST (al menos en comparación con los otros pasos de compilación). Aun así, ¿por qué usar AST separados? Por lo menos, usar el mismo AST le permite usar herramientas que funcionan en AST (incluso cosas simples como impresoras AST).

Sí, estoy totalmente de acuerdo en que ser más modular es algo bueno, pero si esa es la única razón, ¿por qué las implementaciones de otros idiomas tienden a apuntar a LLVM IR en lugar de AST de clang?

Estos anticipos pueden ser erróneos o pasar por alto detalles, así que siéntase libre de dar estas respuestas si tiene más detalles o mis suposiciones están equivocadas.


Para cualquiera que quiera responder una pregunta más definitiva: ¿cuáles son las ventajas y desventajas de un IR tipo ensamblaje frente a un IR tipo árbol?

Praxeolítico
fuente
1
No soy un experto en LLVM, pero creo que hay un pequeño malentendido de su parte. LLVM no tiene asm como IR. De hecho, su IR se parece más a un gráfico que a un árbol. Supongo que por 'asm-like' te estás refiriendo al IR legible por humanos (archivos * .ll), si es así, se hace solo por conveniencia. Pero, esperemos a un verdadero experto que pueda dar una respuesta más completa :)
AlexDenisov
1
Un aspecto importante puede ser la historia: LLVM se diseñó originalmente para desacoplar los backends del compilador de las interfaces del compilador. La idea era que los proveedores de compiladores competirían en optimizaciones de lenguaje, y los vendedores de CPU competirían en optimizaciones de bajo nivel. Por ejemplo, Microsoft y Apple competirían entre sí cuyo compilador de C produce el "mejor" código de bits de C, e Intel y AMD competirían entre sí cuyo back-end LLVM produce el "mejor" código de máquina del código de bits. Los proveedores de aplicaciones enviarían sus aplicaciones en código de bits, y la compilación final se realizaría en el usuario ...
Jörg W Mittag
1
… máquina. LLVM comenzó en un momento, donde no estaba del todo claro que todos usarían Intel. Apple todavía estaba en PowerPC, Intel seguía presionando a Itanium, y así sucesivamente. AFAIK, Apple todavía usa LLVM de esta manera, en algunos de sus marcos 3D, donde el código se envía como código de bits y luego se compila para nVidia o ATI, dependiendo del tipo de tarjeta instalada.
Jörg W Mittag
1
Perdóname, pero ¿qué es un IR?
Adam Copley
1
@AdamCopley representación intermedia
Praxeolitic

Respuestas:

13

Aquí hay una serie de preguntas relacionadas entre sí, intentaré separarlas lo mejor que pueda.

¿Por qué otros lenguajes se basan en LLVM IR y no suenan AST?

Esto se debe simplemente a que clang es una interfaz C / C ++ y el AST que produce está estrechamente acoplado a C / C ++. Otro lenguaje podría usarlo, pero necesitaría una semántica casi idéntica a algún subconjunto de C / C ++, lo cual es muy limitante. Como señala, el análisis de un AST es bastante sencillo, por lo que es poco probable que valga la pena ahorrar un poco en restringir sus elecciones semánticas.

Sin embargo, si está escribiendo herramientas para C / C ++, por ejemplo, analizadores estáticos, reutilizar el AST tiene mucho sentido, ya que es mucho más fácil trabajar con el AST que el texto sin formato si está trabajando con C / C ++ .

¿Por qué LLVM IR es la forma que es?

Se eligió LLVM IR como una forma apropiada para escribir optimizaciones del compilador. Como tal, su característica principal es que está en forma SSA . Es un IR de nivel bastante bajo, por lo que es aplicable a una amplia gama de idiomas, por ejemplo, no escribe memoria, ya que esto varía mucho entre idiomas.

Ahora, resulta que escribir optimizaciones de compiladores es una tarea bastante especializada y a menudo es ortogonal al diseño de características del lenguaje. Sin embargo, tener un lenguaje compilado que se ejecute rápidamente es un requisito bastante general. Además, la conversión de LLVM IR a ASM es bastante mecánica y, en general, tampoco es interesante para los diseñadores de idiomas.

Por lo tanto, reducir un lenguaje a LLVM IR le da al diseñador de lenguaje muchas "cosas gratis" que son muy útiles en la práctica, dejándolo concentrado en el lenguaje mismo.

¿Sería útil un IR diferente (OK, no preguntado pero implícito)?

¡Absolutamente! Los AST son bastante buenos para ciertas transformaciones en la estructura del programa, pero son muy difíciles de usar si desea transformar el flujo del programa. Un formulario SSA es generalmente mejor. Sin embargo, LLVM IR tiene un nivel muy bajo, por lo que se pierde gran parte de la estructura de alto nivel (a propósito, por lo que es de aplicación más general). Tener un IR entre el AST y el IR de bajo nivel puede ser beneficioso aquí. Tanto Rust como Swift adoptan este enfoque y tienen un IR de alto nivel entre los dos.

Alex
fuente
Haskell también tiene varios IR antes de llegar a LLVM.
DylanSp
1
@ DylanSp De hecho. Está comenzando a convertirse en la mejor práctica de facto para idiomas complejos. Por ejemplo, Rust no hizo esto inicialmente y ha refactorizado para incluir un IR de alto nivel. También creo que se ha hablado sobre hacer esto para el sonido metálico, pero no estoy seguro de a dónde fue.
Alex