Clang:compilación de un encabezado C en LLVM IR/bitcode

 C Programming >> Programación C >  >> Tags >> Clang
Clang:compilación de un encabezado C en LLVM IR/bitcode


Digamos que tengo el siguiente archivo de encabezado C trivial:


// 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 parezca a esto :


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

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


Pasar esto a través de Clang para generar LLVM IR produce un módulo vacío (ya que ninguna de las definiciones se usa realmente):


$ 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 me encontré con dos preguntas relacionadas:una de una lista de correo y otra de StackOverflow. Ambos sugirieron usar el -femit-all-decls bandera, así que probé 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 intenté deshabilitar las optimizaciones (ambas con -O0 y -disable-llvm-optzns ), pero eso no hizo ninguna diferencia para la salida. Usando la siguiente variación did producir 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);
}

Luego ejecutando:


$ 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 del marcador de posición doThings , ¡así 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 por adelantado. Lo que me lleva a...


¿Por qué?


Básicamente, estoy construyendo una implementación para un lenguaje usando LLVM para generar código. La implementación debe admitir la interoperabilidad de C mediante la especificación de archivos de encabezado de C y bibliotecas asociadas únicamente (sin declaraciones manuales), que luego utilizará el compilador 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/código de bits, que luego puede obtener la firma de tipo de cada función

  2. Utilice libclang para analizar los encabezados, luego consultar 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 foo1.h anterior ) 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 construir un analizador C)


Respuestas:


Quizás la solución menos elegante, pero manteniendo la idea de un doThings función que obliga 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 pueden superarse de forma relativamente sencilla:



  1. En lugar de compilar el encabezado directamente, #include (o más probablemente, una versión preprocesada del mismo, o múltiples encabezados) desde un archivo .c que contiene 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 esa complejidad que tiene en el código de "usos" anterior. En realidad, no es necesario que recopile las firmas de funciones usted mismo .


    Todo lo que necesita es la lista de los nombres mismos 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 que se vea 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 enormemente 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} (instanciandolo independientemente de lo que sea ). Esto significa que no necesita almacenar ningún tipo de información real, solo el tipo de contexto de donde extrajiste el nombre en el encabezado.


    (Obviamente, dé a la función ficticia y a los tipos de marcadores de posición nombres exclusivos extendidos para que no entren en conflicto con el código que realmente desea conservar)



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



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



  • grep a través de los encabezados para #include directivas que toman un argumento entre paréntesis angulares (es decir, un encabezado instalado para el que no desea generar declaraciones).

  • utilice esta lista para crear una carpeta de inclusión ficticia con todos los archivos de inclusión necesarios presentes pero vacíos

  • preprocesarlo con la esperanza de simplificar 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 análisis ridículamente simplificado para crear la lista de usos en la función ficticia y emita el código para el archivo .c.


No captará todas las posibilidades; pero con un poco de ajuste y extensión, probablemente se ocupará de un gran subconjunto de código de encabezado realista. Puede reemplazar esto con un analizador simplificado dedicado (uno creado para ver solo los patrones de los contextos que necesita) en una etapa posterior.


Algunas respuestas de código


// foo1.h typedef int foo;
typedef struct { foo a;
char const* b;
} bar;
bar baz(foo*, bar*, ...);
%struct.bar = type { i32, i8* } declare { i32, i8* } @baz(i32*, %struct.bar*, ...) 
$ 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)"}
$ 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)"}
// 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);
}
$ 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)"}
// foo.c #include "foo.h" void doThings(void) {
... }
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 },
};
}