Как стать автором
Обновить

Комментарии 136

Ну что сказать, реклама LLVM удалась ;)

Согласен, хоть и плюсануть не могу.

По мне, так clang тормознее в компиляции, чем gcc.
С другой стороны — в статье отмечено важное преимущество перед обычными компиллерами(или правильнее сказать линковщиками под конкретную платформу?) — байткод реально переносим, но только в том случае, если байт реально 8бит, а не к-то вывернутый DISP-проц с 10-битным байтом и нормальной Си-IDE

Опять же — речь идёт только о высокоуровневых языках, в то время как на Си или Rust(когда же он заржавеет) шланг сливает, так же как и любому алгоритму, заточенному под платформу — посмотрите на код ffmpeg — там если его компилить по дефолту — то туча «вариативно», то туча объектников собирается полчаса под каждый вариант набора комманд проца…

У вас несколько неверное представление, поэтому вы пару раз перемешали мух с котлетами:


  • Назвать LLVM IR байт-кодом не совсем корректно, мягко говоря. Тогда и исходник на C — тоже байт код, ибо тоже состоит из байтов ;)
  • В терминах LLVM IR программа манипулирует "примерно регистрами" заданной разрядности. Поэтому (наоборот) такая IR-программа будет относительно переносима между (не привязана к) 8-битной ATMega, 40-битному SHARC, 32/64-битному x86 и т.д.
  • На практике LLVM IR переносим примерно как шарообразный конь в вакууму. Т.е. пока некий код "шарообразный" и "в вакууме" — он переносим, а если чуточку иначе — сразу бац и коллапс волновой функции по массе причин ;)
Поэтому (наоборот) такая IR-программа будет относительно переносима между (не привязана к) 8-битной ATMega, 40-битному SHARC, 32/64-битному x86 и т.д.

Более переносима, чем машинный код; но намного менее переносима, чем исходник на ЯВУ. Цель переносимости между целевыми архитектурами перед LLVM-IR и не ставилась.

В контексте удобно рассказать о знатной бага-фиче с [[gnu::pure]] в C++ и __attribute((pure))__ в C:


  1. В самом clang атрибут "pure" не документирован, но на зло для совместимости с gcc поддерживается фронтендом и обрабатывается бэкендом, а также честно виден через __has_attribute(pure).
  2. Встречая [[gnu::pure]] clang насильно включает noexcept(true), т.е. задним числом меняет тип функции (начиная с C++17).
  3. Всё происходит молча, без каких-либо предупреждений, при включении любой диагностики, и даже при наличии у функции явного noexcept(false).

Теперь представьте, что у вас проект с использованием десятка продвинутых C++ библиотек, в одной из которых есть трех-этажные шаблонны с абсолютно обоснованным использованием [[gnu::pure]] в паре-тройке функций, т.е. с втихую добавленными noexcept(true).


Этот noexcept(true) неплохо "протекает" через инлайнинг и IPO. При этом clang много чего оптимизирует/совмещает кадры стека и т.д. Соответственно, в каких-то случаях появление исключения приведет к вызову std::terminate(), а в других нет.


При каких-нибудь жалких 10-20К строк кода отлаживать сплошное удовольствие. Я провозился несколько часов, пока не дошел до "поштучного" анализа unwind-таблиц. Что характерно, эта бага-фича гуглится элементарно, как только понимаешь связь между [[pure]] и проблемами с исключениями. Но никак не раньше ;)

Спасибо Yermack за дополнение! — компилятор Julia тоже сделан на основе LLVM.
А можно пояснить для тех, у кого навыки работы с Си остались в университетском прошлом, что же случится при компиляции и выполнении той программы?
Если использовать gcc, то программа напечатает «begin» и войдёт в бесконечный цикл.

Если использовать clang, то программа напечатает «begin» дважды (!) и дальше упадёт в кору. Не спрашивайте. Хорошо хоть демоны не появились…
Это при использовании достаточно старого clang (6.0). В новых версиях может быть всё по-другому. Как бы там ни было, полагаться на неопределённое поведение — ошибка!
А как нынче правильно сделать бесконечный цикл?
Вам понравится мой хабрапост шестилетней давности: habr.com/ru/post/229963

Проще всего внутри цикла сделать что-то полезное (а иначе зачем он вообще нужен?)


Если нужен пустой бесконечный цикл — действительно есть смысл прочитать хабрапост tyomitch.

а иначе зачем он вообще нужен?
Например для того, чтобы ничего не делать. На некоторых процессорах нет инструкции «ничего не делать».

Можно просто сделать доступ к volatile переменной. Например, увеличить ее на 1.

А что произойдёт, когда счётчик переполнится?
Отвечаю себе: если переменная unsigned — то ничего не произойдёт. Если signed — то опять undefined behavior.

Будет UB. Но тогда просто присвоить 1.

В порядке курьёза:

static unsigned foo = 0;

static void die() {
    while(1)
        foo++;
}

--превращается в бесконечный пустой цикл, как вы и просили :-)
(Но никто не гарантирует, что такое поведение сохранится и в будущих версиях.)
В STM32 например постоянное обращение к памяти в два раза замедляет DMA.
В том и фишка, что обращения к памяти в цикле не будет.
Ааа, не заметил, что здесь static вместо volatile. Странно, что оптимизатор это всё не выкинул.
Чтобы счётчик не переполнился, надо использовать операцию «подёргивания» (++a--) :)
Неужели оптимизатор не выкинет эту конструкцию?)

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

А и действительно. Но выглядят эти костыли к циклу конечно странно. Почему бы компилятору просто не сделать то, что ему сказали.
-O0 говорит компилятору «просто делай то, что тебе сказали».
Но обычно пользователи компилятора хотят от него совсем не этого.
Пользователи хотят UB в пустом цикле?)

И тогда CPU будет кушать примерно вдвое больше энергии.


Лучше __asm __volatite("").


P.S.
Интересна логика (и познания) "первого минуса" ;)

Если не секрет, на каких процессорах NOP нет?
Речь не о NOP же, а о HLT, WFI и прочих.
Обычно пустой цикл всё-таки для NOPов, ака реализация блокирующей задержки, в сон до прерывания не видел, чтобы так уводили.

Чуть менее чем во всех мультизадачных ОС используется именно такой паттерн: idle-задача с минимальным приоритетом в виде бесконечного цикла с HLT-подобной инструкцией (остановка CPU до прерывания).


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

Но там же не пустой цикл с volatile (или обычным с -О0) счётчиком?

Честно говоря я уже запутался в том, кто и что здесь пытался сформулировать и обсудить. В реальность уже лет 20-30 примерно так:


  1. __asm __volatile("bla-bla-bla") — чтобы компилятор не выбросил конструкцию и не задумывался о UB в бесконечных циклах.
  2. __asm __volatile("bla-bla-bla" ::: "memory") — тоже самое, плюс уведомление компилятора об изменении любых переменных в памяти.
  3. __asm __volatile("HLT" ::: "memory") — в idle-задаче или планировщике.
  4. __asm __volatile("PAUSE" ::: "memory") — в циклах ожидания на блокировках.
  5. __asm __volatile("NOP") — для заполнения при выравнивании и в циклах задержки.
Я спрашивал про NOP в ответ на это обсуждение:
Проще всего внутри цикла сделать что-то полезное (а иначе зачем он вообще нужен?)
Например для того, чтобы ничего не делать. На некоторых процессорах нет инструкции «ничего не делать».

Совсем пустой бесконечный цикл не нужен, поэтому и причислен к UB. Но цикл с чем-нибудь вроде __asm __volatite(...) уже не является совсем пустым и не вызывает UB.


С небольшими оговорками можно сказать что инструкции NOP/PAUSE/HLT или их аналоги есть на всех архитектурах CPU:


  • с точки зрения кодирования команд CPU эти инструкции могут быть синонимами других реальных инструкций. Например, на x86: NOP = XCHG AX, AX; PAUSE = REP XCHG AX, AX
  • исполнительное ядро CPU (в том числе в зависимости от модели/ревизии) может либо обрабатывать их в точности в соответствии с кодированием (Intel 8086 вместо NOP реально выполнял XCHG AX, AX), либо в соответствии с дополнительной семантикой (Intel 486 начал именно выполнять PAUSE уступая шину, а не REP NOP).
  • в худшем случае вместо NOP/PAUSE/HLT могут использоваться (замещаться при ассемблировании) заведомо безобидные и всегда доступные инструкции. Например, условный или безусловный переход к следующей команде.
Типичный сценарий: что-то делать в обработчиках прерываний, ничего не делать в основном потоке. Инструкция NOP вам тут мало чем поможет.
Зачем нам в таком случае пустой цикл? В main loop тогда может быть перевыставление watchdog и уход в сон. Но если у нас нет инструкции ухода в сон, то тогда процессор будет в цикле молотить, явно выполняя переходя между итерациями цикла. Или это и есть ожидаемое поведение?
Зачем нам в таком случае пустой цикл?
Эээ, а какие есть альтернативы? Не пойму вашу мысль.
Давайте разверну свою мысль, а то мы кругами ходим, хотя я заранее признаю, что текст ниже попахивает извращениями и с вечным циклом проще.
Main loop нам нужна для того, чтобы instruction pointer не ушёл за границы программы с неясными последствиями. Если работаем только на прерывания и у процессора есть HLT, то мы после инициализации ставим HLT, а для RET/RETI смещаем адрес возврата, чтобы вернуться на тот же HLT, а не после него. Если HLT или аналога нет, то тут только цикл, выполняющий NOP, пока не будет перехода по прерыванию.
В цикле NOP не нужен, так как не несёт никакого смысла, достаточно просто написать jmp $, и вечный цикл готов.
> __asm __volatile(«bla-bla-bla» ::: «memory»)

С C++11 работает также: std::atomic_signal_fence(std::memory_order_release);
// любой кроме relaxed, хотя я бы ставил acq_rel как аналог такого «memory».

На всякий: __volatile + __asm("":::"memory") даёт именно std::memory_order_acq_rel.


Т.е. в плюсах для аналогичного эффекта нужно std::atomic_signal_fence(std::memory_order_acq_rel), а не что-то другое.

Ну так я и говорю
> хотя я бы ставил acq_rel как аналог такого «memory».

А минимум в том смысле, что чтобы цикл не считался пустым.

Извините, а разве это не баг clang’a?

Для UB допускается генерация любого кода.
А что плохого в бесконечных циклах? Чем явно бесконечный цикл отличается от обычного по какому-то вычисляемому и неизвестному на этапе компиляции условию?
И как быть без бесконечных циклов к примеру в embedded разработке, где обычно главная функция — именно бесконечный цикл, внутри которого выполняется вся работа устройства?
На самом деле речь идёт не о любом бесконечном цикле, а бесконечном цикле без side-эффектов, т. е. никак не меняющем состояние программы. Циклы же с side-эффектами вполне себе defined.
Стоит добавить, что не всегда очевидно, есть внутри цикла сайд-эффекты или нет.
Например, изменение глобальной переменной не будет сайд-эффектом, если эта переменная нигде не читается.

Речь о бесконечных циклах без внешних эффектов. Если внутри цикла выполняется работа устройства — у него есть побочный эффект, и там уже без UB.

На самом деле, неопределённое поведение даже в одном компиляторе очень зависит от версии: кто-то может пропустить и оставить зависающий цикл, кто-то выдаст ошибку, а кто-то молча уберёт его в виде оптимизации т.к там ничего не выполняется. Можно вставить этот код с -O2 в godbolt.org и посмотреть итоговый ASM по разным версиям Кланга – удастся найти все перечисленные варианты поведения.

А с каких пор openmp в clang стал лучше gccшного?

Наверное, говорить «лучше» всё же некорректно (зря я использовал это слово… оно в принципе очень скользкое).

Думаю, правильней было бы привести те же аргументы, что я привёл в самом начале раздела «LLVM против GCC». Благодаря «либеральной» лицензии, интерес к LLVM версии OpenMP библиотеки (libomp) сейчас явно больше, чем к GNU версии (libgomp) — и её проще использовать в проектах с частично закрытым кодом, в том числе для поддержки акселераторов при гетерогенных вычислениях. Соответственно, в libomp больше инвестиций — со всеми вытекающими.
Спасибо за простое изложение, переодически возникал вопрос популярности llvm в последнее время, особенно при чтении статей про rust и haskell, теперь все стало понятно.
Пожалуйста!
Помимо C++ и Rust, LLVM используется в компиляторах (как статических, так и динамических) для таких разных языков как D, Fortran, Haskell, Julia, Kotlin, Lua, PHP, Python.

С недавних пор (точнее с осени прошлого года) еще и Ada:
github.com/AdaCore/gnat-llvm
blog.adacore.com/combining-gnat-with-llvm

Кроме всего прочего, это делается для того, чтобы заюзать например KLEE
valexey, спасибо за дополнение! Хотя, конечно, Ada в 2020 не модный и не хипстерский. :)
Ага :-)

Cейчас готовится очередной ISO стандарт для Ады — Ada 202x, сейчас действующий стандарт это Ada 2012. А Ada/SPARK недавно нвидия начала использовать для своего железоориентированного программирования. Но основной драйвер тут конечно SPARK (не путать с Apache Spark), ибо позволяет много чего доказать формально, что ценно когда надежность софта имеет значение.

Сейчас уже не модно, завтра снова будет модно — история циклична :-)
НЛО прилетело и опубликовало эту надпись здесь
Ада генерирует довольно нетребовательный к ресурсам код, а что с идрисом — не в курсе.
НЛО прилетело и опубликовало эту надпись здесь
Свежий действующий стандарт для Ада от 2012го, так что она тоже есть.

В военке и ракетостроении очень даже модный, особенно в западном.

Да и на гражданке тоже — всякие боинги и Airbus'ы управляются софтом на Аде писанном.
Кроме всего прочего, это делается для того, чтобы заюзать например KLEE

KLEE разве не помер?

Судя по их сайту — нет. klee.github.io
«Компилятор языка Rust также написан на LLVM».
Вероятно вы хотели написать «Компилятор языка Rust также использует LLVM»
Да, Вы правы — исправил. Спасибо за багфикс! :)
Не подскажете, есть ли какой-нибудь туториал, примеры или документация, как сделать фронт-енд для своего языка к LLVM?

На Хабре вроде было что-то, и на сайте LLVM есть статьи по этому поводу.

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

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

Извините, но… Делфи ещё развивается? Вот это для меня новость. Надо вспомнить былое.

Внезапно, да.
Сегодня Дельфи умеет компилировать приложения под Win32/x64, Android32/x64, iOS(x64), MacOS (x64), Linux (в т.ч. и Astra).
Установив третьи компоненты можно также "скомпилировать" приложение и для веба, но тут уже есть нюансы.

К слову, кроме Делфи, Фрипаскаль умеет использовать LLVM как бэкэнд. Пока на некоторых платформах, но в планах на другие перенести тоже.
Одна из важных областей применения LLVM — это тензорные компиляторы

Можете раскрыть поподробнее эту часть? пары ссылок или примеров запросов в гугл было бы достаточно :)
You made my day! :)

На счет поддержки C++ не правда. Поддержка C++20 появляется в GCC не позже чем в Clang-е. И почему это libc++ — "наиболее полная реализация стандартной библиотеки C++", чем libstdc++ хуже (в libstdc++ к примеру уже есть Ranges)? Да, Richard Smith работает над Clang-ом, зато LWG chair Jonathan Wakely работает над GCC. В целом в комитете по стандартизации разработчики GCC и Clang-а представлены одинаково.

ну и судя по этой табличке бОльшая часть фич C++20 уже реализована в GCC, в то время как в clang ещё нет.
хотя, предположу, что ещё рано говорить, ведь и в GCC ещё много чего нет.
Все утверждения на счет т.н. недостатоков GCC очень спорны. Проблема GCC vs Clang исключительно политическая — для гигантов индустрии лиценция GPLv3 как удавка на горле. Более того, наблюдаем элемент коррупционной составляющей — человек пишуший и принимающий стандарты C++ явно и непрекрыто топит за Clang.

И да, в GCC тоже есть промежуточное представление программ — RTL (LISP подобные древовидные структуры) с момента его создания.
Внутри GCC кошмарная кастрюля спагетти 33-летней выдержки; добавлять туда поддержку новых фич намного сложнее, чем в LLVM.
(Например, я был поражён, когда узнал, что GCC генерирует .o не напрямую, а создаёт временный файл с ассемблерным листингом, и пропускает его через gas.)
Таков путь Unix. Зачем ассемблировать внутри компилятора, когда есть отдельный ассемблер.
Представьте себе программу, состоящую из ~20М инструкций. Сколько времени в сумме уходит на то, чтобы для каждой инструкции сначала синтезировать ассемблерную строчку (внутри gcc), а потом распарсить её обратно (внутри gas)?
Не профилировал, но думаю что мало, если сравнивать с другими стадиями компиляции и оптимизации.
Когда возникают проблемы вызванные оптимизацией, иного способа разобраться в происходящем, кроме как как чтение ассемблерного кода — просто нет. Так что, многие разработчик, и я в том числе, очень высоко ценят возможность иметь ассемблерный листинг программы, на понятном языке, а не на каком нибудь IR.
Согласен. В LLVM эта возможность тоже есть, но как отладочная фича, выключенная по умолчанию, — а не как часть production chain.
Если ассемблирование не является одной из стадий сборки, то в стает вопрос адекватности представления ассемблерного листинга бинарному коду. И не отсохнет ли эта «мало востребованная» фича в будущем.
И я реально сталкивался с таким в одном промышленном компиляторе. Долго не мог понять где баг, а потом заметил что при одновременной генерации .o и .s код в них несколько отличается.

Есть флаг -S, который поддерживают абсолютно все компиляторы. И нет, он точно не "отсохнет". :-)


Что касается бинарного кода — не забывайте, что исполняемый файл создаёт не компилятор, а линкер. И он ещё как может поменять бинарий! — вплоть до замены инструкций (например, длинные переходы / вызовы на короткие).

Принципиальная разница в том, что для GCC этот флаг означает «сделай на один шаг меньше, чем обычно делаешь», а для Clang — «сделай на один шаг больше, чем обычно делаешь».
Поэтому баг, внесённый в генератор ассемблерных листингов, в GCC проявится сразу же (при генерации любого бинарника), а в Clang может оставаться незамеченным.


Промежуточное представление LLVM спроектировано так, чтобы иметь три вида: бинарный, текстовый и в виде структур языка C++. Изоморфизм этих представлений гарантируется разработчиками.

IR от LLVM в текстовом виде вполне понятен (и он немного более высокоуровневый, чем ассемблерный код). Аналог для GCC можно получить в виде GIMPLE-представления на момент перед expmed-шагом, но он значительно более запутан и требует сильно больше знаний для чтения.
Ассемблерный выхлоп GCC полезен только без оптимизации и если попросить выводить туда же текст исходных строк; с существенной оптимизацией его начинает корёжить так, что там уже мало понятно, откуда что пришло, и после этого читать foo.s или выдачу objdump -d foo.o — разница минимальна. (Точнее, есть в описаниях релокаций, но это редко становится предметом проблемы при оптимизации.)
Поэтому, поддерживая Вашу позицию в целом, вынужден категорически не согласиться в конкретном примере.
От создания временного файла можно отказаться ключом -pipe — тогда он действительно будет передавать через пайп. Я удивлён, что этот ключ не используется по умолчанию. (Затачиваются на то, что некоторые ассемблеры многопроходные и требуют возможность читать исходник по нескольку раз? gas так не делает.) На FreeBSD этот ключ раньше был по умолчанию (что у них сейчас после отказа от GCC — не знаю).

Исходно же это вызвано было тем, что на целевых платформах GCC первого поколения самому генерировать объектники было сильно чревато лицензионными проблемами (закрытые форматы, патенты и т.п.), но ассемблер был доступен всегда. Потом же не было смысла менять это решение — ассемблирование составляет ничтожную часть временны́х затрат.

Я бы не стал говорить, что Clang / Apache 2.0 "лучше" или "хуже" GCC / GPLv3.


Лицензии отталкиваются от разных философских принципов. Результат в мире компиляторов — такой, что я описал. К сожалению, GPL явно тормозит дальнейшее развитие GCC. "Принципы важнее"? © Ричард Столман — возможно, не берусь судить. Это уж точно вопрос политический.

По моему скромному мнению, развитие GCC в основном тормозит не лицензия, а более мутное внутреннее устройство (по историческим причинам) и, как следствие, высокий порог входа. Как пример, GPL как-то не особо мешает развитию Linux.


Тем не менее, лицензия Apache 2.0 действительно позволяет коммерческим компаниям гораздо лучше (понятнее и с меньшими рисками) защищать свои инвестиции при разработке собственных продуктов на основе clang. Фактически в upstream clang-а сейчас большинство инфраструктурных изменений попадает именно так — для выстраивания базиса закрытых разработок.

"Главная проблема GPL для компаний, выпускающих секретное "железо": если бинарии для этого железа генерируются с помощью GCC, то исходный код компилятора, в том числе и секретную часть, надо открыть"
Эээ?.. Первый раз про это слышу, уважаемый автор, поясните утверждение, пожалуйста

Если честно, я немного затрудняюсь с ответом… Что именно нужно пояснить?


Про особенности GPL можно почитать здесь.

Если я правильно понял утверждение, то при использовании gcc для компиляции
некоторой программы необходимо раскрывать исходный код компилируемой программы. Если это так, то это не следует из GPLv3. Или, во всяком случае, я не нашёл такого условия. Поэтому и прошу пояснить, откуда взялось такое утверждение
Если же предложение нужно понимать как "при доработке gcc под свои нужды, необходимо раскрыть исходный код получившегося компилятора" — то да, вопрос снят, тут yleo уже подробно всё расписал

Да, имелось в виду второе.


Сорри, если не совсем понятно выразился. :(

Добро, спасибо :)

Ну да, andreybokhanko несколько упросил и смешал разные обстоятельства и причины, приправив "секретным железом".


В случае Apple было примерно так:


  • Хотелось Objective-C компилятор cо специфическими фичами для Darwin.
  • Хотелось C компилятор с дополнительными фичами для Objective-C и Darwin.
  • При использовании GCC требовалось:
    1. Расплачиваться трудоемкостью за толщину культурного слоя и техдолг внутри GCC.
    2. Убеждать community принимать эти правки, в том числе вносить изменения в общие/базовые компоненты и "раскатывать" эти изменения по другим (ненужным для Apple) архитектурам CPU.
    3. "Засвечивать" все свои идеи и открывать реализацию под GPL.

Поэтому логично что "манагеры эпла" решили попробовать, когда появился подходящий PhD с работающим (пусть и академическим) проектом. А потом поперло...

Думаю, точные причины менеджеров Apple могут знать только они сами — да и то может забыли за давностью лет. :-)


То, что я написал (про "секретное железо") я лично слышал от людей из Apple на встрече по GCC в 2007 году. Так что я как чукча — что вижу, то и пою.

С "секретным железом" нет никаких проблем, пока вы не передаёте кому-либо условный GCC с вашими "правками для секретного железа". Но и в этом случае, в соответствии с GPL, вы обязаны показать исходники только тем, кому дали готовый компилятор и только по их требованию (которого может НЕ быть по массе причин).


Более того, передаваемые исходники должны обеспечивать возможность сборки компилятора, но вовсе НЕ обязаны содержать какую-либо документацию по "секретному железу" или как-либо его раскрывать.


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


Тем не менее, у Apple не было тогда и нет сейчас "секретного железа", которое бы требовал правок компилятора. Тогда планировались, а теперь есть софтверные механизмы защиты завязанные на фичи компилятора, но не "железо". Причем (насколько мне известно) всё необходимое давно есть и в GCC ;)

2. Убеждать community принимать эти правки, в том числе вносить изменения в общие/базовые компоненты и «раскатывать» эти изменения по другим (ненужным для Apple) архитектурам CPU.

Этот пункт необязателен: вполне могли сделать свой форк GCC.

Равно как и в LLVM community нужно делать всё перечисленное.


Конечно, если основатель community твой сотрудник, то делать это сильно проще.

Сейчас-то да, но когда в Apple принимали решение о женитьбе на clang ситуация была совсем иная.

Форкнуть-то можно, но затем:


  • выпиливать из форка всё лишнее, либо самим раскатывать изменения.
  • фиксировать интерфейс базовых/общих компонентов и иметь проблемы со своими доработками, либо иметь проблемы с импортом доработок и исправлений из upstream.
  • и т.п.

Короче, форк не требует убеждать community, в обмен на выполнение соответствующей работы своими руками (объём которой будет постоянно расти).

Спасибо!

Очень много фактических ошибок в статье.
То же самое касается поддержки языка C++. Ричард Смит, инженер компании Google, выступающий секретарём комитета ISO по стандартизации C++ — иными словами, тот человек, кто собственно пишет текст всех дополнений стандарта своими руками — является маинтейнером фронт-энда Clang. Многие другие заметные участники комитета также являются активными разработчиками Clang / LLVM. Сложите эти два факта, и вывод очевиден: поддержка новых дополнений в языке C++ раньше всего появляется именно в Clang'е.

Нет, из этого такой вывод нельзя сделать. Более того, на самом-то деле это не так, сейчас поддержка стандарта C++20 в GCC лучше, чем в Clang. См. gcc.gnu.org/projects/cxx-status.html#cxx2a и clang.llvm.org/cxx_status.html#cxx20 — можно даже чисто визуально по количеству красного понять, что в GCC поддержка лучше. Концепты в GCC уже почти реализованы, а в Clang до этого еще далеко.

Ещё одно важное преимущество Clang — и как мы знаем, главная причина перехода команды Android на LLVM — развитая поддержка статической верификации.

Вы видимо путаете статический анализ со статической верификацией. Верификатора в Clang нет. Есть проекты, использующие Clang и LLVM, которые такой функционал предоставляют. Clang Static Analyzer ( clang-analyzer.llvm.org ) -это не верификатор. Он ничего не доказывает. Clang и LLVM не являются верификаторами, но могут быть использованы как фронт-энд для верификаторов — см. seahorn.github.io
Clang разрабатывался как полностью совместимая замена GCC, так что в стандартном проекте для перехода достаточно просто поменять имя компилятора.

Нет, не полностью. Например, не поддерживаются nested functions из GCC — lists.llvm.org/pipermail/cfe-dev/2015-September/045035.html
Если с демонами не повезёт, вы точно заметите быструю скорость компиляции и линковки, улучшения в производительности, оптимальное использование новых инструкций ARM

Нет, это не точно. Может быть для ARM архитектур это действительно так (не проверял), но вот тесты phoronix для x86-64 показывают, что на некоторых тестах Clang быстрее, на некоторых — GCC, и явного перевеса ни у кого из них нет.
Для Google всегда были важны надёжность и безопасность программ — например, одно из правил компании требует обязательного добавления статической проверки в компилятор для каждой новой ошибки, обнаруженной в продакшене. Добавить подобного рода проверку в Clang гораздо проще.

А зачем это встраивать в компилятор? Ведь можно в сборочный скрипт добавить предварительный запуск специального статического анализатора(или встроить это в систему непрерывной интеграции, чтобы проверка стат. анализатором запускалась на каждом коммите), сам же компилятор можно вызывать с вообще полностью выключенными варнингами, это не его основная функция.
Ещё один аргумент в пользу Clang — поддержка Windows. GCC органически не подходит для Windows, и хотя есть версия GCC и для этой операционной системы, серьёзную разработку с её помощью вести нельзя. Некоторые вещи, например поддержку отладочной информации в формате PDB, в GCC в принципе невозможно добавить — всё из-за тех же лицензионных ограничений.

Вот с этого места поподробней. Каким образом отсутствие поддержки PDB в компиляторе GCC мешает серьезной разработке? У GCC есть поддержка DWARF, чем он не устраивает? Или под полноценной разработкой понимается совместимость с MSVC? Тогда и Clang для серьезной разработки не подходит, ведь там неполная поддержка SEH
clang.llvm.org/docs/MSVCCompatibility.html:
Asynchronous Exceptions (SEH): Partial. Structured exceptions (__try / __except / __finally) mostly work on x86 and x64. LLVM does not model asynchronous exceptions, so it is currently impossible to catch an asynchronous exception generated in the same frame as the catching __try.
А зачем это встраивать в компилятор? Ведь можно в сборочный скрипт добавить предварительный запуск специального статического анализатора

А зачем добавлять предварительный запуск специального статического анализатора, если можно всё нужное встроить в компилятор?
А зачем добавлять предварительный запуск специального статического анализатора, если можно всё нужное встроить в компилятор?

Тут сначала надо понять, что же является нужным. Если встраивать функционал Coverity или PVS-Studio, это приведет к очень долгой компиляции. Статические анализаторы смотрят на исходный код совершенно не так, как на него смотрят компиляторы, у компиляторов задача — перегнать побыстрее человекочитаемый исходный код в некое абстрактное представление, и дальше с ним делать какие-то манипуляции. У стат. анализатора задачи совсем другие, и это можно даже понять по тем ошибкам, которые та же PVS-Studio умеет диагностировать, более подробно о подходах можно почитать например там www.viva64.com/ru/b/0592, хотя наверняка есть и более серьезные научные статьи на эту тему. Для компиляции это всё лишнее, сильно ее замедлит, и эффективный стат. анализ может с компиляцией вообще никак не пересекаться, т.е. оперировать другим абстрактными представлениями, которые для целей компиляции не нужны.

Если встраивать что-то меньшее — ну ок, встраивать можно. Мне лично больше нравится юникс вей: Пишите программы, которые делают что-то одно и делают это хорошо.
Если встраивать функционал Coverity или PVS-Studio, это приведет к очень долгой компиляции.

«Clang со встроенным Coverity» заведомо отработает быстрее, чем связка «Clang отдельно и Coverity отдельно», хотя бы потому что парситься исходник будет только один раз.
При этом совсем необязательно по умолчанию включать дорогие анализы.
«Clang со встроенным Coverity» заведомо отработает быстрее, чем связка «Clang отдельно и Coverity отдельно», хотя бы потому что парситься исходник будет только один раз.

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

С другой стороны, можно применить подходы инкрементального статического анализа, когда где-то в IDE при изменении и сохранении конкретного файла с исходным кодом, «переанализирование» производилось бы только этого файла и тех файлов, которые его инклудят, «переанализированный» кусок абстрактного представления, соответствующий измененному файлу замещал бы то, что там было до этого, и производился бы стат. анализ без полного переанализирования, но при этом никакой перекомпиляции (даже инкрементальной) и запуск кучи юнит-тестов на каждую правку мы не хотим делать.

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

Конечно же проводит. Не такой тщательный, как специализированные инструменты, но всё равно очень продвинутый. Я недавно постил пример.
Конечно же проводит. Не такой тщательный, как специализированные инструменты, но всё равно очень продвинутый.

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

Вот например такой код, на который сейчас и GCC и Clang жалуются
warning: this 'for' clause does not guard… [-Wmisleading-indentation]
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  for (int i = 0; i < 10; ++i)
    printf("%d\n", i);
    printf("blablabla\n");
  return EXIT_SUCCESS;
}

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

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

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

Пример интересный, но для меня это не ново. Я бы это не называл стат. анализом, это скорее результат оптимизации на основе UB от разыменования нулевого указателя. И это зависит от опции -fdelete-null-pointer-checks
Я б лучше бы взял __builtin_unreachable():
godbolt.org/z/EEaP4j
#define ALWAYS_FALSE(a) if(a) __builtin_unreachable()
#define ALWAYS_TRUE(a) ALWAYS_FALSE(!(a))

int test_array(unsigned char a[10])
{
  for (int i = 1; i < 10; i++)
  {
    ALWAYS_TRUE(a[i-1] <= a[i]);
  }
  return a[0] <= a[2];
}


Тот цикл for() с ALWAYS_TRUE гарантирует компилятору, что массив сортирован, и данная функция соптимизируется в GCC (но не Clang) до return 1;
Кстати если сделать return a[0] <= a[3] в этом примере, оно уже так хорошо не соптимизирует, видимо есть какие-то ограничения на глубину такого анализа.
Чем вас не устраивает, что два этих варианта совмещены в одном бинарнике, и опции компиляции включают/отключают конкретные анализы?

Я ж вроде написал, специализированные стат. анализаторы лучше анализируют и находят больше ошибок, чем тот уровень стат. анализа, который по скорости работы приемлемо встраивать (и который де-факто встраивается) непосредственно в компилятор. И я б не сказал, что меня это очень сильно не устраивает чем-то, просто констатирую факт. Специализированный инструмент обычно лучше швейцарского ножа
Я бы это не называл стат. анализом, это скорее результат оптимизации на основе UB от разыменования нулевого указателя.

И то и другое: на основании UB удаляется косвенный вызов и проверки перед ним, на основании статанализа диапазон возможных значений переменной протаскивается по всей программе.

Я б лучше бы взял __builtin_unreachable()

Там спрашивали, как указать компилятору диапазон значений переменной при помощи стандартных средств Си.
Там спрашивали, как указать компилятору диапазон значений переменной при помощи стандартных средств Си.

На некоторых контроллерах можно (и нужно) разыменовывать нулевой указатель, и там этот трюк не сработает. Я б лучше попробовал другой UB под это дело приспособить
Нельзя, даже когда нужно: по стандарту Си, это UB независимо от платформы.
На SO пишут, что единственный корректный способ обратиться по нулевому адресу — это объявить в коде на Си внешний символ, и на этапе линковки разместить его по нулевому адресу.
Нельзя, даже когда нужно: по стандарту Си, это UB независимо от платформы.

Нельзя, но если очень хочется, то можно.
Посмотрим, что по этому поводу написано в документации GCC
gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
-fdelete-null-pointer-checks

Assume that programs cannot safely dereference null pointers, and that no code or data element resides at address zero. This option enables simple constant folding optimizations at all optimization levels. In addition, other optimization passes in GCC use this flag to control global dataflow analyses that eliminate useless checks for null pointers; these assume that a memory access to address zero always results in a trap, so that if a pointer is checked after it has already been dereferenced, it cannot be null.

Note however that in some environments this assumption is not true. Use -fno-delete-null-pointer-checks to disable this optimization for programs that depend on that behavior.

This option is enabled by default on most targets. On Nios II ELF, it defaults to off. On AVR, CR16, and MSP430, this option is completely disabled.

Passes that use the dataflow information are enabled independently at different optimization levels.


Могу раскрыть еще одну «страшную тайну» — использование dlsym() для получения указателей на функции это UB
pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html:
Note that conversion from a void * pointer to a function pointer as in:

fptr = (int (*)(int))dlsym(handle, "my_function");


is not defined by the ISO C standard. This standard requires this conversion to work correctly on conforming implementations.

Стандарт C99 явно запрещает преобразование void * в указатель на функцию www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf#%5B%7B%22num%22%3A116%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C-27%2C816%2Cnull%5D
8 A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.

stackoverflow.com/a/13697654
Теперь живите с этим :)
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Всё правильно написали. Кстати и в целом задачи у компиляторов и анализаторов разные. Компилятор должен работать как можно быстрее. Анализатор, так как запускается реже (другие сценарии использования), может (и вынужден) работать медленнее и потреблять больше памяти.
А где-то реализована «оптимизация на протяжении всей жизни приложения»? Как модуль ядра или как часть JRE?
Прежде всего, Apple хорошо известна как компания, стремящаяся контролировать весь технологический стек.
Записывает в словарик вежливых определений напротив «страдает острым NIH-синдромом».

lld (сверх-быстрый линковщик),

mold смотрит на lld с очень высокой горы

Зарегистрируйтесь на Хабре, чтобы оставить комментарий