LINUX.ORG.RU

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

 ,


3

6

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

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

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

Сравнивать надо в пределах одного и того же компилятора.

С чего бы? Компиляторы не так используются. С трансляцией в Cи можно использовать быстрый компилятор без оптимизаций для разработки, а оптимизированный, но медленный для конечной компиляции. С LLVM у тебя что так, что эдак – тормоз.

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

С LLVM у тебя что так, что эдак – тормоз.

Если смотреть сюда то наоборот gcc тормоз, и если там же добавить параметр -O3 к clang то компиляция замедлится примерно в 10 раз. Так что все нормально у llvm с ускорением при компиляции без оптимизации.

anonymous
()
Ответ на: комментарий от yvv1
  1. В LLVM IR нет даже аналогов int_least32_t или int_fast32_t. А использовать i64 переменные на 8-битном процессоре не очень удобно.

  2. Так это наоборот плохо. Часть оптимизаций приходится делать на фронтенде. Для ptrtoint надо явно указывать битность адреса.

  3. Намного хуже, чем у Си++. Даже хуже, чем у Си. Типизированных указателей нет.

  4. Ложь. malloc, free и alloca такие же как в Си. Пулы и прочее также через библиотеки.

  5. Также ложь,

  6. lto есть в gcc. -fwhole на Эльбрусе работает для Си и не работает для llvm.

  7. Вот разве что.

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

Ну к примеру это у тебя тупо вся программа - машина состояний, у которой нет конечного состояния.

Если цикл бесконечный и в нём нет ввода-вывода, то она ничего наблюдаемого не делает.

Или это поток, весь смысл которого - получать сообщение от кого-то, отвечать на него как-то и делать это бесконечно (пока ОС его не прибьёт).

  1. Бесконечный цикл с вводом (а сообщение это ввод) в Си++ не выкидывается.

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

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

ir llvm это такой типа супернаворочанный мегаассемблер. на нем можно нарисовать любое «замыкание».

Можно. Также, как и на Си++. А можно взять ассемблер с более близкой семантикой, но если для него нет такого эффективного компилятора, как для Си++ или LLVM, то скорость программы будет ниже.

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

За жизнь спроектировал примерно 11 языков и написал примерно 12 компиляторов (некоторые забрасывал не доделав до минимально рабочего состояния). В том числе сейчас занимаюсь разработкой компилятора. Один компилятор компилировал в Flat Assembler, два в Си (в том числе текущий), а остальные в LLVM IR (генерировался вручную). Субъективно для генерации кода LLVM IR удобнее Си раз в 5. Причины:

  • Слабая типизация в Си, сильная в LLVM. Примерно 30% всех ошибок допущенных мною при написании Си кода, связанны со слабой типизацией. При этом я прекрасно знаю как Си работает с типами но бывает что уставший или отвлёкся или пишешь код 12+ часов подряд и где-то допускаешь ошибку, при этом код с ошибкой выглядит корректно. Пример:
    uint64_t const a = foo(); // a = 30
    uint64_t const b = 15 << a; // кажется что результат 0x3'C000'0000, на самом деле 0xFFFF'FFFF'C000'0000
    
  • Неизвестный размер стандартных типов данных в битах в Си, известный в LLVM. Размер Char в битах - зависит от архитектуры, размер int в битах - зависит от архитектуры и компилятора, но не менее 16 бит. Можно было бы просто использовать uint8_t и т.д., но многие функции из стандартной библиотеки, а также многие встроенные в компилятор функции и операторы используют такие типы, что тоже доставляет проблем. Например есть функции которые в случая ошибки возвращают отрицательные числа (-1, -2 и т.д.) но при этом имеют тип size_t, размер в битах которого неизвестен, который является без-знаков типом и при этом не имеет знакового аналога. И для того, чтобы узнать, что произошла ошибка (неважно какая), в место
    if ((signed size_t)foo() < 0)
    , приходится писать
    if ((foo() >> sizeof(size_t) * 8 - 1) == 1)
    . Также бывали ошибки из за этого, например хотел посчитать количество установленных бит в числе (с помощью __builtin_popcount), забыл что число имеет тип char, число было отрицательным, char вначале преобразовался в int, int преобразовался в unsigned int и уже в нём было подсчитано количество установленных бит. Разумеется результат не корректный.
  • В LLVM гораздо больше различных встроенных стандартных функций, особенно это касается SIMD векторов, с которыми мне много приходится работать. Ситуация улучшает с появлением новых стандартов Си. Например в c23 добавили числа с указанным количеством бит, которые в LLVM есть уже давно, но по прежнему многие функции в Си не работают с ними, например rotate_left ил bswap да тот же popcount, при этом опять же всё это уже давно есть в LLVM.
  • В LLVM меньше неопределённого поведения чем в Си, причём в LLVM в некоторых случаях можно выбрать хочешь ли ты неопределённого поведения или нет.
Taetricus
()
Ответ на: комментарий от dataman

Выбор оптимального размера int происходит во время перевода кода в IR.

Так и я про то же. При переводе надо учитывать целевую платформу, поэтому ни о каком «Platform independence» не может быть и речи.

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

Слабая типизация в Си, сильная в LLVM. Примерно 30% всех ошибок допущенных мною при написании Си кода, связанны со слабой типизацией.

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

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

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

declare i32 @puts(ptr nocapture) nounwind

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

Например есть функции которые в случая ошибки возвращают отрицательные числа (-1, -2 и т.д.) но при этом имеют тип size_t

По стандарту может быть только -1.

if (foo() != (size_t)(-1))

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

В LLVM гораздо больше различных встроенных стандартных функций, особенно это касается SIMD векторов, с которыми мне много приходится работать.

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

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

Здесь согласен. Но для генерированного кода это также менее актуально. В конце концов и для генерированного Си можно везде вместо a+b писать __builtin_add_overflow и не иметь UB с переполнением.

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

LLVM удобен, когда нужно портировать компилятор на новую платформу. Сильно меньше работы - сделать транслятор из IR в ассемблер целевого контроллера.

Например, IAR раньше делал компиляторы C/C++ для микроконтроллеров полностью самостоятельно, но не потянул постоянно внедрять новые языковые возможности, и переключился на LLVM. Теперь им проще обновлять версии стандартов.

То же самое они могли провернуть с GCC-шным RTL, но не стали. Лицензия не та :-)

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

LLVM удобен, когда нужно портировать компилятор на новую платформу. Сильно меньше работы - сделать транслятор из IR в ассемблер целевого контроллера.

Вот не сказал бы. Объем бэкенда для Си++ и IR сопоставим.

IAR раньше делал компиляторы C/C++ для микроконтроллеров полностью самостоятельно, но не потянул постоянно внедрять новые языковые возможности, и переключился на LLVM.

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

То же самое они могли провернуть с GCC-шным RTL, но не стали. Лицензия не та :-)

Могли компилировать в C++, а потом через GCC в бинарник. У GCC только исходники под GPL. Использование его как компилятора разрешено в любых целях.

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

есть функции которые в случая ошибки возвращают отрицательные числа (-1, -2 и т.д.) но при этом имеют тип size_t,

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

и при этом не имеет знакового аналога

ssize_t же.

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

В этой теме обсуждаем выбор между «компилировать в LLVM» и «компилировать в Си++».

Идеи «делать свой велосипед», «слинковать с GPL» и прочие к теме не относятся.

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

Много кто перестал делать свои компиляторы C++

Раньше даже трансляция в си не очень популярна была, а в C++ кроме nim и felix(который прямо позиционируется как генератор с++ кода) и не слышал больше. Вот трансляция прямо в машинный код или реже в ассемблер была вполне популярной.

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

Если

Объем бэкенда для Си++ и IR сопоставим.

то почему тогда

Много кто перестал делать свои компиляторы C++, а стал транслировать в LLVM.

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

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

unspecified

Значит можно

Нет, нельзя, потому что в unspecified входит, например «если сейчас вторник, то в каждую чётную секунду все указатели равны».

the specializations for any pointer type yield a total order, even if the built-in operators <, >, <=, >= do not.

А вот это прикольно. Не подозревал о существовании такого.

i-rinat ★★★★★
()

Я не понимаю, почему не использовать просто компиляцию через Си или Си++

Потому что специально спроектированный LLVM IR или MLIR в качестве IR подходят лучше чем ЯП, но если тебе интересно, то есть некоторые языки, например Nim, которые сначала транслируются в Си.

Оптимизации сделает компилятор Си

Ему для этого неплохо было бы какой-то свой IR тоже иметь просто.

А во вторых, на некоторых языках можно сделать вещи, которые нормально оптимизируются только в случае использования IR.

Я, например, с трудом могу представить, что Option тип будет нормально оптимизироваться при трансляции в сишку, а не в IR.

Или например что какой-нибудь lambda folding упомянутый в книжке let over lambda будет при трансляции в Си нормально работать.

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

Компилировать из C++ в C++? А в чём тогда будет состоять их продукт? Что они будут продавать?

Вот именно. А то, что трансляция в LLVM почти настолько же бессмысленна, не так заметно.

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

Нет, нельзя, потому что в unspecified входит, например «если сейчас вторник, то в каждую чётную секунду все указатели равны».

Но программа не упадёт и не запустит ядерную бомбу.

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

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

Или например что какой-нибудь lambda folding упомянутый в книжке let over lambda будет при трансляции в Си нормально работать.

А чем ему проще при трансляции в LLVM IR? В обоих случаях надо определить, что функция не замыкание, сравнить с уже преобразованными с точностью до имён переменных и если есть совпадение, заменить на вызов уже сформированной.

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

А чем ему проще при трансляции в LLVM IR?

Я вообще про IR в общем говорил.

Впрочем, мне кажется, что я недостаточно разбираюсь и мне не хватает экспертности нормально ответить на твой вопрос. Извини, что вообще попытался.

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

Так это проще, чем транслировать в оптимизированный ассемблер.

Ну я об этом и говорю. Вот смотри, нужно тебе написать C/C++ компилятор для новой архитектуры. Вариантов три:

  • написать полностью самостоятельно
  • сделать на основе clang/LLVM
  • портировать gcc.

Первый вариант очень сложный. Сейчас так не делают.

Второй вариант сильно проще - нужно только сделать трансляцию LLVM IR в ассемблерные инструкции. Остальное взять готовое от clang. Продукт можно закрыть.

Третий вариант похож на второй - нужно сделать трансляцию gcc-шного RTL в ассемблерные инструкции. Не знаю, сложнее ли это, чем IR, но результат будет под GPL. Вероятно, это главная причина.

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

А то, что трансляция в LLVM почти настолько же бессмысленна, не так заметно.

Если на целевой платформе нет компилятора C++ (новая платформа), то смысл очень даже есть:)

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

LLVM удобен, когда нужно портировать компилятор на новую платформу. Сильно меньше работы - сделать транслятор из IR в ассемблер целевого контроллера. Например, IAR…

Могли компилировать в C++, а потом через GCC в бинарник.

Так им же нужен не gcc-бинарик, а бинарик под новую платформу. IAR создаёт IDE для микроконтроллеров. Если для 32-битных контроллеров ещё есть свой порт gcc, то для 8-битных нет (хотя можно найти в сети какие-то заброшенные попытки сделать).

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

использует туже самую технологию трансляции в промежуточный язык с дальнейшей оптимизацией

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

если промежуточный язык будет высокоуровневым, то возникнут проблемы из-за того, что фронтенд не будет знать например физический размер типа. например результат функции sizeof(some_type), который нужен фронтенду, для статических вычислений.

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

а поскольку вполне вероятно, что си компилятор сам реализован на llvm, то получается, что си грамматика вообще излишня, и можно сразу гнать в ir llvm.

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

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

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

Что «думает» Llama о LLVM IR vs MLIR?

Llama не имеет личных мнений, но я могу предоставить сравнение LLVM IR и MLIR.
LLVM IR (Intermediate Representation) - это промежуточное представление кода, используемое в компиляторе LLVM для оптимизации и генерации машинного кода. LLVM IR является низкоуровневым, тесно связанным с машинным кодом и ориентированным на производительность.
MLIR (Multi-Level Intermediate Representation) - это более высокоуровневое промежуточное представление, предназначенное для поддержки различных уровней абстракции и оптимизации. MLIR может представлять как высокоуровневые абстракции, так и низкоуровневые детали, что позволяет использовать его для различных задач, включая компиляцию, трансляцию и оптимизацию.
Основные отличия LLVM IR и MLIR:

    Уровень абстракции: LLVM IR - низкоуровневый, MLIR - многоуровневый.
    Цели: LLVM IR - оптимизация и генерация машинного кода, MLIR - поддержка различных уровней абстракции и оптимизации.
    Применение: LLVM IR - компиляция и оптимизация машинного кода, MLIR - компиляция, трансляция, оптимизация и другие задачи.

В целом, LLVM IR и MLIR решают разные задачи и могут использоваться вместе для достижения лучших результатов в различных областях.
I am still improving my command of other languages, and I may make errors while attempting them.
yvv1
()
Ответ на: комментарий от Beewek

нужно тебе написать C/C++ компилятор для новой архитектуры

Согласен. Я больше озадачен про всякие Rust/Haskell и прочие новые языки.

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

Я понимаю ее как своеобразную «универсальную среду» сборки ПО, ведъ в последние 10-15 появилось огромное количество различных архитектурных платформ, к тому же виртулизация развилась стремительно. А у классических компиляторов есть один большой недостаток - они очень сильно завязаны на какой-то определенной платформе, например gcc на x86 и осуществлять сборку пакетов под разные архитектуры довольно большая проблема. Второй аспект, заключается в юридических терках на лицензию, у gcc довольно большие ограничения в ее использование есть. Если я не ошибаюсь, то freebsd gcc выкину из поддержки по этой причине.

nager
()

https://en.wikipedia.org/wiki/Cfront

Cfront 4.0 was abandoned in 1993 after a failed attempt to add exception support.

https://en.wikipedia.org/wiki/C--

C is a poor choice for functional languages: it does not guarantee tail-call optimization, or support accurate garbage collection or efficient exception handling.

Думаю, корутины на си тоже хорошо не сделать.

C++ по-прежнему половину из этого не умеет, да еще мощно тормозит компиляцию.

unsigned ★★★★
()
Последнее исправление: unsigned (всего исправлений: 2)

Я не понимаю, почему не использовать просто компиляцию через Си или Си++.

Любой сколь-нибудь современный компилятор Си (включая GCC) реализует внутри себя что-то по типу LLVM. То есть компилятор сначала конвертирует код на Си во внутреннее представление, а потом из этого внутреннего представления генерирует машинный код.

Раз оно так устроено, то почему бы не предоставить работу с внутреннем представлением и кодогенерацией в качестве отдельной библиотеки? Это проще, быстрее и удобнее чем генерить текст кода на Си.

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

Чистые функции существуют только в воображении. В реальном мире существуют исключения при нехватке памяти, неподдерживаемых инструкциях, битой памяти в конце концов.

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

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

В реальном мире double sin(double x) может вернуть для одинаковых x разные результаты?

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