LINUX.ORG.RU

LLVM. Зачем он вообще нужен?

 ,


3

6

Я понимаю, зачем его используют: чтобы не писать 100500 раз в каждом компиляторе одни и те же алгоритмы оптимизации.

Я не понимаю, почему не использовать просто компиляцию через Си или Си++. Оптимизации сделает компилятор Си. Семантика у LLVM всё равно совпадает с Си, по объёму кода компилятора тоже выигрыша практически нет. Зато если использовать Си, можно использовать любой из компиляторов Си и компилировать для платформ, для которых нет реализации LLVM.

★★★★★
Ответ на: комментарий от monk

Да (была история с Intel процессорами с разной точностью). А также может упасть например потому что FPU не поддерживается. Последнее прекрасно обрабатывается через SEH.

X512 ★★★★★
()
Последнее исправление: X512 (всего исправлений: 1)
Ответ на: комментарий от X512

sin тоже должен возвращать коды ошибок вроде «no FPU»?

«no FPU» должно быть при инициализации программы (модуля) использующей возможности fpu, если нет (программной) эмуляции.

anonymous
()
Ответ на: комментарий от X512

Если у процессора нет операции sin, он генерирует исключение, что программа выполнила недопустимую операцию. То, что программа выполняет недопустимые операции - это уже ошибка кодогенератора. Это тоже самое, что дергать на i486 инструкции avx2.

Программа при инициализации (до входа в main) должна сказать, что у процессора нет fpu, и завершить выполнение, до выполнения недопустимой инструкции sin, или подключить (програмную) эмуляцию.

anonymous
()
Ответ на: комментарий от buddhist

В этом и суть. Лисп, особенно CL - это промышленный индустриальный язык, стандартизированный ANSI, в отличие от академических поделок для выбивания грантов и написания дисеров

lovesan ★★★
()
Ответ на: комментарий от X512

Что процессор должен делать при исполнении инструкции sin когда нет FPU?

Процессор генерирует прерывание. А уже язык программирования трактует это как немедленный останов, исключение или код ошибки возврата.

monk ★★★★★
() автор топика
Ответ на: комментарий от X512

sin тоже должен возвращать коды ошибок вроде «no FPU»? memcpy должен возвращать код «illegal memory read/write»?

Это лучше, чем когда по сигнатуре не видно с чем этот кусок кода может упасть.

monk ★★★★★
() автор топика

можно использовать форт, в чём вопрос..стековая машина, всё хорошо. в цели CLR и JVM ложится почти один в один, зато уровнем выше

imho: сам по себе LLVM просто серьёзный пинок под зад gcc - «хватит заниматься фигнёй» :-) концептуально они не сильно отличаются - и там и там свой IL с обвязками. Пинок видимо хорошо прошёл и был воспринят. новые gcc по факту лучше

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

можно использовать форт, в чём вопрос..стековая машина, всё хорошо. в цели CLR и JVM ложится почти один в один, зато уровнем выше

Эффективных (на уровне GCC или LLVM) компиляторов ни для Форта, ни для CLR/JVM нет.

monk ★★★★★
() автор топика
Ответ на: комментарий от lovesan

Расскажи же нам о современных коммерческих применениях коммон лиспа. А ещё о том, почему деятельность типа купи-продай лучше выбивания грантов и написания диссеров.

buddhist ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Ну через doxygen значит, в общем пометить функцию не проблема.

Проблема транзитивности.

Написал ты

int foo(int x, int y)
{
  return lib_c_foobar(lib_a_baz(x), lib_b_bar(y));
}

Написал в doxygen, все исключения, которые может бросать foo исходя из документации на библиотеки a, b и c.

Потом вышли новые версии библиотек, добавилась пара новых исключений, но узнаешь ты про это от возмущённого пользователя, который сделал обработку всех исключений из документации, а у него упала программа.

А если у тебя явный тип-перечисление в описании ошибки, то при его изменении программа потребует перекомпиляции, а при проверке линтер укажет, что не все варианты проверены.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Ну это проблема документации, организации, точно так же сторонняя библиотека может сделать panic а не возврат ошибки, и узнаешь ты это от пользователя который вечно ноет, а мог бы и сам запилить, опенсорс же.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от monk

Так исключение бросают в предположении, что его кто-то поймает и обработает

ловишь в main все необработанные исключения, пишешь - unhandled exception catched, и выходишь из проги с высоко поднятой головой.

так всегда делали.

alysnix ★★★
()
Ответ на: комментарий от LongLiveUbuntu

С тоже может быть промежуточным языком.

Ты еще скажи. что Ассемблер может.

UNCOL может: uncol-proposal.tex hello-world.tex

это github.com/damelang/silt, с реализацией на смоллтоке implementations/squeak/Silt.package

зачем?

SSA представление.

QBE проще и легче (а silt ещё легче), а у LLVM больше проходов оптимизаций.

ну а так-то можно и какой-нибудь [COLA.brainfuck.tutorial)(https://www.hpi.uni-potsdam.de/hirschfeld/misc/cola/index.html) взять или там Kernel с first-class objects=first class environment organix/kernel_abe64 cм. Semantic Extensibility with Vau)

ещё ribbit компактная схема, с трансляцией через си.

а так вообще и tcl::tcc в природе существует.

anonymous
()
Ответ на: комментарий от monk

Но зачем?

чтобы SSA представление. удобное для оптимизации.

ну и ещё можно проходы оптимизации ключиками подобрать произвольные.

сколько там их, около сотни, кажется? или больше?

anonymous
()
Ответ на: комментарий от annulen

был ещё компилятор интерстон Е. Зуева (который редкая профессия и ещё стандарт С++16/17/18 перевёл ) в котором было семантическое, а не абстрактно-синтаксическое представление. у него ещё c11 – C++ с русскими буквами был.

наверное, на тех же VM и фронтенде, что и по первой ссылке.

а LLVM вообще проще из Ocaml дёргать, чем из C++

но что там было с оптимизацииями сопоставимыми – я уже не помню.

anonymous
()
Ответ на: комментарий от annulen

SSA ещё XDS компилятор строил, в котором модула-2 и оберон, и транспиляция в си. было это начиная года с 1993, за сколько там лет до LLVM.

а вот в компиляторе «Странник-Модула-Паскаль» – исходники недавно выложили на гитхаб — кажется, свой велосипед. не помню, использовалось ли там SSA, кажется, нет.

но это такой Глагол Издателя, типа того.

в общем, без LLVM SSA тоже бывает, и гораздо проще.

например, Myrrdin функциональный язычок типа Ocaml на QBE с SSA без LLVM.

anonymous
()
Ответ на: комментарий от anonymous

вот тут что-то про реализацию Странник-Модула-Си-Паскаль:

docs/st_txt_prog.html:

Для разнообразия проект “Странник Модула-Си-Паскаль” полностью русскоязычен. Интерфейс пользователя, языки программирования, демонстрационные примеры, документация даже сами исходные тексты компилятора написаны на русском языке. Ниже рассказано о применяемых языках программирования, интегрированной среде и некоторых секретах компиляции (внутреннняя структура компилятора и генерируемого им exe-файла)

В настоящее время компилятор поддерживает три языка программирования – Модула-2 (Оберон-2), Паскаль и Си (Си++). Все языки программирования компилятора “Странник” имеют общую семантическую базу (одинаковый набор операторов, базовых и производных типов, одинаковый механизм процедур и модулей). Более подробно о языках с общей семантической базой можно прочитать в статьях автора в Мире ПК (“Современные языки программирования – результаты эволюции”, N3 за 2001) и Программисте (“Сравнение языков программирования Си++, Паскаль и Ада”, N2 за 2002 год). Кратко можно сказать, что при приведении каждого языка к общей семантической базе языки были частично сокращены, частично расширены. Ниже приведен список конструкций, присутствующих во всех языках программирования компилятора.

anonymous
()
Ответ на: комментарий от anonymous

Конечной целью проекта является создание многоязычного компилятора с нетекстовой интегрированной средой (частично этому вопросу была посвящена статья в Мире ПК 3 за 2001 г.). В такой среде внутреннее представление программы должно представлять древовидную структуру (модуль – процедуры – операторы – выражение и т.д.). Внешне программа по-прежнему будет выглядеть как текст на каком-либо языке программирования. В этом случае само понятие языка программирования нивелируется, поскольку перейти (и перевести программу) с одного языка на другой можно будет простым переключением соответствующих настроек компилятора. Разумеется, список применяемых языков в этом случае можно расширять до бесконечности. Существенно упрощается и ускоряется в этом случае и сама компиляция, поскольку отпадают самые “ресурсоемкие” стадии компиляции – лексический и синтаксический разбор. Разумеется, сочетать внутреннюю древовидную структуру с внешним представлением в виде текста крайне сложно, поэтому пока компилятор имеет традиционный текстовый интерфейс, однако создание нового интерфейса – главная задача автора на ближайшую перспективу.

то есть, у него тоже некоторое общее семантическое дерево (а не абстрактно-синтаксическое)

но там ЕМНИП, был свой велосипед, про описание IR надо смотреть в исходниках или в статьях

SSA ЕМНИП, не было.

то есть, это такой быстрый однопроходной с эффективным представлением в памяти (ЕМНИП) типа турбо паскаля.

хотя сейчас например существуют рамдиски, и с них даже и тяжежеловесное многословное С++ может быстрее конпелироваться.

anonymous
()
Ответ на: комментарий от anonymous

создание многоязычного компилятора с нетекстовой интегрированной средой (частично этому вопросу была посвящена статья в Мире ПК 3 за 2001 г.). В такой среде внутреннее представление программы должно представлять древовидную структуру (модуль – процедуры – операторы – выражение и т.д.). Внешне программа по-прежнему будет выглядеть как текст на каком-либо языке программирования. В этом случае само понятие языка программирования нивелируется, поскольку перейти (и перевести программу) с одного языка на другой можно будет простым переключением соответствующих настроек компилятора. Разумеется, список применяемых языков в этом случае можно расширять до бесконечности.

то есть, что-то вроде структурного редактора типа SEDIT из InterLisp mako vm или eisl ISLISP.

но почему-то без лиспа или ML.

anonymous
()
Ответ на: комментарий от anonymous

strannik/blob/master/src_a_(Modula)/Demo2_1.m#L103

няшная такая модула с ресурсами диалогов и контролов окон прямо в исходниках :))

ADW StoneBrook модула-2 под win32 ещё компактные бинарники выдаёт

anonymous
()
Ответ на: комментарий от anonymous

strannik/blob/master/src_a_(Modula)/демо5_4.m

окошко в DirectX

ну и переводил бы и ключевые слова модулы-2, а не только идентификаторы

а то смесь французского с нижегородским, тупо из-за того что ключевые слова не на русском.

очередной Яр-Тур.~ получился бы.

anonymous
()
Ответ на: комментарий от alysnix

ловишь в main все необработанные исключения, пишешь - unhandled exception catched

Там уже поздно. Так уже проще std::set_terminate прицепить.

Проблема в том, что исключения используют для восстановимых ошибок. А то, что они могут прилететь откуда угодно, заставляет писать программы, которые не должны внезапно падать, в стиле:

for(auto a : get_data()) {
   try {
      x = x + а.d1 * a.d2;
   } catch(...) {}; // игнорируем недокументированное
}
~foo::foo() 
{
   try {
      somecode();
   } catch(...) {}; // в деструкторе нельзя исключения
}
monk ★★★★★
() автор топика
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от anonymous

чтобы SSA представление. удобное для оптимизации.

Так зачем его строить для своего языка самостоятельно, если компилятор Си++ его может построить за тебя?

ну и ещё можно проходы оптимизации ключиками подобрать произвольные.

А зачем? Почти всегда нужны только аналоги -O0-O3.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Так зачем его строить для своего языка самостоятельно,

ну это не то чтобы так уж и сложно, но дьявол в деталях.

например, XDS компилятор модула-2/оберон-2/С с общей кодовой базой, оптимизациями и кодогенераторами.

исходники они открыли, есть на гитхабе excelsior-oss/xds как и новая версия excelsior-oss/xds-2.60

ещё у них был любопытный AOT компилятор Явы Excelsior JET (но без исходников), на гитхабе есть плагины и примеры, но не сам JET (он кажется на модуле-2 был написан). но это не вполне актуально с учётом GraalVM (гораздо более тяжеловесной).

IR там описано: ir-win.txt ir_def.ob2 ir.ob2, но есть также и SSA представление excelsior-oss/xds/blob/master/Sources/Comp/src/be_krnl/ssa.ob2

из документации: excelsior-oss/xds/blob/master/Sources/Doc/Comp/src/intro.tex:

\xds{} is based on a platform-independent front-end for both source languages which performs all syntactic and semantic checks on the source program. The compiler builds an internal representation of the compilation unit in memory and performs platform-independent analysis and optimizations. After that the compiler emits output code. It can be either native code for the target platform or text in the ANSI C language. ANSI C code generation allows you to cross compile \mt{}/\ot{} for almost any platform.

\xds{} compilers produce optimized ANSI C code which is further compiled by a C compiler. The call of a C compiler can be done transparent for the user. However, \xds{} can be used as \mt/\ot{} to C translator, as it produces easy readable and understandable text. It is also possible to preserve your source code comments in their original context.

бекендов несколько: src/be_*, один для трансляции через си, другой нативный в родной код.

ещё там BURG где-то используется.

если компилятор Си++ его может построить за тебя?

может, но нативный обычно работает быстрее.

а сгенерированное бекендом в си можно пересобрать другим компилятором – для переносимости. например, сгенерировать под одной системой и попытаться пересобрать под другой.

А зачем? Почти всегда нужны только аналоги -O0…-O3.

может, какой-то дополнительный проход оптимизации будет более полезный именно для этого языка.

anonymous
()
Ответ на: комментарий от monk

Проблема в том, что исключения используют для восстановимых ошибок.

ну и как они должны восстанавливаться если условий и перезапусков нет, сигнального протокола ошибок нет, стек уже размотан и как их перезапускать – в общем случае непонятно в с++?

// в деструкторе нельзя исключения

а в конструкторе что ли можно? :)

anonymous
()
Ответ на: комментарий от monk

Это было бы важно, если на LLVM писать вручную.

Так и пишется вручную! В компиляторе бывает для каких либо кейсов написан шаблон из 400 строк кода на C или LLVM IR, который немного меняется в зависимости от ситуации. Но пишется он вручную.

При генерации можно приведение типа хоть на каждую операцию явно указывать.

Даже для тех случаев где не надо вручную писать множество строк кода, есть другая проблема. Пример:
Без приведения типов:

uint64_t const __r3 = foo(__r0, __r1, __r2);

С приведением типов:

uint64_t const __r3 = (uint64_t (*)(const uint8_t*, uint64_t, uint64_t))foo((const uint8_t*)__r0, (uint64_t)__r1, (uint64_t)__r2);

Для меня первый вариант является более читаемым, а при разработке компиляторов периодически приходится читать что там компилятор нагенерировал, чтобы понять где ошибка.

Стандартная библиотека у LLVM та же. И добавляет проблем при генерации вызова

declare i32 @puts(ptr nocapture) nounwind

А потом запускаем на платформе, где int = int64 или int = int16 и получаем странное поведение.

В начале напомню, что в моём комментарии я писал не только про стандартную библиотеку, но и про встроенные в компилятор функции (аля __builtin_*):

... многие функции из стандартной библиотеки, а также многие встроенные в компилятор функции и операторы ...

Это важно для пояснения! Как я уже написал выше в этом комментарии, внутри компилятора приходится писать на том, что генерирует компилятор. При написании таких кусков кода на C, начинаешь использовать то, что C предоставляет, а когда используешь LLVM IR, соответственно начинаешь использовать его возможности. У LLVM все размеры (почти) известны, а вот в C - нет, что доставляет неудобства. В случае со стандартной библиотекой, поскольку

на платформе, где int = int64 или int = int16 и получаем странное поведение

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

В C++ можно сделать std::make_signed_t<std::size_t>.

Я C++ никогда не использовал. Да и ведь проблема всё равно остаётся, ведь это решение частного случая, а не проблемы в целом.

Например? Что есть в LLVM и нет аналогичного __builtin в GCC?

Вектора (SIMD) из бит (нет в GCC, но есть в clang), адекватная конвертация из векторов из бит в числа с соответствующим количеством бит (нет даже в clang, но в clang можно костыльным образом делать), есть аналогичные __bultin'ы, но варианты для типов данных которых в gcc и clang нет, memcpy_inline и memset_inline (нет в GCC, но есть в clang), некоторые функции из стандартной библиотеки, но варианты для типов данных которых (вариантов) в gcc и clang нет, встроенная работа с матрицами (нет в GCC, но есть в clang). Уверен что много ещё чего, но это так, что вспомнилось.

Taetricus
()
Ответ на: комментарий от LamerOk

Можно примеры?

https://en.cppreference.com/w/c/string/multibyte/mbrtoc32

ssize_t же.

Редко пользуюсь size_t, поэтому спросил у гугла есть ли signed size_t, он выдал https://stackoverflow.com/questions/19352403/typedef-for-a-signed-type-that-can-contain-a-size-t, я не внимательно прочитал и сделал неверный вывод, за подсказку спасибо, там где у себя накостылил - переделаю. Но на всякий случай скажу, что саму проблему неизвестного размера типов данных это не решает.

Taetricus
()
Ответ на: комментарий от anonymous

может, но нативный обычно работает быстрее.

Имеет смысл только если твой фронтенд не хуже оптимизирует, чем clang, например.

Вот для сравнения две версии компилятора v. Одна скомпилирована clang из v.c, а вторая из v.ll (достаточно прямолинейно преобразованного из С).

$ time ./v-over-ll -o v cmd/v

real    0m25,505s
user    0m24,040s
sys     0m1,047s
$ time ./v-over-c -o v cmd/v

real    0m23,435s
user    0m22,419s
sys     0m1,006s
monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Тест не корректный, не помню подробности, но вроде если компилировать ll файл с помощью clang, то он проведёт некоторые действия которые он делал с C файлами повторно. Вот вроде как надо:

time clang -O3 -Wno-everything v.c -o v

real	1m14,955s
user	1m14,351s
sys	0m0,286s

clang -O3 -S -emit-llvm -Wno-everything v.c -o v.ll
time clang -O3 -Wno-everything v.ll -o v1

real	1m2,825s
user	1m2,357s
sys	0m0,200s

time llc -O3 --filetype=obj --relocation-model=pic v.ll -o v2.o

real	0m36,100s
user	0m35,766s
sys	0m0,181s

time clang v2.o -o v2

real	0m0,378s
user	0m0,325s
sys	0m0,052s

ls v*
-rwxr-xr-x 1 taetricus 4,8M авг 13 20:15 v
-rwxr-xr-x 1 taetricus 4,9M авг 13 20:18 v1
-rwxr-xr-x 1 taetricus 4,8M авг 13 20:47 v2
-rw-r--r-- 1 taetricus 6,4M авг 13 20:13 v.c
-rw-r--r-- 1 taetricus  58M авг 13 20:17 v.ll
-rw-r--r-- 1 taetricus 7,4M авг 13 20:33 v2.o

Как видно размер в моём варианте ближе, но v и v2 не совпадают, не помню как делать полностью идентичные файлы. Но всё это сравнение тёплого с мягким, поскольку нужно сравнивать два компилятора одного языка один который использует C, а второй LLVM. Поскольку при компиляции C в LLVM IR, clang производит оптимизации, которые нужно будет делать компилятору гипотетического языка, а этот тест этого не учитывает.

Taetricus
()
Ответ на: комментарий от anonymous

ну и как они должны восстанавливаться если условий и перезапусков нет, сигнального протокола ошибок нет, стек уже размотан и как их перезапускать – в общем случае непонятно в с++?

Сигнальный протокол и перезапуски для C++ — это передача обработчика ошибок параметром. А через исключения мы ловим результат. Например, если текстовый редактор не смог прочитать файл, он не должен при этом падать, а должен просто сообщить, что не получилось.

а в конструкторе что ли можно? :)

Можно. В том смысле, что при этом не происходит безусловного падения программы. В худшем случае будет утечка ресурсов, а если их выделать через умные указатели, то и её не будет.

monk ★★★★★
() автор топика
Ответ на: комментарий от Taetricus

В компиляторе бывает для каких либо кейсов написан шаблон из 400 строк кода на C или LLVM IR

Это пишется один раз в жизни. И в этом шаблоне можно просто типы руками проставить для всех значений, всё равно его никто читать не будет.

а при разработке компиляторов периодически приходится читать что там компилятор нагенерировал, чтобы понять где ошибка

Опять же. При разработке, а не при использовании.

но и про встроенные в компилятор функции (аля _builtin*)

И? Если это твой компилятор, можешь все типы указать явно. Если надо совместимость с Си, то при трансляции в LLVM всё равно придётся велосипедить свой stddef.h + длины сишных типов.

При написании таких кусков кода на C, начинаешь использовать то, что C предоставляет, а когда используешь LLVM IR, соответственно начинаешь использовать его возможности.

LLVM предоставляет свою альтернативу POSIX? Как ни крути, компилятор придётся использовать системные вызовы, у которых сишный ABI.

я писал простую библиотеку на другом языке (не обязательно на C), в которой размер всех параметров и результатов был равным на всех платформах и эту библиотеку линковал с основным кодом.

И в какой размер транслировался, например, int fd вызова write? int64? А при вызове на микроконтроллере на каждый write приходилось расширять передаваемый номер до 64 битов?

Вектора (SIMD) из бит (нет в GCC, но есть в clang)

https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html

http://rsusu1.rnd.runnet.ru/linux/doc/c_ug/comm1046.htm

?

memcpy_inline и memset_inline (нет в GCC, но есть в clang)

Есть: https://godbolt.org/z/acWx5Gqc8

встроенная работа с матрицами (нет в GCC, но есть в clang)

Зачем? Автовекторизация и так работает. А прибивать гвоздями типы матриц к компилятору не очень хорошо.

monk ★★★★★
() автор топика