プログラミングについてのWiki。

ネイティブコンパイラ作成を支援するライブラリです。 ⇒ 関連日記

注意事項

  • C#のネイティブコンパイラではありません。C#で作ったネイティブコンパイラ作成用のサポートライブラリです。
  • 言語処理系を含まないため、特定の言語(C言語、C#等)をコンパイルすることはできません。
  • 言語処理系を作成する場合、下層のリンク処理等をCompilerLibに丸投げすれば、PEの細かいことを気にせずにネイティブコンパイラを作ることができます。CompilerLibはそのためのライブラリです。
  • C#で作ったプログラムを.NETなしにネイティブで動かすように変換することはできません。将来的にサポートする予定もありません。C#のAoTコンパイラを期待された方にはごめんなさい。
    • 応用でC#のAoTコンパイラを作ることは可能です。原理的にはCILを抜き出して、x86コードを生成してCompilerLibに渡せばできるはずです。ただし説明は簡単ですがやるととんでもなく難しいので、現時点では開発する予定はありません。

言語処理系


CompilerLibを利用した言語処理系です。

ダウンロード

過去のリリース

今後の課題

  • アセンブラの充実
    • LLPML 1.0で必要になったニーモニックとオペランドの組み合わせしかサポートされていません。
    • MMX/SSE2命令はある程度実装されていますが、FPU命令はまったく実装されていません。
    • まともに使うには整備する必要があります。
  • 自分自身の記述
    • C#での機能拡張はほどほどにして、言語処理系を実装して自分自身を記述します。 ⇒ Eating one's own dog food
  • x64対応
    • いまどきこんなレイヤで作業をするなら、そろそろ手を出す時期でしょう。


コンパイラの内部処理用ライブラリを想定しているため、言語処理系を含んでいません。テキストではなくオブジェクトを渡します。アセンブリをコードで組み立てて出力するというイメージです。

以下の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を利用したコードは以下のようになります。

var module = new Module();
var c = new List<OpCode>();

const int STD_OUTPUT_HANDLE = -11;
var GetStdHandle = module.GetFunction(CallType.Std, "kernel32.dll", "GetStdHandle");
var WriteConsole = module.GetFunction(CallType.Std, "kernel32.dll", "WriteConsoleW");
var ExitProcess = module.GetFunction(CallType.Std, "kernel32.dll", "ExitProcess");

// HANDLE stdout = GetStdHandle(STD_OUTPUT_HANDLE);
var stdout = new Addr32(module.GetInt32("stdout"));
c.AddRange(GetStdHandle.Invoke(STD_OUTPUT_HANDLE));
c.Add(I386.Mov(stdout, Reg32.EAX));

var dummy = module.GetInt32("dummy");
var 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");

コード記述方法


CompilerLibではオブジェクトベースでコードを記述します。
変数

Moduleインスタンスからグローバル変数のポインタを取得するのがModule.GetInt32()です。ポインタが指す値はAddr32クラスを通して扱います。C++と対比させたイメージを示します。

【C++】
extern int g_abc;
int *p_abc = &g_abc;
int &abc = *p_abc;

【CompilerLib】
var p_abc = module.GetInt32("g_abc");
var 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));

ラベル

以下のアセンブリと同等のものを作成します。
XORやLOOPでコードを節約できますが、ここでは使用しません。

    mov eax, 0
    mov ecx, 100
label:
    add eax, ecx
    dec ecx
    jnz label

CompilerLibを利用したコードは以下のようになります。

var c = new List<OpCode>();
c.Add(I386.Mov(Reg32.EAX, 0));
c.Add(I386.Mov(Reg32.ECX, 100));
var 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を作成して、
アドレスを取得するためだけに使用しています。

Wiki内検索

Menu

ここは自由に編集できるエリアです。

管理人/副管理人のみ編集できます