Создание действующего компилятора с помощью инфраструктуры LLVM. Часть 1

Построение собственного компилятора с помощью инфраструктуры LLVM и ее промежуточного представления (LLVM IR)

Инфраструктура компилятора LLVM предоставляет мощные возможности для оптимизации приложений вне зависимости от используемого языка программирования. В первой статье данного цикла из двух частей излагаются основы LLVM. Построение собственного компилятора стало еще проще!

Арпан Сен, технический директор, Synapti Computer Aided Design Pvt Ltd

Арпан Сен (Arpan Sen) – ведущий инженер, работающий над разработкой программного обеспечения в области автоматизации электронного проектирования. На протяжении нескольких лет он работал над некоторыми функциями UNIX, в том числе Solaris, SunOS, HP-UX и IRIX, а также Linux и Microsoft Windows. Он проявляет живой интерес к методикам оптимизации производительности программного обеспечения, теории графов и параллельным вычислениям. Арпан является аспирантов в области программных систем.



12.11.2012

LLVM (Low Level Virtual Machine) — это весьма мощная инфраструктура компилятора, предназначенная для оптимизации программ, написанных на предпочтительном для разработчика языке программирования, на этапах компиляции, связывания и исполнения. Инфраструктура LLVM работает на нескольких разных платформах. Ее основное достоинство — генерация кода, который исполняется с высокой скоростью.

Другие статьи данного цикла

Просмотреть другие статьи данного цикла: Создание действующего компилятора с помощью инфраструктуры LLVM.

В основе инфраструктуры LLVM лежит хорошо документированное т. н. "промежуточное представление" программного кода (intermediate representation, IR). В данной статье — которая является первой в цикле из двух частей — рассматриваются основы представления LLVM IR, а также некоторые релевантные тонкости. В процессе освоения предлагаемого материала мы построим генератор кода, который автоматизирует фазу генерации LLVM IR. При наличии генератора LLVM IR разработчику достаточно иметь т.н. "фронтенд" для своего предпочтительного языка программирования, чтобы получить полную систему (парсер (фронтенд) + генератор IR-кода + LLVM-бэкенд). Таким образом, построение собственного компилятора существенно упростилось.

Начало работы с LLVM

До начала работы вам необходимо осуществить компиляцию LLVM на своем компьютере для разработки (инструментальном компьютере). Соответствующая ссылка приведена в разделе Ресурсы. Примеры в данной статье базируются на LLVM версии 3.0. Два самых важных инструмента для завершающей сборки и установки кода LLVM — это llc и lli.

Инструменты llc и lli

LLVM — это виртуальная машина и, как таковая, она имеет собственное представление программы в виде промежуточного байт-кода. В конечном итоге нам необходимо скомпилировать байт-код LLVM в код на языке ассемблера для своей конкретной платформы. После этого мы сможем запустить этот код с помощью нативных ассемблера и компоновщика для этой платформы с целью генерации исполняемого кода, общих библиотек и т. д. Для преобразования байт-кода LLVM в код на языке ассемблера для конкретной платформы применяется инструмент llc (ссылка на дополнительную информацию по этому инструменту приведена в разделе Ресурсы). Возможность непосредственного исполнения порций байт-кода LLVM избавляет нас от необходимости дожидаться сбоев нативного исполняемого кода, чтобы осознать наличие ошибок в своей программе. Именно здесь оказывается полезным инструмент lli, поскольку он способен непосредственно исполнять байт-код (или с помощью интерпретатора, или с помощью встроенного JIT-компилятора (just-in-time). Ссылка на дополнительную информацию об инструменте lli приведена в разделе Ресурсы.

Пакет llvm-gcc

llvm-gcc— это модифицированная версия пакета gcc (GNU Compiler Collection), которая при исполнении с опциями -S -emit-llvm генерирует байт-код LLVM. После этого мы можем воспользоваться инструментом lli для исполнения этого байт-кода (другое название — код ассемблера LLVM). Ссылка на дополнительную информацию о llvm-gcc приведена в разделе Ресурсы. Если в вашей системе пакет llvm-gcc еще не установлен, вы сможете собрать его из исходных кодов. Ссылка на соответствующее пошаговое руководство приведена в разделе Ресурсы.


Построение приложения Hello World с помощью LLVM

Чтобы лучше понять LLVM, необходимо изучить промежуточное представление LLVM (LLVM IR) и его характерные особенности. Этот процесс аналогичен изучению очередного языка программирования. Если вы знакомы с языками C и C ++, а также с их индивидуальными особенностями, то у вас не будет никаких затруднений при изучении LLVM IR. В листинге 1 показана ваша первая программа, которая печатает фразу Hello World на консоли. Для компиляции этого кода используется пакет llvm-gcc.

Листинг 1. Знакомая программа Hello World
#include <stdio.h>
int main( )
{ 
  printf("Hello World!\n");
}

Для компиляции приведенного выше кода введите следующую команду.

Tintin.local# llvm-gcc helloworld.cpp -S -emit-llvm

После компиляции пакет llvm-gcc генерирует файл helloworld.s, который вы можете запустить с помощью инструмента lli для печати нашего сообщения на консоли. Инструмент lli используется следующим образом.

Tintin.local# lli helloworld.s
Hello, World

Теперь посмотрим на код ассемблера LLVM. Соответствующий код показан в листинге 2.

Листинг 2. Байт-код LLVM для программы Hello World
@.str = private constant [13 x i8] c"Hello World!\00", align 1 ;

define i32 @main() ssp {
entry:
  %retval = alloca i32
  %0 = alloca i32
  %"alloca point" = bitcast i32 0 to i32
  %1 = call i32 @puts(i8* getelementptr inbounds ([13 x i8]* @.str, i64 0, i64 0))
  store i32 0, i32* %0, align 4
  %2 = load i32* %0, align 4
  store i32 %2, i32* %retval, align 4
  br label %return
return:
  %retval1 = load i32* %retval
  ret i32 %retval1
}

declare i32 @puts(i8*)

Что такое LLVM IR

Инфраструктура LLVM содержит детально проработанный язык ассемблера (соответствующая ссылка приведена в разделе Ресурсы). До создания собственной версии программы Hello World, показанной выше, вам необходимо знать следующую информацию.

  • Комментарий в ассемблере LLVM начинается с символа (;) и продолжается вплоть до конца строки.
  • Глобальные идентификаторы начинаются с символа (@). Все имена функций и глобальных переменных также должны начинаться с символа (@).
  • Локальные идентификаторы в LLVM начинаются с символа (%). Типичное регулярное выражение для идентификаторов имеет вид: [%@][a-zA-Z$._][a-zA-Z$._0-9]*.
  • В LLVM имеется мощная система типов; что считается одной из самых важных особенностей LLVM. В LLVM тип целого числа определяется как iN, где N— количество битов, занимаемых целым числом. Вы можете задать любую "ширину" в битах в интервале от 1 до (223 – 1).
  • Векторный тип или тип массива декларируется как [количество элементов X размер каждого элемента]. Таким образом, тип для строки "Hello World!" декларируется как [13 x i8] — в предположении, что каждый символ занимает 1 байт плюс 1 дополнительный байт предусматривается для символа NULL.
  • Декларирование глобальной строковой константы для строки hello-world осуществляется следующим образом: @hello = constant [13 x i8] c"Hello World!\00". Для декларирования констант используется ключевое слово constant, за которым следуют тип и значение. Тип уже был рассмотрен выше, теперь мы рассмотрим значение. Сначала идет символ c, за которым следует вся строка в двойных кавычках, включая заключительные символы \0 и 0. К сожалению, документация LLVM не объясняет, почему строка должна объявляться с префиксом c и заканчиваться двумя символами: символом NULL и символом 0. В разделе Ресурсы имеется ссылка на файл грамматики (/llvm/trunk/utils/llvm.grm), который содержит информацию о других особенностях LLVM.
  • LLVM позволяет декларировать и задавать функции. Вместо перечисления всего списка функциональных возможностей LLVM я сконцентрируюсь на "скелетном наборе". Сначала указывается ключевое слово define, за ним следует возвращаемый тип и, наконец, имя функции. Простое определение метода main возвращает 32-битовое целое число может быть например, таким: define i32 @main() { ; код LLVM-ассеблера, возвращающий i32 }.
  • Декларации функций, как и определения, имеют много дополнительных особенностей. Рассмотрим простейшую декларацию метода puts , который является LLVM-эквивалентом метода printf: declare i32 puts(i8*). Декларация начинается с ключевого слова declare, за который следуют возвращаемый тип, имя функции и, опционально, список аргументов этой функции. Декларация должна находиться в глобальной области.
  • Каждая функция заканчивается оператором возврата. Существуют две формы оператора возврата: ret <type> <value> и ret void. Для нашего простого метода main подходит оператор ret i32 0.
  • Вызов функции осуществляется следующим образом: call <function return type> <function name> <optional function arguments>. Обратите внимание, что перед каждым аргументом функции должен быть указан его тип. Тест функции, возвращающей целое число длиной 6 битов и принимающей целое число длиной 36 битов, имеет следующий синтаксис: call i6 @test( i36 %arg1 ).

Для начала этого достаточно. Вы должны задать метод main, константу для строки и декларацию метода puts, который осуществляет фактическую печать. В листинге 3 показан соответствующий код.

Листинг 3. Первая попытка создания программы Hello World в ручном режиме
declare  i32 @puts(i8*) 
@global_str = constant [13 x i8] c"Hello World!\00"
define i32 @main { 
  call i32 @puts( [13 x i8] @global_str )
  ret i32 0 
}

Журнал инструмента lli выглядит следующим образом.

lli: test.s:5:29: error: global variable reference must have pointer type
  call i32 @puts( [13 x i8] @global_str )
                            ^

Как видим, что-то пошло не так, как планировалось. Что же произошло? Как указывалось выше, инфраструктура LLVM имеет мощную систему типов. Метод puts ожидал указателя i8, а мы передали вектор i8, поэтому инструмент lli сразу же обратил внимание на наличие ошибки. Данная проблема, порожденная программированием на C, имеет очевидное решение — это типизация. А это, в свою очередь, приводит нас к LLVM-инструкции под названием getelementptr. Обратите внимание, что вы должны в листинге 3 изменить вызов puts на call i32 @puts(i8* %t), где %t— это тип i8*, который является результат типизации [13 x i8] to i8* (в разделе Ресурсы приведена ссылка на подробное описание инструкции getelementptr). Прежде чем двигаться дальше, обратитесь к листингу 4, на котором представлен работоспособный код.

Листинг 4. Использование инструкции getelementptr для корректной типизации указателя
declare i32 @puts (i8*)
@global_str = constant [13 x i8] c"Hello World!\00"

define i32 @main() {
  %temp = getelementptr [13 x i8]*  @global_str, i64 0, i64 0
  call i32 @puts(i8* %temp)
  ret i32 0
}

Первый аргумент инструкции getelementptr представляет собой указатель на глобальную строковую переменную. Первый индекс, i64 0, необходим для перешагивания через указатель на глобальную переменную. Первый аргумент инструкции getelementptr должен всегда являться значением типа pointer, поэтому первый индекс перешагивает через этот указатель. Значение 0 соответствует смещению на 0 элементов от этого указателя. На моем инструментальном компьютере исполняется 64-битовая версия ОС Linux®, соответственно, длина указателя составляет 8 байтов. Второй индекс, i64 0, используется для выбора 0-го элемента строки, который поступает как аргумент в метод puts.


Создание собственного генератора кода LLVM IR

Мы понимаем, что LLVM IR — это прекрасно, однако нам нужна система автоматической генерации кода, которая делала бы дамп ассемблера LLVM. К счастью, LLVM обладает достаточной поддержкой интерфейса прикладного программирования (API-интерфейса) для решения этой задачи (ссылка на соответствующее руководство программиста приведена в разделе Ресурсы). Отыщите на своем инструментальном компьютере файл LLVMContext.h; если этот файл отсутствует, то, вероятно, вы сделали что-то не так при установке LLVM.

Теперь мы создадим программу, которая генерирует код LLVM IR для программы Hello World, рассмотренной ранее. Данная программа не будет иметь дело со всем API-интерфейсом инфраструктуры LLVM, однако приведенные ниже примеры кода наглядно демонстрируют, что буквально каждый бит API-интерфейса LLVM является интуитивно понятным и удобным.

LLVM комплектуется отличным инструментом под названием llvm-config (см. раздел Ресурсы). Выполните следующие команды: команду llvm-config –cxxflags для получения флагов компиляции, которые необходимо передать в g++; команду llvm-config –ldflags для получения опций компоновщика; команду llvm-config –libs для связывания с нужными библиотеками LLVM. В примере в листинге 5, все опции необходимо передать в g++.

Листинг 5. Использование инструмента llvm-config для построения кода с помощью API-интерфейса LLVM
tintin# llvm-config --cxxflags --ldflags --libs \
-I/usr/include  -DNDEBUG -D_GNU_SOURCE \
-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS \
-D__STDC_LIMIT_MACROS -O3  -fno-exceptions -fno-rtti -fno-common \
-Woverloaded-virtual -Wcast-qual \
-L/usr/lib  -lpthread -lm \
-lLLVMXCoreCodeGen -lLLVMTableGen -lLLVMSystemZCodeGen \
-lLLVMSparcCodeGen -lLLVMPTXCodeGen \
-lLLVMPowerPCCodeGen -lLLVMMSP430CodeGen -lLLVMMipsCodeGen \
-lLLVMMCJIT -lLLVMRuntimeDyld \
-lLLVMObject -lLLVMMCDisassembler -lLLVMXCoreDesc -lLLVMXCoreInfo \
-lLLVMSystemZDesc -lLLVMSystemZInfo \
-lLLVMSparcDesc -lLLVMSparcInfo -lLLVMPowerPCDesc -lLLVMPowerPCInfo \
-lLLVMPowerPCAsmPrinter \
-lLLVMPTXDesc -lLLVMPTXInfo -lLLVMPTXAsmPrinter -lLLVMMipsDesc \
-lLLVMMipsInfo -lLLVMMipsAsmPrinter \
-lLLVMMSP430Desc -lLLVMMSP430Info -lLLVMMSP430AsmPrinter \
-lLLVMMBlazeDisassembler -lLLVMMBlazeAsmParser \
-lLLVMMBlazeCodeGen -lLLVMMBlazeDesc -lLLVMMBlazeAsmPrinter \
-lLLVMMBlazeInfo -lLLVMLinker -lLLVMipo \
-lLLVMInterpreter -lLLVMInstrumentation -lLLVMJIT -lLLVMExecutionEngine \
-lLLVMDebugInfo -lLLVMCppBackend \
-lLLVMCppBackendInfo -lLLVMCellSPUCodeGen -lLLVMCellSPUDesc \
-lLLVMCellSPUInfo -lLLVMCBackend \
-lLLVMCBackendInfo -lLLVMBlackfinCodeGen -lLLVMBlackfinDesc \
-lLLVMBlackfinInfo -lLLVMBitWriter \
-lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen \
-lLLVMX86Desc -lLLVMX86AsmPrinter -lLLVMX86Utils \
-lLLVMX86Info -lLLVMAsmParser -lLLVMARMDisassembler -lLLVMARMAsmParser \
-lLLVMARMCodeGen -lLLVMARMDesc \
-lLLVMARMAsmPrinter -lLLVMARMInfo -lLLVMArchive -lLLVMBitReader \
-lLLVMAlphaCodeGen -lLLVMSelectionDAG \
-lLLVMAsmPrinter -lLLVMMCParser -lLLVMCodeGen -lLLVMScalarOpts \
-lLLVMInstCombine -lLLVMTransformUtils \
-lLLVMipa -lLLVMAnalysis -lLLVMTarget -lLLVMCore -lLLVMAlphaDesc \
-lLLVMAlphaInfo -lLLVMMC -lLLVMSupport

LLVM-модули, контексты и т. д.

Класс модуля LLVM — это контейнер верхнего уровня для всех остальных объектов LLVM IR. Класс LLVM-модуля может содержать список глобальных переменных, функции, другие модули, от которых зависит данный модуль, таблицы символов и т. д. Конструктор для LLVM-модуля:

explicit Module(StringRef ModuleID, LLVMContext& C);

Вы должны начать свою программу с создания LLVM-модуля. Первый аргумент — это имя модуля, оно может быть любой фиктивной строкой. Второй аргумент — это класс LLVMContext. Класс LLVMContext не вполне прозрачен, однако нам достаточно понимать, что он предоставляет контекст, в котором создаются переменные и т. д. Этот класс становится важным при наличии нескольких потоков — когда вы создаете локальный контекст для каждого потока, и каждый поток исполняется совершенно независимо от контекста любого другого потока. На данный момент мы используем глобальный дескриптор контекста по умолчанию, который предоставляется инфраструктурой LLVM. Код для создания модуля:

llvm::LLVMContext& context = llvm::getGlobalContext();

llvm::Module* module = new llvm::Module("top", context);

Следующий важный класс, который необходимо изучить, фактически предоставляет API-интерфейс для создания LLVM-инструкций и для их вставки в базовые блоки: это класс IRBuilder. Класс IRBuilder имеет массу различных "бантиков", однако я выбрал самый простой способ для его построения — посредством передачи в него глобального контекста с помощью нижеследующего кода.

llvm::LLVMContext& context = llvm::getGlobalContext();

llvm::Module* module = new llvm::Module("top", context);

llvm::IRBuilder<> builder(context);

Когда модель объекта LLVM будет готова, вы сможете осуществить вывод ее содержимого посредством вызова метода dump этого модуля. Соответствующий код показан в листинге 6.

Листинг 6. Создание фиктивного модуля
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module* module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  module->dump( );
}

После исполнения кода в листинге 6 на консоль будет выведена следующая информация:

; ModuleID = 'top'

Затем вам нужно создать метод main. Инфраструктура LLVM предоставляет класс llvm::Function для создания функции и класс llvm::FunctionType для ассоциирования возвращаемого типа для функции. Кроме того, не следует забывать, что метод main должен быть частью модуля. Соответствующий код показан в листинге 7.

Листинг 7. Добавление метода main в модуль
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  llvm::FunctionType *funcType = 
      llvm::FunctionType::get(builder.getInt32Ty(), false);
  llvm::Function *mainFunc = 
      llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);

  module->dump( );
}

Обратите внимание, что мы хотели, чтобы метод main возвратил void, вот почему мы осуществили вызов builder.getVoidTy(); чтобы main возвратил i32, необходим вызов builder.getInt32Ty(). После компиляции и исполнения кода в листинге 7 мы получаем следующий результат:

; ModuleID = 'top'
declare void @main()

Мы пока еще не сформировали набор инструкций, которые должен выполнять метод main. Для этого необходимо задать базовый блок и ассоциировать его с методом main. Базовый блок (basic block) — это набор инструкций в коде LLVM IR, позволяющих задавать метки (похожие на метки в языке C) в рамках своего конструктора. Builder.setInsertPoint сообщает механизму LLVM, где вставлять следующие инструкции. Соответствующий код показан в листинге 8.

Листинг 8. Добавление базового блока к методу main
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  llvm::FunctionType *funcType = 
      llvm::FunctionType::get(builder.getInt32Ty(), false);
  llvm::Function *mainFunc = 
      llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);

  llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  module->dump( );
}

Ниже показан результат исполнения кода в листинге 8. Обратите внимание, что поскольку базовый блок для main задан, дамп LLVM теперь рассматривает main как определение метода, а не как декларацию. Неплохо!

; ModuleID = 'top'
define void @main() { 
entrypoint: 
}

Теперь добавьте к коду глобальную строку hello-world. Соответствующий код показан в листинге 9.

Листинг 9. Добавление глобальной строки к LLVM-модулю
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRBuilder.h"

int main()
{
  llvm::LLVMContext& context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("top", context);
  llvm::IRBuilder<> builder(context); 

  llvm::FunctionType *funcType = 
      llvm::FunctionType::get(builder.getVoidTy(), false);
  llvm::Function *mainFunc = 
      llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);

  llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  llvm::Value *helloWorld = builder.CreateGlobalStringPtr("hello world!\n");

  module->dump( );
}

В этом результате выполнения кода в листинге 9 обратите внимание, как механизм LLVM выводит строку:

; ModuleID = 'top'
@0 = internal unnamed_addr constant [14 x i8] c"hello world!\0A\00"
define void @main() {
entrypoint:
}

Теперь нам нужно декларировать метод puts и вызвать его. Для декларирования метода puts необходимо создать надлежащий тип FunctionType*. На примере нашего первоначального кода Hello World вы знаете, что puts возвращает i32 и принимает i8* как входной аргумент. В листинге 10 показан код, создающий надлежащий тип для метода puts.

Листинг 10. Код для декларирования метода puts
  std::vector<llvm::Type *> putsArgs;
  putsArgs.push_back(builder.getInt8Ty()->getPointerTo());
  llvm::ArrayRef<llvm::Type*>  argsRef(putsArgs);

  llvm::FunctionType *putsType = 
    llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false);
  llvm::Constant *putsFunc = module->getOrInsertFunction("puts", putsType);

Первый аргумент FunctionType::get— это возвращаемый тип; второй аргумент — это структура LLVM:: ArrayRef, последний аргумент false указывает, что далее не будет следовать переменное количество аргументов. Структура ArrayRef подобна вектору, за исключением того, что она не содержит базовых данных и используется преимущественно для упаковки блоков данных в виде массивов и векторов. После этого изменения результаты исполнения выглядят, как в листинге 11.

Листинг 11. Декларирование метода puts
; ModuleID = 'top'
@0 = internal unnamed_addr constant [14 x i8] c"hello world!\0A\00"
define void @main() {
entrypoint:
}
declare i32 @puts(i8*)

Теперь нам осталось вызвать метод puts внутри метода main, а затем осуществить возврат из main. API-интерфейс LLVM позаботится о типизации и обо всем остальном: Нам нужно вызвать puts для инициализации builder.CreateCall. И, наконец, для создания оператора возврата мы осуществляем вызов builder.CreateRetVoid. В листинге 12 работоспособный код представлен в полном объеме.

Листинг 12. Полный текст кода для вывода на печать фразы Hello World
#include "llvm/ADT/ArrayRef.h"
#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Function.h"
#include "llvm/BasicBlock.h"
#include "llvm/Support/IRBuilder.h"
#include <vector>
#include <string>

int main()
{
  llvm::LLVMContext & context = llvm::getGlobalContext();
  llvm::Module *module = new llvm::Module("asdf", context);
  llvm::IRBuilder<> builder(context);

  llvm::FunctionType *funcType = llvm::FunctionType::get(builder.getVoidTy(), false);
  llvm::Function *mainFunc = 
    llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "main", module);
  llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunc);
  builder.SetInsertPoint(entry);

  llvm::Value *helloWorld = builder.CreateGlobalStringPtr("hello world!\n");

  std::vector<llvm::Type *> putsArgs;
  putsArgs.push_back(builder.getInt8Ty()->getPointerTo());
  llvm::ArrayRef<llvm::Type*>  argsRef(putsArgs);

  llvm::FunctionType *putsType = 
    llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false);
  llvm::Constant *putsFunc = module->getOrInsertFunction("puts", putsType);

  builder.CreateCall(putsFunc, helloWorld);
  builder.CreateRetVoid();
  module->dump();
}

Заключение

Другие статьи данного цикла

Читайте другие статьи данного цикла: Создание действующего компилятора с помощью инфраструктуры LLVM.

В процессе изучения этой первой статьи по LLVM вы узнали о таких инструментах LLVM, как lli и llvm-config, углубились в промежуточный код LLVM и воспользовались API-интерфейсом LLVM для генерации промежуточного кода при решении собственной задачи. Во второй и заключительной части этого цикла рассматривается еще одна задача, для выполнения которой вы можете воспользоваться инфраструктурой LLVM, — добавление дополнительного прохода компиляции с минимальными усилиями.

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Linux
ArticleID=845332
ArticleTitle=Создание действующего компилятора с помощью инфраструктуры LLVM. Часть 1
publish-date=11122012