Clang - Compilazione di un'intestazione C in LLVM IR/bitcode

Clang - Compilazione di un'intestazione C in LLVM IR/bitcode


Supponiamo di avere il seguente banale file di intestazione C:


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

Il mio obiettivo è prendere questo file e produrre un modulo LLVM che assomigli a questo :


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

In altre parole, converti un C .h file con dichiarazioni nell'IR LLVM equivalente, inclusa la risoluzione del tipo, l'espansione della macro e così via.


Il passaggio attraverso Clang per generare LLVM IR produce un modulo vuoto (poiché nessuna delle definizioni viene effettivamente utilizzata):


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

Il mio primo istinto è stato quello di rivolgermi a Google e mi sono imbattuto in due domande correlate:una da una mailing list e una da StackOverflow. Entrambi hanno suggerito di utilizzare il -femit-all-decls flag, quindi l'ho provato:


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

Stesso risultato.


Ho anche provato a disabilitare le ottimizzazioni (entrambe con -O0 e -disable-llvm-optzns ), ma ciò non ha fatto alcuna differenza per l'output. Utilizzando la seguente variazione fatto produrre l'IR desiderato:


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

Quindi in esecuzione:


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

Oltre al segnaposto doThings , questo è esattamente come voglio che assomigli all'output! Il problema è che ciò richiede 1.) l'utilizzo di una versione modificata dell'intestazione e 2.) la conoscenza del tipo di cose in anticipo. Il che mi porta a...


Perché?


Fondamentalmente, sto costruendo un'implementazione per un linguaggio che utilizza LLVM per generare codice. L'implementazione dovrebbe supportare l'interoperabilità C specificando solo i file di intestazione C e le librerie associate (nessuna dichiarazione manuale), che verrà quindi utilizzata dal compilatore prima del collegamento per garantire che le chiamate di funzione corrispondano alle loro firme. Quindi, ho ristretto il problema a 2 possibili soluzioni:



  1. Trasforma i file di intestazione in LLVM IR/bitcode, che può quindi ottenere la firma del tipo di ciascuna funzione

  2. Usa libclang per analizzare le intestazioni, quindi interrogare i tipi dall'AST risultante (la mia "ultima risorsa" nel caso non ci sia una risposta sufficiente per questa domanda)


TL;DR


Devo prendere un file di intestazione C (come il foo1.h sopra). ) e, senza modificarlo, genera il suddetto LLVM IR previsto utilizzando Clang, OR, trova un altro modo per ottenere le firme delle funzioni dai file di intestazione C (preferibilmente usando libclang o costruire un parser C)


Risposte:


Forse la soluzione meno elegante, ma restando nell'idea di un doThings funzione che forza il compilatore a emettere IR perché vengono utilizzate le definizioni:


I due problemi che identifichi con questo approccio sono che richiede la modifica dell'intestazione e che richiede una comprensione più profonda dei tipi coinvolti per generare "usi" da inserire nella funzione. Entrambi possono essere superati in modo relativamente semplice:



  1. Invece di compilare direttamente l'intestazione, #include esso (o più probabilmente, una sua versione preelaborata o più intestazioni) da un file .c che contiene tutto il codice "usa". Abbastanza semplice:


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

  2. Non hai bisogno di informazioni dettagliate sul tipo per generare usi specifici dei nomi, abbinare le istanze di struct ai parametri e tutta quella complessità che hai nel codice "usa" sopra. In realtà non è necessario raccogliere personalmente le firme delle funzioni .


    Tutto ciò che serve è l'elenco dei nomi stessi e tenere traccia se sono per una funzione o per un tipo di oggetto. Puoi quindi ridefinire la tua funzione "usa" in modo che assomigli a questa:


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

    Ciò semplifica notevolmente gli "usi" necessari di un nome per eseguirne il cast a un tipo di funzione uniforme (e prendere il suo puntatore anziché chiamarlo) o avvolgerlo in &( e ){0} (istanziandolo indipendentemente da cosa sia ). Ciò significa che non è necessario memorizzare le informazioni sul tipo effettivo, ma solo il tipo di contesto da cui hai estratto il nome nell'intestazione.


    (ovviamente dai alla funzione dummy e ai tipi di segnaposto nomi univoci estesi in modo che non entrino in conflitto con il codice che vuoi effettivamente mantenere)



Ciò semplifica enormemente il passaggio di analisi poiché devi solo riconoscere il contesto di una struttura/unione o una dichiarazione di funzione, senza effettivamente dover fare molto con le informazioni circostanti.



Un punto di partenza semplice ma hackish (che probabilmente userei perché ho standard bassi :D ) potrebbe essere:



  • scorri le intestazioni di #include direttive che accettano un argomento tra parentesi angolari (ovvero un'intestazione installata per la quale non si desidera generare dichiarazioni).

  • usa questo elenco per creare una cartella di inclusione fittizia con tutti i file di inclusione necessari presenti ma vuoti

  • preelaboralo nella speranza che semplifichi la sintassi (clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h o qualcosa di simile)

  • passa attraverso struct o union seguito da un nome, } seguito da un nome o name ( , e usa questo non-parse ridicolmente semplificato per costruire l'elenco degli usi nella funzione fittizia ed emettere il codice per il file .c.


Non catturerà tutte le possibilità; ma con un po' di modifiche ed estensioni, probabilmente si occuperà effettivamente di un ampio sottoinsieme di codice di intestazione realistico. Potresti sostituirlo con un parser semplificato dedicato (costruito per guardare solo i modelli dei contesti di cui hai bisogno) in una fase successiva.


Alcune risposte al codice


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