#topicpath #contents コンパイラ作成を支援するライブラリです。ライブラリはC#のマネージドコードですが、出力するのはネイティブバイナリです。([[関連日記>http://gir-lab.spaces.live.com/blog/cns!63A4C32EDA5CF2F7!487.entry]]) ** &color(red){注意事項}; [#rc2aec75] - C#のネイティブコンパイラではありません。C#で作ったネイティブコンパイラ作成用のサポートライブラリです。 -- 言語処理系を含まないため、特定の言語(C言語、C#等)をコンパイルすることはできません。 -- 逆に言うと、言語処理系だけを作って下層のリンク処理等をCompilerLibに丸投げすれば、PEの細かいことを気にせずにネイティブコンパイラを作ることができます。CompilerLibはそのためのライブラリです。 -- &color(red){''【追記】''};言語処理系として[[LLPML]]の開発を開始しました。 - C#で作ったプログラムを.NETなしにネイティブで動かすように変換することは&color(red){''できません''};。将来的にサポートする予定もありません。C#のAoTコンパイラを期待された方にはごめんなさい。 -- 応用でC#のAoTコンパイラを作ることは可能です。原理的には[[PE Analyzer]]でCILを抜き出して、x86コードを生成してCompilerLibに渡せばできるはずです。ただし説明は簡単ですがやるととんでもなく難しいので、現時点では開発する予定はありません。 ** 言語処理系 [#u4ccb418] CompilerLibを利用した言語処理系です。 - [[LLPML]] ** ダウンロード [#jdb13268] - ソース: &ref(CompilerLib-20070114.zip);, ラベルをサポート - ライセンス: パブリックドメイン #include(:VCS2005Exp,notitle) *** 過去のリリース [#s916288a] - ソース: &ref(CompilerLib-20070108.zip);, 簡単なEXEを出力 ** 今後の課題 [#cdae3d41] + アセンブラの充実 -- サンプルで使っているニーモニックとオペランドの組み合わせしかサポートされていません。まともに使うには整備する必要があります。 + 【済】%%ラベル%% //-- CompilerLibではアセンブリのラベルが扱えないため、条件分岐やループができません。オブジェクトベースで表現できるようにする必要があります。 + 自分自身の記述 -- C#での機能拡張はほどほどにして、オブジェクトをそのままXMLでシリアライズしたような言語処理系を実装して、早期に自分自身を記述します。 -- Eat your own dog food. + x64対応 -- いまどきこんなレイヤで作業をするなら、そろそろ手を出す時期でしょう。 ** 例 [#naa680e1] コンパイラの内部処理用ライブラリを想定しているため、言語処理系を含んでいません。テキストではなくオブジェクトを渡します。アセンブリをコードで組み立てて出力するというイメージです。 以下のC言語と同等のものを作成します。 #include <windows.h> int main() { HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE); const char *hello = "Hello, World!\r\n"; DWORD dummy; WriteConsole(stdout, hello, strlen(hello), &dummy, NULL); return 0; } CompilerLibを利用したコードは以下のようになります。 Module module = new Module(); List<OpCode> c = new List<OpCode>(); const int STD_OUTPUT_HANDLE = -11; Function GetStdHandle = module.GetFunction(CallType.Std, "kernel32.dll", "GetStdHandle"); Function WriteConsole = module.GetFunction(CallType.Std, "kernel32.dll", "WriteConsoleW"); Function ExitProcess = module.GetFunction(CallType.Std, "kernel32.dll", "ExitProcess"); // HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE); Addr32 stdout = new Addr32(module.GetInt32("stdout")); c.AddRange(GetStdHandle.Invoke(STD_OUTPUT_HANDLE)); c.Add(I386.Mov(stdout, Reg32.EAX)); Ref<uint> dummy = module.GetInt32("dummy"); string hello = "Hello, World!\r\n"; c.AddRange(WriteConsole.Invoke(stdout, hello, hello.Length, dummy, 0)); c.AddRange(ExitProcess.Invoke(0)); module.Text.OpCodes = c.ToArray(); module.Link("output.exe"); ** コード記述方法 [#d592462d] CompilerLibではオブジェクトベースでコードを記述します。 *** 変数 [#a79ec7ac] Moduleインスタンスからグローバル変数のポインタを取得するのがModule.GetInt32()です。ポインタが指す値はAddr32クラスを通して扱います。C++と対比させたイメージを示します。 【C++】 extern int g_abc; int *p_abc = &g_abc; int &abc = *p_abc; 【CompilerLib】 Ref<uint> p_abc = module.GetInt32("g_abc"); Addr32 abc = new Addr32(p_abc); アセンブラレベルでは、p_abcがEDXに割り当てられた場合、abcは[EDX]に相当します。アセンブリの[]で括ったアドレッシングがnew Addr32()に相当します。 【アセンブリ】 EDX → [EDX] 【C言語】 edx → *edx 【CompilerLib】 edx → new Addr32(edx) この操作はCompilerLibでオブジェクト化されたアセンブリに反映されます。 // mov eax, [0x12345678] I386.Mov(Reg32.EAX, new Addr32(0x12345678)); // mov edx, 0x12345678 I386.Mov(Reg32.EDX, 0x12345678); // mov eax, [edx] I386.Mov(Reg32.EAX, new Addr32(Reg32.EDX)); 【事実誤認のため削除】%%最後の例で[EDX]がnew Addr32(Reg32.EDX)のように対称形になっていないのは、オペランドに許されている組み合わせがレジスタ経由と即値では異なるためです。別の型になっていればI386.Mov()のオーバーロードで表現することができます。たとえばmov [0x12345678], 0x12345678のような組み合わせをコンパイル時に排除する一方で、mov [EDX], 0x12345678のような組み合わせを許容することができます。%% *** ラベル [#xd90f10f] 以下のアセンブリと同等のものを作成します。 XORやLOOPでコードを節約できますが、ここでは使用しません。 mov eax, 0 mov ecx, 100 label: add eax, ecx dec ecx jnz label CompilerLibを利用したコードは以下のようになります。 List<OpCode> c = new List<OpCode>(); c.Add(I386.Mov(Reg32.EAX, 0)); c.Add(I386.Mov(Reg32.ECX, 100)); OpCode label = new OpCode(); c.Add(label); c.Add(I386.Add(Reg32.EAX, Reg32.ECX)); c.Add(I386.Dec(Reg32.ECX)); c.Add(I386.Jnz(label.Address)); デフォルトコンストラクタで空のOpCodeを作成して、 アドレスを取得するためだけに使用しています。 ** コメント [#m2d815ec] #comment(below)