Lumber room
July 2010 29

[C#/.NET] Генерируем машинный код с помощью LLVM

В этом топике я покажу, как без особых трудов сгенерировать и выполнить машинный код с помощью Low Level Virtual Machine на примере функции, вычисляющей ответ на главный вопрос жизни, вселенной и всего такого.

А для работы нам понадобятся

Предварительно скомпилированные версии LLVM в виде Windows DLL. CodePlex
Их нужно скопировать в соответствующие системные папки или в папки, где будет лежать исполняемый файл Вашего тестового проекта.

Исходный код биндинга, написанный на C#.
Mercurial: https://hg01.codeplex.com/codeblock
Исходный код на Codeplex

Собственно из исходного кода понадобится только проект LLVM, лежащий, соответсвтенно, в папке LLVM. Обратите внимание, проект создан в Visual Studio 2010 и нацелен на .NET 4.0, но из тех же .cs файлов можно собрать биндинг и для старой версии .NET

Приступаем к работе


Создайте новый консольный проект C#, добавьте в решение проект LLVM. Из своего проекта сделайте ссылку на LLVM.

Добавьте в заголовок Program.cs (так по умолчанию называется файл исходного кода) ссылку на соответствующее пространство имён:
using LLVM;
using Type = LLVM.Type;

Теперь можно начинать работу с LLVM.

Инициализация целевой платформы и создание общих элементов


Объявите статические переменные context типа Context, module типа Module и engine типа ExecutionEngine.
static Context context;
static Module module;
static ExecutionEngine engine;

Для чего нужен контекст на данном этапе не важно, поэтому мы просто пользуемся глобальным контекстом LLVM.
Модуль в LLVM — это некий аналог библиотеки. Он может содержать описания функции и статических переменных. При этом определения функций не обязаны лежать в том же модуле, т.е. он может иметь неразрешённые ссылки.
Execution engine — ответственнен за выполнение сгенерированного кода прямо во время исполнения программы. Может быть интерпретатором, либо компилятором.

Далее — инициализация необходимых структур для системы, в которой запущен LLVM, а также — собственно инициализация указанных объектов:
Console.Write("initializing native target...");
Target.InitializeNative();
Console.WriteLine("OK");

Console.Write("reading global context...");
context = Context.Global;
Console.WriteLine("OK");

Console.Write("creating module...");
module = new Module("test", context);
Console.WriteLine("OK");

Console.Write("creating execution engine...");
engine = new ExecutionEngine(module);
Console.WriteLine("OK");

Возвращаем 42


delegate int IntFunc();

private static void Function42()
{
 Console.Write("creating ret42...");
 // создаём в модуле функцию с именем ret42 типа int(void)
 var rettype = IntegerType.GetInt32(context);
 var intfunc = new FunctionType(rettype);
 var ret42 = module.CreateFunction("ret42", intfunc);
 // мы ходим вызвать нашу функцию из .NET, который использует stdcall
 ret42.CallingConvention = CallingConvention.StdCallX86;

 // функция состоит из блоков. любая инструкция перехода внутри функции должна вести на начало нового блока
 // завершающей инструкцией блока может быть только инструкция перехода
 var block = new Block("ret42root", context, ret42);
 // объявляем значение-константу 42
 var c42 = rettype.Constant(42, true);
 // для заполнения блока используется экземпляр специального класса
 // по умолчанию новые инструкции будут последовательно вставляться в указанный блок
 var instructions = new InstructionBuilder(context, block);
 // генерируем инструкцию, возвращающую наш Ответ
 instructions.Return(c42);
 Console.WriteLine("OK");

 Console.Write("compiling ret42...");
 // эта функция - временный костыль, в данном случае в принципе не нужный
 // дело в том, что LLVM не имеет встроенных средств для передачи структур как значений
 // по сути он передаёт их так, словно параметр-структура - это
 // множество параметров-элементарных типов (то есть { i32 i32 } передастся как пара 32-битных int)
 var wrapper = engine.CreateWrapper(ret42, context, module);
 // получаем .NET-делегат для нашей функции. в этот момент LLVM обычно компилирует её в нативный код
 var fun = engine.GetDelegate<IntFunc>(wrapper);
 Console.WriteLine("OK");

 Console.Write("executing...");
 Console.WriteLine(func());
}


* This source code was highlighted with Source Code Highlighter.

+15
786 10
Comments 17
Top of the day