Comments 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:
- В самом clang атрибут "pure" не документирован, но
на злодля совместимости с gcc поддерживается фронтендом и обрабатывается бэкендом, а также честно виден через__has_attribute(pure)
. - Встречая
[[gnu::pure]]
clang насильно включаетnoexcept(true)
, т.е. задним числом меняет тип функции (начиная с C++17). - Всё происходит молча, без каких-либо предупреждений, при включении любой диагностики, и даже при наличии у функции явного
noexcept(false)
.
Теперь представьте, что у вас проект с использованием десятка продвинутых C++ библиотек, в одной из которых есть трех-этажные шаблонны с абсолютно обоснованным использованием [[gnu::pure]]
в паре-тройке функций, т.е. с втихую добавленными noexcept(true)
.
Этот noexcept(true)
неплохо "протекает" через инлайнинг и IPO. При этом clang много чего оптимизирует/совмещает кадры стека и т.д. Соответственно, в каких-то случаях появление исключения приведет к вызову std::terminate()
, а в других нет.
При каких-нибудь жалких 10-20К строк кода отлаживать сплошное удовольствие. Я провозился несколько часов, пока не дошел до "поштучного" анализа unwind-таблиц. Что характерно, эта бага-фича гуглится элементарно, как только понимаешь связь между [[pure]]
и проблемами с исключениями. Но никак не раньше ;)
Если использовать clang, то программа напечатает «begin» дважды (!) и дальше упадёт в кору. Не спрашивайте. Хорошо хоть демоны не появились…
Проще всего внутри цикла сделать что-то полезное (а иначе зачем он вообще нужен?)
Если нужен пустой бесконечный цикл — действительно есть смысл прочитать хабрапост tyomitch.
а иначе зачем он вообще нужен?Например для того, чтобы ничего не делать. На некоторых процессорах нет инструкции «ничего не делать».
Можно просто сделать доступ к volatile переменной. Например, увеличить ее на 1.
И тогда CPU будет кушать примерно вдвое больше энергии.
Лучше __asm __volatite("")
.
P.S.
Интересна логика (и познания) "первого минуса" ;)
Чуть менее чем во всех мультизадачных ОС используется именно такой паттерн: idle-задача с минимальным приоритетом в виде бесконечного цикла с HLT-подобной инструкцией (остановка CPU до прерывания).
В чуть более продвинутых системах этот паттерн немного оптимизируется для исключения лишних переключений контекста — грубо говоря, HLT переноситься в планировщик и терминирует цикл при отсутствии активных задач.
Честно говоря я уже запутался в том, кто и что здесь пытался сформулировать и обсудить. В реальность уже лет 20-30 примерно так:
__asm __volatile("bla-bla-bla")
— чтобы компилятор не выбросил конструкцию и не задумывался о UB в бесконечных циклах.__asm __volatile("bla-bla-bla" ::: "memory")
— тоже самое, плюс уведомление компилятора об изменении любых переменных в памяти.__asm __volatile("HLT" ::: "memory")
— в idle-задаче или планировщике.__asm __volatile("PAUSE" ::: "memory")
— в циклах ожидания на блокировках.__asm __volatile("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 могут использоваться (замещаться при ассемблировании) заведомо безобидные и всегда доступные инструкции. Например, условный или безусловный переход к следующей команде.
Зачем нам в таком случае пустой цикл?Эээ, а какие есть альтернативы? Не пойму вашу мысль.
Main loop нам нужна для того, чтобы instruction pointer не ушёл за границы программы с неясными последствиями. Если работаем только на прерывания и у процессора есть HLT, то мы после инициализации ставим HLT, а для RET/RETI смещаем адрес возврата, чтобы вернуться на тот же HLT, а не после него. Если HLT или аналога нет, то тут только цикл, выполняющий NOP, пока не будет перехода по прерыванию.
С 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), а не что-то другое.
Извините, а разве это не баг clang’a?
На эту тему написаны тонны сообщений, но в общем-то не думаю.
И как быть без бесконечных циклов к примеру в embedded разработке, где обычно главная функция — именно бесконечный цикл, внутри которого выполняется вся работа устройства?
Речь о бесконечных циклах без внешних эффектов. Если внутри цикла выполняется работа устройства — у него есть побочный эффект, и там уже без UB.
А с каких пор openmp в clang стал лучше gccшного?
Думаю, правильней было бы привести те же аргументы, что я привёл в самом начале раздела «LLVM против GCC». Благодаря «либеральной» лицензии, интерес к LLVM версии OpenMP библиотеки (libomp) сейчас явно больше, чем к GNU версии (libgomp) — и её проще использовать в проектах с частично закрытым кодом, в том числе для поддержки акселераторов при гетерогенных вычислениях. Соответственно, в libomp больше инвестиций — со всеми вытекающими.
Помимо 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
Cейчас готовится очередной ISO стандарт для Ады — Ada 202x, сейчас действующий стандарт это Ada 2012. А Ada/SPARK недавно нвидия начала использовать для своего железоориентированного программирования. Но основной драйвер тут конечно SPARK (не путать с Apache Spark), ибо позволяет много чего доказать формально, что ценно когда надежность софта имеет значение.
Сейчас уже не модно, завтра снова будет модно — история циклична :-)
В военке и ракетостроении очень даже модный, особенно в западном.
Кроме всего прочего, это делается для того, чтобы заюзать например KLEE
KLEE разве не помер?
Вероятно вы хотели написать «Компилятор языка Rust также использует LLVM»
Емнип, современная версия Delphi при компиляции под мобильные платформы также использует LLVM.
Извините, но… Делфи ещё развивается? Вот это для меня новость. Надо вспомнить былое.
Одна из важных областей применения LLVM — это тензорные компиляторы
Можете раскрыть поподробнее эту часть? пары ссылок или примеров запросов в гугл было бы достаточно :)
Вот про TensorFlow (есть и видео — поищите на youtube)
mlir.llvm.org/docs/LangRef
"clang" произносится как "шланг" =)
На счет поддержки C++ не правда. Поддержка C++20 появляется в GCC не позже чем в Clang-е. И почему это libc++ — "наиболее полная реализация стандартной библиотеки C++", чем libstdc++ хуже (в libstdc++ к примеру уже есть Ranges)? Да, Richard Smith работает над Clang-ом, зато LWG chair Jonathan Wakely работает над GCC. В целом в комитете по стандартизации разработчики GCC и Clang-а представлены одинаково.
И да, в GCC тоже есть промежуточное представление программ — RTL (LISP подобные древовидные структуры) с момента его создания.
(Например, я был поражён, когда узнал, что GCC генерирует .o не напрямую, а создаёт временный файл с ассемблерным листингом, и пропускает его через gas.)
Есть флаг -S, который поддерживают абсолютно все компиляторы. И нет, он точно не "отсохнет". :-)
Что касается бинарного кода — не забывайте, что исполняемый файл создаёт не компилятор, а линкер. И он ещё как может поменять бинарий! — вплоть до замены инструкций (например, длинные переходы / вызовы на короткие).
Поэтому баг, внесённый в генератор ассемблерных листингов, в GCC проявится сразу же (при генерации любого бинарника), а в Clang может оставаться незамеченным.
Промежуточное представление LLVM спроектировано так, чтобы иметь три вида: бинарный, текстовый и в виде структур языка C++. Изоморфизм этих представлений гарантируется разработчиками.
Ассемблерный выхлоп GCC полезен только без оптимизации и если попросить выводить туда же текст исходных строк; с существенной оптимизацией его начинает корёжить так, что там уже мало понятно, откуда что пришло, и после этого читать foo.s или выдачу objdump -d foo.o — разница минимальна. (Точнее, есть в описаниях релокаций, но это редко становится предметом проблемы при оптимизации.)
Поэтому, поддерживая Вашу позицию в целом, вынужден категорически не согласиться в конкретном примере.
Исходно же это вызвано было тем, что на целевых платформах 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 требовалось:
- Расплачиваться трудоемкостью за толщину культурного слоя и техдолг внутри GCC.
- Убеждать community принимать эти правки, в том числе вносить изменения в общие/базовые компоненты и "раскатывать" эти изменения по другим (ненужным для Apple) архитектурам CPU.
- "Засвечивать" все свои идеи и открывать реализацию под GPL.
Поэтому логично что "манагеры эпла" решили попробовать, когда появился подходящий PhD с работающим (пусть и академическим) проектом. А потом поперло...
Думаю, точные причины менеджеров Apple могут знать только они сами — да и то может забыли за давностью лет. :-)
То, что я написал (про "секретное железо") я лично слышал от людей из Apple на встрече по GCC в 2007 году. Так что я как чукча — что вижу, то и пою.
С "секретным железом" нет никаких проблем, пока вы не передаёте кому-либо условный GCC с вашими "правками для секретного железа". Но и в этом случае, в соответствии с GPL, вы обязаны показать исходники только тем, кому дали готовый компилятор и только по их требованию (которого может НЕ быть по массе причин).
Более того, передаваемые исходники должны обеспечивать возможность сборки компилятора, но вовсе НЕ обязаны содержать какую-либо документацию по "секретному железу" или как-либо его раскрывать.
В реальности же, всё это крайне трудно формализировать до уровня служебных инструкций и соглашений по коммерческой тайне внутри компании. Практически невозможно контролировать и наказать сотрудника за слив информации (доказать что он умышленно раскрыл коммерческую тайну).
Тем не менее, у Apple не было тогда и нет сейчас "секретного железа", которое бы требовал правок компилятора. Тогда планировались, а теперь есть софтверные механизмы защиты завязанные на фичи компилятора, но не "железо". Причем (насколько мне известно) всё необходимое давно есть и в GCC ;)
2. Убеждать community принимать эти правки, в том числе вносить изменения в общие/базовые компоненты и «раскатывать» эти изменения по другим (ненужным для Apple) архитектурам CPU.
Этот пункт необязателен: вполне могли сделать свой форк GCC.
Равно как и в LLVM community нужно делать всё перечисленное.
Конечно, если основатель community твой сотрудник, то делать это сильно проще.
Форкнуть-то можно, но затем:
- выпиливать из форка всё лишнее, либо самим раскатывать изменения.
- фиксировать интерфейс базовых/общих компонентов и иметь проблемы со своими доработками, либо иметь проблемы с импортом доработок и исправлений из 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 независимо от платформы.
Нельзя, но если очень хочется, то можно.
Посмотрим, что по этому поводу написано в документации 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
Теперь живите с этим :)
Прежде всего, Apple хорошо известна как компания, стремящаяся контролировать весь технологический стек.Записывает в словарик вежливых определений напротив «страдает острым NIH-синдромом».
А где-то реализована «оптимизация на протяжении всей жизни приложения»?
В Андроиде: source.android.com/devices/tech/dalvik/jit-compiler
lld (сверх-быстрый линковщик),
mold смотрит на lld с очень высокой горы
Что такое LLVM и зачем он нужен?