clang - LLVM jit y nativo
(4)
No entiendo cómo LLVM JIT se relaciona con la compilación normal sin JIT y la documentación no es buena.
Por ejemplo, supongamos que uso el extremo frontal de clang
:
- Caso 1: Compilo el archivo C a nativo con clang / llvm. Entiendo que este flujo es como gcc flow: obtengo mi ejecutable x86 y se ejecuta.
- Caso 2: Compilo en algún tipo de LLVM IR que se ejecuta en LLVM JIT. En este caso, el ejecutable contiene el tiempo de ejecución de LLVM para ejecutar el IR en JIT, o ¿cómo funciona?
¿Cuál es la diferencia entre estos dos y son correctos? ¿El flujo de LLVM incluye soporte para JIT y no JIT? ¿Cuándo quiero usar JIT? ¿Tiene sentido para un lenguaje como C?
Debes entender que LLVM es una biblioteca que te ayuda a construir compiladores. Clang es simplemente una interfaz para esta biblioteca.
Clang traduce el código C / C ++ a LLVM IR y lo entrega a LLVM, que lo compila en código nativo.
LLVM también puede generar código nativo directamente en la memoria, que luego se puede llamar como una función normal. Por lo tanto, los casos 1. y 2. comparten la generación de código y optimización de LLVM.
Entonces, ¿cómo se usa LLVM como un compilador JIT? Usted crea una aplicación que genera un LLVM IR (en la memoria) y luego utiliza la biblioteca LLVM para generar un código nativo (aún en la memoria). LLVM le devuelve un puntero al que puede llamar después. No hay clang involucrado.
Sin embargo, puede usar Clang para traducir un código C en LLVM IR y cargarlo en su contexto JIT para usar las funciones.
Ejemplos del mundo real:
- Unladen Swallow Python VM
- Rubinius Ruby VM
También está el tutorial de Kaleidoscope que muestra cómo implementar un lenguaje simple con el compilador JIT.
Estoy tomando los pasos para compilar y ejecutar el código JIT''ed desde un mensaje de correo en la comunidad LLVM.
[LLVMdev] MCJIT y tutorial de caleidoscopio
Archivo de cabecera:
// foo.h
extern void foo(void);
y la función para una función foo () simple:
//foo.c
#include <stdio.h>
void foo(void) {
puts("Hello, I''m a shared library");
}
Y la función principal:
//main.c
#include <stdio.h>
#include "foo.h"
int main(void) {
puts("This is a shared library test...");
foo();
return 0;
}
Construye la biblioteca compartida usando foo.c:
gcc foo.c -shared -o libfoo.so -fPIC
Genere el código de bits LLVM para el archivo main.c:
clang -Wall -c -emit-llvm -O3 main.c -o main.bc
Y ejecute el código de bits LLVM a través de jit (y MCJIT) para obtener el resultado deseado:
lli -load=./libfoo.so main.bc
lli -use-mcjit -load=./libfoo.so main.bc
También puede canalizar la salida de clang en lli:
clang -Wall -c -emit-llvm -O3 main.c -o - | lli -load=./libfoo.so
Salida
This is a shared library test...
Hello, I''m a shared library
Fuente obtenida de
La mayoría de los compiladores tienen un extremo delantero, algún código / estructura central de algún tipo, y el backend. Cuando toma su programa C y usa el clang y la compilación de manera que termina con un programa x86 no JIT que puede ejecutar, todavía ha pasado de frontend a middle a backend. Lo mismo ocurre con gcc, gcc va desde la parte frontal hasta la mitad y la parte posterior. La cosa intermedia de Gccs no está totalmente abierta y no se puede utilizar como lo es como LLVM.
Ahora, una cosa que es divertida / interesante sobre llvm, que no puede hacer con otros, o al menos gcc, es que puede tomar todos sus módulos de código fuente, compilarlos en el bytecode de llvms, combinarlos en un gran archivo de bytecode, luego optimice todo, en lugar de la optimización por archivo o por función que obtiene con otros compiladores, con llvm puede obtener cualquier nivel de optimización de programa parcial o compilado que desee. luego puede tomar ese bytecode y usar llc para exportarlo al ensamblador de objetivos. Normalmente lo hago incrustado, así que tengo mi propio código de inicio, pero en teoría debería poder tomar ese archivo de ensamblador y con gcc compilar, vincularlo y ejecutarlo. gcc myfile.s -o myfile. Me imagino que hay una manera de hacer que las herramientas llvm hagan esto y no tengan que usar binutils o gcc, pero no me he tomado el tiempo.
Me gusta llvm porque siempre es un compilador cruzado, a diferencia de gcc, no tienes que compilar uno nuevo para cada objetivo y lidiar con los matices de cada objetivo. No sé si tengo algún uso para el JIT, es lo que estoy diciendo que lo uso como compilador cruzado y como compilador nativo.
Entonces, su primer caso es el frente, medio, final y el proceso está oculto para usted, comienza con la fuente y obtiene un binario, hecho. El segundo caso es si entiendo bien el frente y el medio y me detengo con un archivo que representa el medio. Luego, el punto medio al final (el procesador objetivo específico) puede ocurrir justo a tiempo en el tiempo de ejecución. La diferencia es que el backend, la ejecución en tiempo real del lenguaje intermedio del caso dos, es probablemente diferente al backend del caso uno.
Primero, obtienes el código de bytes LLVM (LLVM IR):
clang -emit-llvm -S -o test.bc test.c
En segundo lugar, utiliza LLVM JIT:
lli test.bc
Que ejecuta el programa.
Entonces, si desea obtener nativo, utiliza el backend LLVM:
llc test.bc
Desde la salida de montaje:
as test.S