671 читатель, 36 постов
Администрация
Модераторы
Популярным языкам — по блогу. Остальным посвящается…
i1 ; булево значение — 0 или 1
i32 ; 32-разрядное целое
i17 ; даже так
i256 ; ого! Генерация машинного кода для типов очень большой разрядности не поддерживается. Например, для x86 вам придётся ограничиться i64, а для x86-64 и других 64-разрядных платформ — 128-битными целыми. Но для промежуточного представления никаких ограничений нет.float, double, а также ряд типов, специфичных для конкретной платформы (например, x86_fp80).void — пустое значение.тип*
i32* ; указатель на 32-битное целое
[число элементов x тип]
[10 x i32]
[8 x double]
{ i32, i32, double } < число элементов x тип >
< 4 x float >
i32 (i32, i32)
float ({ float, float }, { float, float })
%, а глобальные — @. Локальные значения также называют регистрами, а LLVM — виртуальной машиной с бесконечным числом регистров. Пример:%sum = add i32 %n, 5
%diff = sub double %a, %b
%z = add <4 x float> %v1, %v2 ; поэлементное сложение
%cond = icmp eq %x, %y ; Сравнение целых чисел. Результат имеет тип i1.
%success = call i32 @puts(i8* %str)
Тип операндов всегда указывается явно, и однозначно определяет тип результата. Операнды арифметических инструкций должны иметь одинаковый тип, но сами инструкции «перегружены» для любых числовых типов и векторов.bitcast, которая приведёт всё ко всему, но за результат вы отвечаете сами.; x = (a + b) * c - d / e
%tmp1 = add float %a, %b
%tmp2 = mul float %tmp1, %c
%tmp3 = fdiv float %d, %e
%x = sub float %tmp2, %tmp3
Забегая вперёд, за пределы этой статьи, заметим, что при использовании LLVM API для генерации кода всё становится ещё проще, потому что оно следует принципу «инструкция — это значение». Нам не придётся заниматься генерацией уникальных имён для промежуточных значений: функция, генерирующая инструкцию, возвращает значение (объект C++), которое может быть передано как аргумент в другие такие функции.%z = sum i32 %x, %y
%z = sum i32 %z, 5
Новое значение должно получить новое имя:%z.1 = sum i32 %z, 5 Однако, спросите вы, как быть, если одна и та же переменная должна получить разные значения в зависимости от какого-то условия? Или как организовать переменную цикла?ret тип значение — возврат значения из функцииbr i1 условие, label метка_1, label метка_2 — условный переход. Например:define float @max(float %x, float %y)
{
%cond = fcmp ogt float %x, %y
br i1 %cond, label %IfTrue, label %IfFalse
IfTrue:
ret float %x
IfFalse:
ret float %y
} (Синтаксис определения функции, думаю, очевиден).br label метка switch — обобщение br, позволяет организовать таблицу переходов:switch i32 %n, label %Default, [i32 0, label %IfZero i32 5, label %IfFive] invoke и unwind — используются для организации исключений, в этой статье останавливаться на них мы не будем.unreachable — специальная инструкция, показывающая компилятору, что выполнение никогда не достигнет этой точки. Например, эта инструкция может быть вставлена после вызова системной функции завершения процесса.phi тип, [значение_1, label метка_1], ..., [значение_N, label метка_N] В качестве примера рассмотрим функцию вычисления факториала, которую на Си можно было бы записать так:int factorial(int n)
{
int result = n;
int i;
for (i = n - 1; i > 1; --i)
result *= i;
return result;
}
Примечание: блок, который начинается с входа в функцию обозначается %0.define i32 @factorial(i32 %n)
{
%i.start = sub i32 %n, 1
br label %LoopEntry
LoopEntry:
%result = phi i32 [%n, %0], [%result.next, %LoopBody]
%i = phi i32 [%i.start, %0], [%i.next, %LoopBody]
%cond = icmp sle i32 %i, 1
br i1 %cond, label %Exit, label %LoopBody
LoopBody:
%result.next = mul i32 %result, %i
%i.next = sub i32 %i, 1
br label %LoopEntry
Exit:
ret i32 %result
} Пусть вас не смущают бессмысленные на первый взгляд переходы на метку, следующую сразу за инструкцией перехода. Как мы уже сказали, базовый блок обязан заканчиваться явной передачей управления. LLVM также требует, чтобы все phi-инструкции шли в начале блока, и до них не было никаких других инструкций.load и store. Например:%x = load i32* %x.ptr ; загрузили значение типа i32 по указателю %x.ptr
%tmp = add i32 %x, 5 ; прибавили 5
store i32 %tmp, i32* %x.ptr ; и положили обратно
Но чтобы пользоваться указателями, надо как-то выделять память под значения, на которые они указывают.malloc транслируется в вызов одноименной системной функции и выделяет память на куче, возвращая значение — указатель определенного типа. В паре с ней конечно же идёт инструкция free.%struct.ptr = malloc { double, double }
%string = malloc i8, i32 %length
%array = malloc [16 x i32]
free i8* %string
Официальной рекомендации не использовать инструкцию malloc нет, но разработчики признаются, что особого смысла в её существовании сейчас не имеется. Вы можете вызвать вместо неё функцию @malloc или написать свою собственную функцию-аллокатор, отвечающую каким-то особым требованиям.%x.ptr = alloca double ; %x.ptr имеет тип double*
%array = alloca float, i32 8 ; %array имеет тип float*, а не [8 x float]!
Память, выделенная alloca, автоматически освобождается при выходе из функции при помощи инструкций ret или unwind.alloca, load и store мы можем пользоваться локальными переменными так же, как и в любом императивном языке. Например, наша многострадальная функция факториала:define i32 @factorial(i32 %n)
{
%result.ptr = alloca i32 ; выделить память под result
%i.ptr = alloca i32 ; и под i
store i32 %n, i32* %result.ptr ; инициализация result = n
%tmp1 = sub i32 %n, 1
store i32 %tmp1, i32* %i.ptr ; i = n - 1
br label %Loop
Loop:
%i = load i32* %i.ptr ; загружаем значение i
%cond = icmp sle i32 %i, 1 ; и проверяем условие i <= 1
br i1 %cond, label %Exit, label %LoopBody ; если да, переход к возврату значения
LoopBody:
%tmp2 = load i32* %result.ptr
%tmp3 = mul i32 %tmp2, %i
store i32 %tmp3, i32* %result.ptr ; result *= i
%i.next = sub i32 %i, 1
store i32 %i.next, i32* %i.ptr ; --i
br label %Loop
Exit:
%result = load i32* %result.ptr
ret i32 %result ; return result
}
Достаточно многословно, но скажите, где ещё кроме подобной статьи вы будете писать код на LLVM вручную? :-)factorial после прохода этого алгоритма:define i32 @factorial(i32 %n) {
; <label>:0
%tmp1 = sub i32 %n, 1
br label %Loop
Loop:
%i.ptr.0 = phi i32 [ %tmp1, %0 ], [ %i.next, %LoopBody ]
%result.ptr.0 = phi i32 [ %n, %0 ], [ %tmp3, %LoopBody ]
%cond = icmp sle i32 %i.ptr.0, 1
br i1 %cond, label %Exit, label %LoopBody
LoopBody:
%tmp3 = mul i32 %result.ptr.0, %i.ptr.0
%i.next = sub i32 %i.ptr.0, 1
br label %Loop
Exit:
ret i32 %result.ptr.0
}
%ptr = add i32* %array, i32 %index Для вычисления адресов элементов массивов, структур и т. д. с правильной типизацией есть специальная инструкция getelementptr.%array = alloca i32, i32 %size
%ptr = getelementptr i32* %array, i32 %index ; значение типа i32*
getelementptr только вычисляет адрес, но не обращается к памяти. Инструкция принимает произвольное количество индексов и может разыменовывать структуры любой вложенности. Например, из следующего кода на Си:struct s {
int n;
char *a[4];
};
struct *s = ...;
char c = s->a[2][5];
будет сгенерирована такая последовательность инструкций:%ptr = getelementptr { i32, [4 x i8*] }* %s, i32 1, i32 2, i32 5
%c = load i8* %ptr
Как вы заметили, индексы отсчитываются от нуля.getelementptr пара инструкций extractvalue и insertvalue. Они отличаются тем, что принимают не указатель на агрегатный тип данных (массив или структуру), а самое значение такого типа. extractvalue возвращает соответственное значение подэлемента, а не указатель на него, а insertvalue порождает новое значение агрегатного типа.%n = extractvalue { i32, [4 x i8*] } %s, 0
%tmp = add i32 %n, 1
%s.1 = insertvalue { i32, [4 x i8*] } %s, i32 %tmp, 0
@llvm.sqrt.*, @llvm.sin.* и т. д. Есть также примитивы для атомарных операций и некоторые другие.%llvm.dbg.stoppoint (задаёт соответствие между строками исходного кода и генерируемым кодом), %llvm.dbg.declare (задаёт описание локальной переменной) и др., в качестве аргументов которым передаются указатели на специальные структуры.@llvm.gcroot, @llvm.gcread и @llvm.gc.write позволяют закодировать информацию, необходимую для работы GC, а интерфейс плагина к компилятору LLVM — сгенерировать по этой информации нужные структуры данных и вставить обращения к рантайму.int factorial(int n)
{
if (n < 2) return 1;
return n * factorial(n - 1);
}
комментарии (20)
В среднем одинаково с GCC 4.
В их собственной системе «ночного» тестирования есть тесты из SPEC CPU, но самих результатов SPEC я не видел.
То есть, в трёх словах, конечный пользователь имеет возможность код на С/С++ компилить сразу под несколько архитектур с оптимизацией, которая учитывает особенности этих архитектур. Я правильно всё понял?
Сорри, что много вопросов — вещь интересная, копать некогда :-)
llvm-gcc — это мощнейший стресс-тест для LLVM и способ интеграции существующего кода на C/C++. Просто как компилятор C++ особых преимуществ перед GCC он не имеет.
С помощью LLVM можно оптимизировать программы не только при развёртывании, но и в рантайме — оптимизация «горячих» участков кода. Это особенно актуально для динамических языков.
или LLVM непригодно для JIT-компилятора?
P.S. а что не в тематическом блоге?
Судя по всему, когда Mono начинал развиваться, возможности LLVM были неадекватными: комент на LtU разработчика Mono.
Сейчас идёт работа над реализацией CLR и JVM поверх LLVM: VMKit
> или LLVM непригодно для JIT-компилятора?
Ещё как пригодно, возможность заложена изначально.
> а что не в тематическом блоге?
Не придумал в какой.
просто топики в личных блогах на главную не попадают
от себя могу добавить что именно через LLVM идет компиляция C/C++ в AS3 в адобовской алхимии и там действительно все очень поразительно шоколадно получается.
В общем штука офигительно перспективная. И есть надежда что адобе продвинет ее достаточно далеко. Дажешь ОШЕ любому скриптовому языку за 5 копеек! :)
connect.microsoft.com/Phoenix
Взять тот же кэш процессора — он как-то задействуется?
Теории по JIT'у навалом, но это либо радостные возгласы, либо констатация фактов: «теперь у нас есть такая крутая штука», более никаких подробностей. Да, есть исходники, но их разбор шибко тяжкий труд.
Instruction cache задействуется в любом случае, какие тут нужны дополнительные телодвижения?
>>Instruction cache задействуется в любом случае, какие тут нужны дополнительные телодвижения?
Понятия не имею, поэтому и спросил.
Насчёт исключений: invoke позволяет вызвать функцию, задав две точки продолжения: нормальную и ту, куда попадёт управление, если где-то в цепочке вызовов встретится unwind. Всё остальное — забота рантайма. Помимо документации здесь бывает полезно посмотреть на вывод llvm-g++ --emit-llvm — во что он превращает те или иные конструкции.