Clang - C ヘッダーを LLVM IR/ビットコードにコンパイルする



次の単純な C ヘッダー ファイルがあるとします:


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

私の目標は、このファイルを取得して、次のような LLVM モジュールを作成することです :


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

つまり、C の .h を変換します。 型解決、マクロ展開などを含む、同等の LLVM IR への宣言を含むファイル。


これを Clang に渡して LLVM IR を生成すると、空のモジュールが生成されます (定義は実際には使用されないため):


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

私の最初の本能は Google に目を向けることでした。そして、関連する 2 つの質問に出くわしました。1 つはメーリング リストから、もう 1 つは StackOverflow からのものです。どちらも -femit-all-decls を使用することを提案しました フラグなので、試してみました:


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

同じ結果です。


また、最適化を無効にしてみました (両方とも -O0 を使用) と -disable-llvm-optzns )、しかしそれは出力に違いはありませんでした。次のバリエーションを使用すると d 目的の 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);
}

次に実行:


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

プレースホルダーのほかに doThings 、これはまさに私が出力をどのように見せたいかです!問題は、これには 1.) 変更されたバージョンのヘッダーを使用すること、および 2.) 事前に物事の種類を知っていることが必要なことです。


なぜですか?


基本的に、LLVM を使用してコードを生成する言語の実装を構築しています。実装は、C ヘッダー ファイルと関連するライブラリのみ (手動宣言なし) を指定することによって C 相互運用をサポートする必要があります。これは、関数呼び出しが署名と一致することを保証するために、リンク時の前にコンパイラによって使用されます。したがって、問題を 2 つの可能な解決策に絞り込みました:



  1. ヘッダー ファイルを LLVM IR/ビットコードに変換します。これにより、各関数の型シグネチャを取得できます

  2. libclang を使用 ヘッダーを解析し、結果の AST から型をクエリします (この質問に対する十分な回答がない場合の「最後の手段」)


TL;DR


C ヘッダー ファイル (上記の foo1.h など) を取得する必要があります ) を変更せずに、Clang を使用して前述の予想される LLVM IR を生成するか、C ヘッダー ファイルから関数シグネチャを取得する別の方法を見つけます (できれば libclang を使用してください) または C パーサーの構築)


答え:


おそらくあまり洗練されていない解決策ですが、 doThings のアイデアにとどまります 定義が使用されているため、コンパイラに強制的に IR を発行させる関数:


このアプローチで特定された 2 つの問題は、ヘッダーを変更する必要があることと、関数に配置する "使用" を生成するために関連する型をより深く理解する必要があることです。これらはどちらも比較的簡単に克服できます:



  1. ヘッダーを直接コンパイルする代わりに、#include すべての「使用」コードを含む .c ファイルからそれ (または、より可能性が高いのは、その前処理されたバージョンまたは複数のヘッダー) です。簡単に:


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

  2. 名前の特定の使用法を生成するために詳細な型情報は必要ありません。構造体のインスタンス化をパラメーターに一致させ、上記の「uses」コードにあるようなすべての複雑さを実現します。 実際に関数シグネチャを自分で収集する必要はありません .


    必要なのは、名前自体のリストと、それらが関数用かオブジェクト型用かを追跡することだけです。次に、「uses」関数を次のように再定義できます。


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

    これにより、必要な名前の「使用」が大幅に簡素化され、名前を統一された関数型にキャストする (呼び出しではなくポインターを取得する) か、&( でラップすることができます。 と ){0} (それが何であるかに関係なくインスタンス化する )。つまり、実際の型情報を保存する必要はまったくなく、context の種類だけを保存する必要があります。 ヘッダーの名前を抽出したもの。


    (明らかに、ダミー関数とプレースホルダー型に拡張された一意の名前を付けて、実際に保持したいコードと衝突しないようにしてください)



これにより、構造体/共用体または関数宣言のコンテキストを認識するだけでよく、実際に周囲の情報をあまり処理する必要がないため、解析手順が大幅に簡素化されます。



シンプルだがハックな出発点 (私は基準が低いので、おそらくこれを使用します :D ) は次のようになります:



  • #include のヘッダーを grep します 山かっこで囲まれた引数を取るディレクティブ (つまり、宣言も生成したくないインストール済みヘッダー)。

  • このリストを使用して、必要なすべてのインクルード ファイルが存在するが空のダミー インクルード フォルダを作成します

  • 構文を簡素化することを期待して前処理します (clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h または類似のもの)

  • struct を検索する または union 後に名前 } が続きます 後に名前、または name ( が続きます 、そしてこのばかばかしいほど単純化された非解析を使用して、ダミー関数で使用のリストを作成し、.c ファイルのコードを出力します。


すべての可能性を捉えることはできません。しかし、少し調整と拡張を行うことで、現実的なヘッダー コードの大部分を実際に処理できるようになるでしょう。後の段階で、これを専用の単純化されたパーサー (必要なコンテキストのパターンのみを調べるように構築されたパーサー) に置き換えることができます。


いくつかのコードの回答


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