Что такое LLVM и зачем он нужен?

    Всем привет! Думаю, у многих сразу возник другой вопрос — а зачем вообще нужна ещё одна статья про LLVM, ведь на хабре их и так больше сотни? Моей задачей было написать "введение в тему" for the rest of us — профессиональных разработчиков, не планирующих создавать компиляторы и совершенно не интересующихся особенностями устройства LLVM IR. Насколько я знаю, подобного ещё не было.


    Главное, что интересует практически всех — и о чём я планирую рассказать — вынесено в заголовок статьи. Зачем нужен LLVM, когда есть GCC и Visual C++? А если вы не программируете на C++, вам стоит беспокоиться? И вообще, LLVM это Clang? Или нет? И что эти четыре буквы на самом деле означают?


    Что в имени тебе моём?


    Начнём с последнего вопроса. Что скрывается за буквами L-L-V-M? Когда-то давно они были акронимом для "Low Level Virtual Machine", а в наше время означают… ровным счётом ничего.


    LLVM родился как исследовательский проект Криса Латнера (тогда ещё студента-магистра в Университете штата Иллинойс в Урбана-Шампейн) и Викрама Адве (тогда и по сию пору профессора в том же университете). Целью проекта было создание промежуточного представления (intermediate representation, IR) программ, позволяющего проводить "агрессивную оптимизацию в течение всего времени жизни приложения" — что-то вроде Java байт-кода, только круче. Основная идея — сделать представление, одинаково хорошо подходящее как для статической компиляции (когда компилятор получает на вход программу, написанную на языке высокого уровня, например C++, переводит её в LLVM IR, оптимизирует, и получает на выходе быстрый машинный код), так и динамической (когда runtime система получает на вход машинный код вместе с LLVM IR, сохранённым в объектном файле во время статической компиляции, оптимизирует его — с учётом собранного к этому времени динамического профиля — и получает на выходе ещё более быстрый машинный код, для которого можно продолжать собирать профиль, оптимизировать, и так до бесконечности).


    Интересно? Хотите узнать подробности? Диссертация Криса Латнера доступна онлайн.


    Вот картинка из неё, иллюстрирующая принципы работы "оптимизации в течение всего времени жизни приложения":



    Эта картинка, конечно же, про LLVM — но совершенно не про то, что под словом "LLVM" понимается сейчас. Больше того! — на сайте llvm.org написано, что LLVM это не о виртуальных машинах, и вовсе даже не акроним, а просто такое вот название проекта.


    Как так получилось? В 2005 году Крис защитил кандидатскую диссертацию и переехал из Иллинойса в Кремниевую долину...


    Apple, Google и все-все-все


    Примерно в то же время Apple начала задумываться о создании своего собственного компилятора Objective-C и решила сделать ставку на Криса Латнера и его (в то время по сути ещё академический) проект LLVM. Зачем Apple понадобился собственный компилятор? Прежде всего, Apple хорошо известна как компания, стремящаяся контролировать весь технологический стек. Кроме того, у Apple были большие проблемы с инструментами разработки для ранних Маков на основе PowerPC, когда выбранный партнёр (Symantec) не сумел выпустить хороший компилятор вовремя, что поставило под риск будущее всей компании.


    В 2005 году Apple уже достаточно долгое время инвестировала в разработку GCC, но как и многие другие, была сильно недовольна лицензией GPL. Особенно GPLv3, на которую перешёл GCC начиная с версии GCC 4.3. Недовольство Apple было так велико, что последней версией GCC, поставляемой в составе XCode, так и осталась GCC 4.2. Главная проблема GPL для компаний, выпускающих секретное "железо": если бинарии для этого железа генерируются с помощью GCC, то исходный код компилятора, в том числе и секретную часть, надо открыть — и ваше секретное железо теперь совсем не секретное! Проект LLVM всегда был под "либеральной" лицензией (вначале UIUC, потом Apache 2.0), которая не накладывает столь жёстких ограничений, и позволяет свободно комбинировать в одном продукте открытый и закрытый код.


    Apple был нужен обычный статический компилятор, а не система для "оптимизации в течение всего времени жизни приложения", и поэтому Крис Латтнер переделал LLVM в бэк-энд для GCC. "Бэк-эндом" называется та часть компилятора, что выполняет оптимизации и генерирует машинный код; есть ещё "фронт-энд" — принимающая на вход пользовательскую программу на языке высокого уровня и переводящая её в промежуточное представление, например LLVM IR. Комбинация GCC-фронт-энда и LLVM-бэк-энда работала неплохо, но не без проблем — соединение двух больших и независимых проектов, преследующих разные цели, всегда чревато появлением лишних ошибок и обрекает на вечную игру в "догонялки". Вот почему уже в 2006 году Крис начал работать над новым проектом, получившим название "Clang".


    Происхождение имени "clang" интуитивно понятно — это комбинация слов "C" и "language". "C" указывает на семейство C-образных языков, куда кроме самого C входят также C++ и Objective-C. Кстати говоря, "clang" произносится как "к-ланг", а не как "си-ланг"! — не повторяйте популярную ошибку!


    Комбинация фронт-энда Clang и бэк-энда LLVM называется Clang/LLVM или просто Clang. Сейчас это один из самых популярных (если не самый популярный!) компилятор C++ в мире.


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


    Внутренние команды Google всегда были вольны в праве выбора компилятора, и большая часть из них использовала GCC; кто-то применял коммерческие компиляторы от Intel и Microsoft. Постепенно практически весь Google всё-таки перешёл на Clang. Большими вехами стали появление полнокровной поддержки Windows (которую сделала сама Google, причём для компиляции всего-навсего двух программ: Chromium и Google Earth) и переход на LLVM в качестве основного компилятора операционной системы Android. Причину перехода лучше всего объяснили сами разработчики Android: "Пришло время перейти на единый компилятор для Android. Компилятор, способный помочь найти (и устранить) проблемы безопасности."


    После того, как переход сделала Google, рост популярности LLVM было уже не остановить. Всё больше и больше компаний и академических организаций начали делать ставку на LLVM: ARM, IBM, Sony, Samsung, NXP, Facebook, Argonne National Lab… это именно тот случай, когда "всех не перечесть". Некоторые компании продолжают поддерживать GCC, но в значительно большей степени инвестируют именно в LLVM — например, так поступают Intel и Qualcomm. Наплыв инвестиций создал "восходящую спираль роста" — когда новые улучшения LLVM привлекают новых пользователей, те инвестируют в дополнительные улучшения, те в свою очередь приводят ещё больше новых пользователей и инвестиций, и так далее.


    LLVM против GCC


    Возможно, к этому моменту вы уже начали задаваться вопросом: "ну хорошо, причины, побудившие к переходу Apple и Google, понятны… но мне-то что за дело? Почему лично я должен переходить на LLVM? Чем GCC плох?"


    Ответ — абсолютно ничем! GCC продолжает быть отличным компилятором, в который вложены многие годы труда. Да, его сложно расширять, и да, над его развитием работает не так много людей, и да, лицензия GPL серьёзно сужает круг проектов, которые можно сделать на основе кодовой базы GCC — но всё это проблемы разработчиков GCC, а не ваши, ведь так?


    К сожалению, проблемы развития проекта GCC в итоге замечают и конечные пользователи — ведь постепенно GCC начинает отставать от LLVM. Все основные "игроки" мира ARM (Google, Samsung, Qualcomm и собственно компания ARM) сфокусированы на развитии прежде всего LLVM — а значит, поддержка новых процессоров на основе архитектуры ARM появляется в LLVM раньше и включает больше оптимизаций и более тщательный "тюнинг" производительности, чем в GCC.


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


    Ещё одно важное преимущество Clang — и как мы знаем, главная причина перехода команды Android на LLVM — развитая поддержка статической верификации. Говоря простыми словами, Clang находит больше warning'ов, чем GCC, и делает это лучше. Кроме того, есть специальный написанный на основе Clang'а инструмент, под названием Clang Static Analyzer, включающий в себя богатую коллекцию сложных проверок, анализирующих больше чем одну строку кода, и выявляющих проблемы использования языка С++, работы с нулевыми указателями и нарушения безопасности.


    В проект LLVM входит много разных инструментов, являющихся лидерами в своей области: коллекция динамических верификаторов под названием "санитайзеры", рантайм-библиотека OpenMP (лучшая на рынке), lld (сверх-быстрый линковщик), libc++ (наиболее полная реализация стандартной библиотеки C++). Все они могут использоваться независимо от LLVM — в том числе и с GCC тоже, как минимум в теории. На практике же у каждого компилятора есть множество небольших отличий и особенностей, и потому все эти инструменты лучше всего работают именно с LLVM — ведь они разрабатываются, тестируются и выпускаются все вместе.


    Clang разрабатывался как полностью совместимая замена GCC, так что в стандартном проекте для перехода достаточно просто поменять имя компилятора. На практике возможны сюрпризы — от новых ошибок на этапе компиляции до неожиданных падений при тестировании. Обычно это означает, что в проекте есть код, полагающийся на аспекты стандартов C и C++ с "неопределённым поведением" — которое может трактоваться компиляторами GCC и Clang по-разному.


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


        #include <stdio.h>
    
        static void die() {
          while(1)
            ;
        }
    
        int main() {
          printf("begin\n");
          die();
          printf("unreachable\n");
        }

    Попробуйте откомпилировать её с помощью "gcc -O2" и "clang -O2" — результат может вас удивить. Причина в "неопределённом поведении" для бесконечных циклов в стандарте языка C (есть лишний час в запасе? — можете узнать подробности). Раз поведение "неопределено", компилятор волен делать с бесконечными циклами вообще всё, что угодно — даже "выпускать из ноздрей летающих демонов"! (это выражение стало мемом в сообществе C разработчиков). Как можно убедиться, Clang и GCC просто поступают по разному. Конечно же, основывать логику программы на неопределённом поведении не лучшая практика (кто захочет испускать демонов из носа?) и такой код должен исправляться, как и любая другая ошибка.


    Я рекомендую попробовать заменить "gcc" на "clang" (или "g++" на "clang++" в случае C++) в каком-то из ваших проектов. Кто знает? — может вам удастся увидеть летающих демонов?


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


    "Зонтик" LLVM


    Проект LLVM вырос за рамки компилятора C++. Один из важных принципов разработки LLVM — написание кода как набора переиспользуемых библиотек, которые можно соединять разным образом и для разных целей — привёл к появлению множества интересных новых инструментов. Часть из них была добавлена к проекту, так что LLVM со временем превратился в "зонтик" для нескольких совершенно разных подпроектов. Ещё больше инструментов развиваются за пределами проекта LLVM, но полагаются на библиотеки LLVM для анализа и оптимизации программ, а также генерации кода.


    Я уже упоминал Clang Static Analyzer, санитайзеры, OpenMP, libc++ и lld — это инструменты, более всего интересные C++ разработчикам. Компилятор языка Rust также основан на LLVM — решение, позволившее Rust задействовать всю мощь LLVM оптимизатора для генерации быстрого кода для множества аппаратных платформ с самого начала существования языка! Помимо C++ и Rust, LLVM используется в компиляторах (как статических, так и динамических) для таких разных языков как D, Fortran, Haskell, Julia, Kotlin, Lua, PHP, Python. Создателю нового языка достаточно написать фронт-энд, переводящий программы в формат LLVM IR, чтобы воспользоваться оптимизатором и генератором кода мирового уровня! Лёгкость использования LLVM в этом качестве дала толчок появлению новых разработок в области языков программирования.


    Одна из важных областей применения LLVM — это тензорные компиляторы, многократно ускоряющие задачи машинного обучения. Два из самых популярных ML фреймворков — TensorFlow компании Google и PyTorch от Facebook — полагаются именно на LLVM для генерации быстрого кода.


    Сейчас LLVM стал настолько популярен, что он встречается практически повсюду. Если вы запускаете приложение или ML модель (на телефоне, десктопе или серверной ферме), то почти наверняка ваше приложение или модель каким-то образом прошло через один из инструментов, использующих LLVM.


    Всё это сделало LLVM критически важным проектом для основных игроков в индустрии — ведь каждый новый патч в "core" библиотеки LLVM влияет на сотни инструментов! Список спонсоров LLVM Developers' Meeting читается как справочник "кто есть кто" мира IT. Компания, в которой я работаю, Huawei, также является спонсором — ведь как любая большая и разносторонняя организация, мы используем LLVM в большом числе своих продуктов.


    Надеюсь, эта небольшая статья справилась со своей задачей, и вы стали лучше понимать что такое LLVM и почему этот проект так важен. Возможно даже решили попробовать один из LLVM-инструментов в своём проекте?


    Я был и остаюсь вовлечён в разработку LLVM в течение многих лет (сначала в Intel, затем в NXP и теперь в Huawei), так что этот проект очень близок моему сердцу. Раскрою карты! — у статьи была и вторая, секретная цель: заразить энтузиазмом и верой в LLVM всех читателей. Что скажете? — удалось? :)

    Huawei
    Компания

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

      +17

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

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

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

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

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


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

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

          В контексте удобно рассказать о знатной бага-фиче с [[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]] и проблемами с исключениями. Но никак не раньше ;)

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

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

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


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

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

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

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

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

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

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

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

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

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

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


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


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

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

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


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

                                          0
                                          Но там же не пустой цикл с volatile (или обычным с -О0) счётчиком?
                                            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") — для заполнения при выравнивании и в циклах задержки.
                                              0
                                              Я спрашивал про NOP в ответ на это обсуждение:
                                              Проще всего внутри цикла сделать что-то полезное (а иначе зачем он вообще нужен?)
                                              Например для того, чтобы ничего не делать. На некоторых процессорах нет инструкции «ничего не делать».
                                                0

                                                Совсем пустой бесконечный цикл не нужен, поэтому и причислен к 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 могут использоваться (замещаться при ассемблировании) заведомо безобидные и всегда доступные инструкции. Например, условный или безусловный переход к следующей команде.
                                                  0
                                                  Типичный сценарий: что-то делать в обработчиках прерываний, ничего не делать в основном потоке. Инструкция NOP вам тут мало чем поможет.
                                                    0
                                                    Зачем нам в таком случае пустой цикл? В main loop тогда может быть перевыставление watchdog и уход в сон. Но если у нас нет инструкции ухода в сон, то тогда процессор будет в цикле молотить, явно выполняя переходя между итерациями цикла. Или это и есть ожидаемое поведение?
                                                      0
                                                      Зачем нам в таком случае пустой цикл?
                                                      Эээ, а какие есть альтернативы? Не пойму вашу мысль.
                                                        0
                                                        Давайте разверну свою мысль, а то мы кругами ходим, хотя я заранее признаю, что текст ниже попахивает извращениями и с вечным циклом проще.
                                                        Main loop нам нужна для того, чтобы instruction pointer не ушёл за границы программы с неясными последствиями. Если работаем только на прерывания и у процессора есть HLT, то мы после инициализации ставим HLT, а для RET/RETI смещаем адрес возврата, чтобы вернуться на тот же HLT, а не после него. Если HLT или аналога нет, то тут только цикл, выполняющий NOP, пока не будет перехода по прерыванию.
                                                          0
                                                          В цикле NOP не нужен, так как не несёт никакого смысла, достаточно просто написать jmp $, и вечный цикл готов.
                                                  0
                                                  > __asm __volatile(«bla-bla-bla» ::: «memory»)

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

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


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

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

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

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

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

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

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

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

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

                                  Думаю, правильней было бы привести те же аргументы, что я привёл в самом начале раздела «LLVM против GCC». Благодаря «либеральной» лицензии, интерес к LLVM версии OpenMP библиотеки (libomp) сейчас явно больше, чем к GNU версии (libgomp) — и её проще использовать в проектах с частично закрытым кодом, в том числе для поддержки акселераторов при гетерогенных вычислениях. Соответственно, в libomp больше инвестиций — со всеми вытекающими.
                                  +3
                                  Спасибо за простое изложение, переодически возникал вопрос популярности llvm в последнее время, особенно при чтении статей про rust и haskell, теперь все стало понятно.
                                  +5
                                  Помимо 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
                                    +1
                                    valexey, спасибо за дополнение! Хотя, конечно, Ada в 2020 не модный и не хипстерский. :)
                                      0
                                      Ага :-)

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

                                      Сейчас уже не модно, завтра снова будет модно — история циклична :-)
                                        0

                                        Судя по краткому описанию (и использованию солверов для проверки VC) доказать оно может только узкий и разрешимый с точки зрения proof finding набор фактов.


                                        Короче, непонятно, зачем это нужно, когда есть всякие коки и идрисы.

                                          0
                                          Ада генерирует довольно нетребовательный к ресурсам код, а что с идрисом — не в курсе.
                                            0

                                            А он уже есть, а не «готовится стандарт».

                                              0
                                              Свежий действующий стандарт для Ада от 2012го, так что она тоже есть.
                                        +4

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

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

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

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

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

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

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

                                              +1

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

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

                                                Можете раскрыть поподробнее эту часть? пары ссылок или примеров запросов в гугл было бы достаточно :)
                                                0
                                                Стоит упомянуть, что промежуточное код-представление LLVM (IR) не всегда такое низкоуровневое.

                                                mlir.llvm.org/docs/LangRef
                                                  0

                                                  "clang" произносится как "шланг" =)

                                                    –2
                                                    You made my day! :)
                                                    +3

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

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

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

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


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

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

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

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


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

                                                                    +1

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


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

                                                                  0

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

                                                                    0

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


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

                                                                      0

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

                                                                        +1

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


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

                                                                          +1
                                                                          Добро, спасибо :)
                                                                      0

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


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


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

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

                                                                        0

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


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

                                                                          +2

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


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


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


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

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

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

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


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

                                                                              0

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

                                                                              +1

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


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

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

                                                                              0

                                                                              Спасибо!

                                                                            +5
                                                                            Очень много фактических ошибок в статье.
                                                                            То же самое касается поддержки языка 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, и явного перевеса ни у кого из них нет.
                                                                              –2
                                                                              Для 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.
                                                                                +2
                                                                                А зачем это встраивать в компилятор? Ведь можно в сборочный скрипт добавить предварительный запуск специального статического анализатора

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

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

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

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

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

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

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

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

                                                                                          Да, проводит. Я говорил о гипотетическом сценарии, если сделать два варианта 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 без фигурных скобок совершенно не нужен для компиляции, и с т.з. компиляции это лишние телодвижения, хотя и не очень затратные.
                                                                                            0
                                                                                            Я привёл пример, когда статанализ используется для оптимизаций при компиляции, а не только для выдачи диагностики.

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

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

                                                                                              Пример интересный, но для меня это не ново. Я бы это не называл стат. анализом, это скорее результат оптимизации на основе 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] в этом примере, оно уже так хорошо не соптимизирует, видимо есть какие-то ограничения на глубину такого анализа.
                                                                                              Чем вас не устраивает, что два этих варианта совмещены в одном бинарнике, и опции компиляции включают/отключают конкретные анализы?

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

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

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

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

                                                                                                  На некоторых контроллерах можно (и нужно) разыменовывать нулевой указатель, и там этот трюк не сработает. Я б лучше попробовал другой UB под это дело приспособить
                                                                                                    0
                                                                                                    Нельзя, даже когда нужно: по стандарту Си, это UB независимо от платформы.
                                                                                                    На SO пишут, что единственный корректный способ обратиться по нулевому адресу — это объявить в коде на Си внешний символ, и на этапе линковки разместить его по нулевому адресу.
                                                                                                      +1
                                                                                                      Нельзя, даже когда нужно: по стандарту Си, это 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
                                                                                                      Теперь живите с этим :)
                                                                                                      0
                                                                                                      Я б лучше попробовал другой UB под это дело приспособить

                                                                                                      MAX_INT + 1?

                                                                                                        0
                                                                                                        Нет, не подходит godbolt.org/z/cc9479

                                                                                                        Можно перебирать разные UB, может какое-то еще подойдет
                                                                                                          0

                                                                                                          Ну, если серьёзно, тут либо компилятороспецифичные интринсики, либо ничего.

                                                                                            0
                                                                                            Всё правильно написали. Кстати и в целом задачи у компиляторов и анализаторов разные. Компилятор должен работать как можно быстрее. Анализатор, так как запускается реже (другие сценарии использования), может (и вынужден) работать медленнее и потреблять больше памяти.
                                                                                  0
                                                                                  А где-то реализована «оптимизация на протяжении всей жизни приложения»? Как модуль ядра или как часть JRE?
                                                                                  Прежде всего, Apple хорошо известна как компания, стремящаяся контролировать весь технологический стек.
                                                                                  Записывает в словарик вежливых определений напротив «страдает острым NIH-синдромом».

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

                                                                                Самое читаемое