compiler c++ c clang llvm llvm-ir

c++ - compiler - Clang-Compilando un encabezado C a LLVM IR/bitcode



llvm compiler download (1)

Digamos que tengo el siguiente archivo de cabecera trivial C:

// foo1.h typedef int foo; typedef struct { foo a; char const* b; } bar; bar baz(foo*, bar*, ...);

Mi objetivo es tomar este archivo y producir un módulo LLVM que se parece a esto :

%struct.bar = type { i32, i8* } declare { i32, i8* } @baz(i32*, %struct.bar*, ...)

En otras palabras, convierta un archivo C .h con declaraciones en el LLVM IR equivalente, incluida la resolución de tipos, la expansión de macros, etc.

Al pasar esto a través de Clang para generar LLVM, IR genera un módulo vacío (ya que no se usa ninguna de las definiciones):

$ clang -cc1 -S -emit-llvm foo1.h -o - ; ModuleID = ''foo1.h'' target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-darwin13.3.0" !llvm.ident = !{!0} !0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}

Mi primer instinto fue recurrir a Google, y encontré dos preguntas relacionadas: una de una lista de correo y otra de StackOverflow . Ambos sugirieron usar la -femit-all-decls , así que intenté eso:

$ clang -cc1 -femit-all-decls -S -emit-llvm foo1.h -o - ; ModuleID = ''foo1.h'' target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-darwin13.3.0" !llvm.ident = !{!0} !0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}

Mismo resultado

También he intentado deshabilitar las optimizaciones (con -O0 y -disable-llvm-optzns ), pero eso no hizo ninguna diferencia en la salida. Usando la siguiente variación produjo el IR deseado:

// foo2.h typedef int foo; typedef struct { foo a; char const* b; } bar; bar baz(foo*, bar*, ...); void doThings() { foo a = 0; bar myBar; baz(&a, &myBar); }

Entonces corriendo:

$ clang -cc1 -S -emit-llvm foo2.h -o - ; ModuleID = ''foo2.h'' target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-apple-darwin13.3.0" %struct.bar = type { i32, i8* } ; Function Attrs: nounwind define void @doThings() #0 { entry: %a = alloca i32, align 4 %myBar = alloca %struct.bar, align 8 %coerce = alloca %struct.bar, align 8 store i32 0, i32* %a, align 4 %call = call { i32, i8* } (i32*, %struct.bar*, ...)* @baz(i32* %a, %struct.bar* %myBar) %0 = bitcast %struct.bar* %coerce to { i32, i8* }* %1 = getelementptr { i32, i8* }* %0, i32 0, i32 0 %2 = extractvalue { i32, i8* } %call, 0 store i32 %2, i32* %1, align 1 %3 = getelementptr { i32, i8* }* %0, i32 0, i32 1 %4 = extractvalue { i32, i8* } %call, 1 store i8* %4, i8** %3, align 1 ret void } declare { i32, i8* } @baz(i32*, %struct.bar*, ...) #1 attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" } attributes #1 = { "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" } !llvm.ident = !{!0} !0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}

Además de las doThings los marcadores de posición, ¡esto es exactamente como quiero que se vea la salida! El problema es que esto requiere 1.) usar una versión modificada del encabezado, y 2.) conocer los tipos de cosas de antemano. Lo que me lleva a ...

¿Por qué?

Básicamente, estoy creando una implementación para un lenguaje que utiliza LLVM para generar código. La implementación debe admitir la interoperabilidad de C especificando los archivos de encabezado de C y las bibliotecas asociadas únicamente (sin declaraciones manuales), que luego el compilador utilizará antes del tiempo de enlace para garantizar que las invocaciones de funciones coincidan con sus firmas. Por lo tanto, he reducido el problema a 2 posibles soluciones:

  1. Convierta los archivos de encabezado en LLVM IR / bitcode, que luego puede obtener la firma de tipo de cada función
  2. Use libclang para analizar los encabezados, luego consulte los tipos del AST resultante (mi ''último recurso'' en caso de que no haya una respuesta suficiente para esta pregunta)

TL; DR

Necesito tomar un archivo de encabezado C (como el anterior foo1.h ) y, sin cambiarlo, generar el LLVM IR esperado mencionado anteriormente usando Clang, O, encontrar otra forma de obtener firmas de funciones de los archivos de encabezado C (preferiblemente usando libclang o construyendo un analizador C)


Quizás la solución menos elegante, pero doThings la idea de una función doThings que obligue al compilador a emitir IR porque se usan las definiciones:

Los dos problemas que identifica con este enfoque son que requiere modificar el encabezado y que requiere una comprensión más profunda de los tipos involucrados para generar "usos" para poner en la función. Ambos de estos pueden superarse de manera relativamente simple:

  1. En lugar de compilar el encabezado directamente, #include (o más probablemente, una versión preprocesada del mismo, o varios encabezados) de un archivo .c que contenga todo el código de "usos". Bastante sencillo:

    // foo.c #include "foo.h" void doThings(void) { ... }

  2. No necesita información de tipo detallada para generar usos específicos de los nombres, haciendo coincidir las instancias de estructura con los parámetros y toda la complejidad que tiene en el código de "usos" anterior. En realidad no es necesario reunir las firmas de la función usted mismo .

    Todo lo que necesita es la lista de los propios nombres y realizar un seguimiento de si son para una función o para un tipo de objeto. Luego puede redefinir su función de "usos" para verse así:

    void * doThings(void) { typedef void * (*vfun)(void); typedef union v { void * o; vfun f; } v; return (v[]) { (v){ .o = &(bar){0} }, (v){ .f = (vfun)baz }, }; }

    Esto simplifica en gran medida los "usos" necesarios de un nombre para convertirlo en un tipo de función uniforme (y tomar su puntero en lugar de llamarlo), o envolverlo en &( y ){0} (crear una instancia de él independientemente de lo que sea ). Esto significa que no necesita almacenar información de tipo real, solo el tipo de contexto del cual extrajo el nombre en el encabezado.

    (Obviamente, asigne la función ficticia y los marcadores de posición amplían los nombres únicos para que no coincidan con el código que realmente desea conservar)

Esto simplifica enormemente el paso del análisis, ya que solo tiene que reconocer el contexto de una declaración de estructura / unión o función, sin que realmente tenga que hacer mucho con la información que lo rodea.

Un punto de partida simple pero truculento (que probablemente usaría porque tengo estándares bajos: D) podría ser:

  • grep a través de los encabezados para las directivas #include que toman un argumento entre corchetes (es decir, un encabezado instalado para el que no quiere generar declaraciones).
  • use esta lista para crear una carpeta de inclusión ficticia con todos los archivos de inclusión necesarios presentes pero vacíos
  • preproceselo con la esperanza de que simplifique la sintaxis ( clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h o algo similar)
  • grep a través de struct o union seguido de un nombre, } seguido de un nombre, o name ( , y use este no parse ridículamente simplificado para crear la lista de usos en la función ficticia, y emitir el código para el archivo .c).

No atrapará todas las posibilidades; pero con un poco de ajustes y extensiones, probablemente se ocupará de un gran subconjunto de código de encabezado realista. Podría reemplazarlo con un analizador simplificado dedicado (uno creado para observar solo los patrones de los contextos que necesita) en una etapa posterior.