LLVM jit и родной
Я не понимаю, как LLVM JIT относится к нормальной компиляции JIT, а документация не очень хороша.
Например, предположим, что я использую интерфейс clang
:
- Случай 1: Я компилирую файл C на native с clang/llvm. Этот поток, который я понимаю, похож на gcc-поток - я получаю исполняемый файл x86 и запускает его.
- Случай 2: Я собираюсь в какой-то LLVM IR, который работает на LLVM JIT. В этом случае исполняемый файл содержит время выполнения LLVM для выполнения IR на JIT или как он работает?
В чем разница между этими двумя и они правильны? Включает ли поток LLVM поддержку как JIT, так и не JIT? Когда я хочу использовать JIT - это имеет смысл вообще для языка, такого как C?
Ответы
Ответ 1
Вы должны понимать, что LLVM - это библиотека, которая помогает вам создавать компиляторы. Clang - это просто интерфейс для этой библиотеки.
Clang переводит код C/С++ в LLVM IR и передает его LLVM, который компилирует его в собственный код.
LLVM также может генерировать собственный код непосредственно в памяти, который затем может быть вызван как нормальная функция. Итак, случаи 1. и 2. разделяют оптимизацию LLVM и генерации кода.
Итак, как использовать LLVM как JIT-компилятор? Вы создаете приложение, которое генерирует некоторый LLVM IR (в памяти), затем используйте библиотеку LLVM для генерации собственного кода (все еще в памяти). LLVM возвращает вам указатель, который вы можете вызвать позже. Никакого clang.
Однако вы можете использовать clang для перевода некоторого кода C в LLVM IR и загрузить его в свой JIT-контекст, чтобы использовать функции.
Примеры реального мира:
Существует также Калейдоскоп, в котором показано, как реализовать простой язык с компилятором JIT.
Ответ 2
Сначала вы получаете байт-код LLVM (LLVM IR):
clang -emit-llvm -S -o test.bc test.c
Во-вторых, вы используете LLVM JIT:
lli test.bc
Запускает программу.
Затем, если вы хотите получить native, вы используете LLVM-сервер:
llc test.bc
Из вывода сборки:
as test.S
Ответ 3
Я предпринимаю шаги для компиляции и запуска JIT-кода из почтового сообщения в сообществе LLVM.
[LLVMdev] Учебник MCJIT и калейдоскопа
Заголовочный файл:
// foo.h
extern void foo(void);
и функция для простой функции foo():
//foo.c
#include <stdio.h>
void foo(void) {
puts("Hello, I'm a shared library");
}
И основная функция:
//main.c
#include <stdio.h>
#include "foo.h"
int main(void) {
puts("This is a shared library test...");
foo();
return 0;
}
Создайте общую библиотеку с помощью foo.c:
gcc foo.c -shared -o libfoo.so -fPIC
Сгенерируйте бит LLVM для файла main.c:
clang -Wall -c -emit-llvm -O3 main.c -o main.bc
И запустите LLVM-бит-код через jit (и MCJIT), чтобы получить желаемый результат:
lli -load=./libfoo.so main.bc
lli -use-mcjit -load=./libfoo.so main.bc
Вы также можете передать вывод clang в lli:
clang -Wall -c -emit-llvm -O3 main.c -o - | lli -load=./libfoo.so
Выход
This is a shared library test...
Hello, I'm a shared library
Источник, полученный из
Общие библиотеки с GCC в Linux
Ответ 4
Большинство компиляторов имеют переднюю часть, некоторый средний код/структуру какого-то типа и бэкэнд. Когда вы берете свою программу на C и используете clang и компилируете, чтобы в итоге вы получили не-JIT-x86-программу, которую вы можете просто запустить, вы все равно переходите от интерфейса к среднему для бэкэнд. То же самое касается gcc, gcc идет от интерфейса к среднему и к бэкэнду. Средство Gccs не является широко открытым и удобным, как LLVM.
Теперь одно интересное/интересное о llvm, которое вы не можете сделать с другими, или, по крайней мере, gcc, состоит в том, что вы можете взять все свои модули исходного кода, скомпилировать их в байт-код llvms, объединить их в один большой байт-код файл, а затем оптимизировать все это, а не для каждого файла или для оптимизации функции, которую вы получаете с другими компиляторами, с llvm вы можете получить любой уровень частичной оптимизации программы компиляции. то вы можете взять этот байт-код и использовать llc для экспорта его на ассемблер целей. Я обычно встраиваюсь, поэтому у меня есть свой собственный код запуска, который я обертываю, но теоретически вы должны взять этот файл ассемблера и скомпилировать gcc и связать его и запустить. gcc myfile.s -o myfile. Я предполагаю, что есть способ получить инструменты llvm для этого и не использовать binutils или gcc, но я не нашел времени.
Мне нравится llvm, потому что он всегда является кросс-компилятором, в отличие от gcc вам не нужно собирать новую для каждой цели и обрабатывать нюансы для каждой цели. Я не знаю, что я использую для JIT то, что я говорю, что я использую его как кросс-компилятор и как родной компилятор.
Итак, ваш первый случай - это фронт, середина, конец, и процесс скрыт от вас, вы начинаете с источника и получаете двоичный файл. Второй случай - если я правильно понял фронт и середину и остановился с некоторым файлом, представляющим середину. Тогда среднее и конечное (конкретный целевой процессор) может произойти как раз во время выполнения. Разница в том, что бэкэнд, выполнение в реальном времени среднего языка второго случая, скорее всего, отличается от бэкэнда одного случая.