Clang – Kompilieren eines C-Headers zu LLVM IR/Bitcode

Clang – Kompilieren eines C-Headers zu LLVM IR/Bitcode


Angenommen, ich habe die folgende triviale C-Header-Datei:


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

Mein Ziel ist es, diese Datei zu nehmen und ein LLVM-Modul zu erstellen, das in etwa so aussieht :


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

Mit anderen Worten, konvertieren Sie ein C .h Datei mit Deklarationen in die entsprechende LLVM-IR, einschließlich Typauflösung, Makroerweiterung usw.


Wenn Sie dies durch Clang leiten, um LLVM IR zu generieren, wird ein leeres Modul erzeugt (da keine der Definitionen tatsächlich verwendet wird):


$ 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)"}

Mein erster Instinkt war, mich an Google zu wenden, und ich stieß auf zwei verwandte Fragen:eine von einer Mailingliste und eine von StackOverflow. Beide schlugen vor, die -femit-all-decls zu verwenden flag, also habe ich das versucht:


$ 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)"}

Gleiches Ergebnis.


Ich habe auch versucht, Optimierungen zu deaktivieren (beide mit -O0 und -disable-llvm-optzns ), aber das machte keinen Unterschied für die Ausgabe. Mit der folgenden Variante tat erzeugen Sie die gewünschte IR:


// 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);
}

Dann läuft:


$ 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)"}

Neben dem Platzhalter doThings , genau so soll die Ausgabe aussehen! Das Problem ist, dass dies 1.) die Verwendung einer modifizierten Version des Headers und 2.) die Kenntnis der Arten von Dingen im Voraus erfordert. Was mich zu...

führt

Warum?


Im Grunde baue ich eine Implementierung für eine Sprache, die LLVM verwendet, um Code zu generieren. Die Implementierung sollte C-Interop unterstützen, indem nur C-Header-Dateien und zugehörige Bibliotheken (keine manuellen Deklarationen) angegeben werden, die dann vom Compiler vor der Verknüpfungszeit verwendet werden, um sicherzustellen, dass Funktionsaufrufe mit ihren Signaturen übereinstimmen. Daher habe ich das Problem auf 2 mögliche Lösungen eingegrenzt:



  1. Wandle die Header-Dateien in LLVM IR/Bitcode um, der dann die Typsignatur jeder Funktion erhalten kann

  2. Verwenden Sie libclang um die Header zu parsen und dann die Typen aus dem resultierenden AST abzufragen (mein "letzter Ausweg", falls es keine ausreichende Antwort auf diese Frage gibt)


TL;DR


Ich muss eine C-Header-Datei nehmen (wie die obige foo1.h ) und, ohne es zu ändern, die oben erwähnte erwartete LLVM-IR mit Clang generieren, ODER, einen anderen Weg finden, um Funktionssignaturen aus C-Header-Dateien zu erhalten (am besten mit libclang oder Erstellen eines C-Parsers)


Antworten:


Vielleicht die weniger elegante Lösung, aber bei der Idee eines doThings bleibend Funktion, die den Compiler zwingt, IR auszugeben, weil die Definitionen verwendet werden:


Die beiden Probleme, die Sie mit diesem Ansatz identifizieren, sind, dass der Header geändert werden muss und dass ein tieferes Verständnis der beteiligten Typen erforderlich ist, um "Verwendungen" zum Einfügen der Funktion zu generieren. Beides kann relativ einfach überwunden werden:



  1. Anstatt den Header direkt zu kompilieren, #include es (oder wahrscheinlicher eine vorverarbeitete Version davon oder mehrere Header) aus einer .c-Datei, die den gesamten "uses"-Code enthält. Einfach genug:


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

  2. Sie benötigen keine detaillierten Typinformationen, um spezifische Verwendungen der Namen zu generieren, Strukturinstanzen mit Parametern abzugleichen und all diese Komplexität, wie Sie sie im obigen "uses" -Code haben. Sie müssen die Funktionssignaturen eigentlich nicht selbst sammeln .


    Alles, was Sie brauchen, ist die Liste der Namen selbst und um zu verfolgen, ob sie für eine Funktion oder für einen Objekttyp sind. Anschließend können Sie Ihre „Uses“-Funktion so umdefinieren, dass sie wie folgt aussieht:


    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 },
    };
    }

    Dies vereinfacht die notwendigen "Verwendungen" eines Namens erheblich, um ihn entweder in einen einheitlichen Funktionstyp umzuwandeln (und seinen Zeiger zu nehmen, anstatt ihn aufzurufen) oder ihn in &( zu verpacken und ){0} (instanziieren unabhängig davon, was es ist ). Das bedeutet, dass Sie überhaupt keine tatsächlichen Typinformationen speichern müssen, sondern nur die Art des Kontexts aus der Sie den Namen im Header extrahiert haben.


    (Geben Sie der Dummy-Funktion und den Platzhaltertypen natürlich erweiterte eindeutige Namen, damit sie nicht mit dem Code kollidieren, den Sie tatsächlich behalten möchten)



Dies vereinfacht den Parsing-Schritt enorm, da Sie nur den Kontext einer Struct/Union- oder Funktionsdeklaration erkennen müssen, ohne sich wirklich viel mit den umgebenden Informationen zu tun zu müssen.



Ein einfacher, aber hackiger Ausgangspunkt (den ich wahrscheinlich verwenden würde, weil ich niedrige Standards habe :D ) könnte sein:



  • Durchsuchen Sie die Header nach #include Direktiven, die ein Argument in spitzen Klammern annehmen (d. h. einen installierten Header, für den Sie nicht auch Deklarationen generieren möchten).

  • verwenden Sie diese Liste, um einen Dummy-Include-Ordner zu erstellen, in dem alle erforderlichen Include-Dateien vorhanden, aber leer sind

  • vorverarbeiten in der Hoffnung, dass dies die Syntax vereinfacht (clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h oder ähnliches)

  • Suche nach struct oder union gefolgt von einem Namen, } gefolgt von einem Namen oder name ( , und verwenden Sie dieses lächerlich vereinfachte Non-Parse, um die Liste der Verwendungen in der Dummy-Funktion zu erstellen und den Code für die .c-Datei auszugeben.


Es wird nicht jede Möglichkeit erfassen; aber mit ein wenig Optimierung und Erweiterung wird es wahrscheinlich tatsächlich mit einer großen Teilmenge realistischen Header-Codes fertig. Sie könnten dies zu einem späteren Zeitpunkt durch einen dedizierten vereinfachten Parser ersetzen (einen, der so gebaut ist, dass er nur die Muster der von Ihnen benötigten Kontexte betrachtet).


Einige Code-Antworten


// 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 },
};
}