-
Notifications
You must be signed in to change notification settings - Fork 100
Compiler support
Сейчас Desbordante можно собрать несколькими различными компиляторами на Linux и macOS. Чтобы так продолжалось и дальше, нужно соблюдать несколько правил, описанных в этом документе. Также здесь собраны рекомендации по поиску проблем, связанных с особенностями различных компиляторов.
В libstdc++ определено довольно много нестандартных расширений языка и STL. Понятно, что их нельзя использовать, если нужно, чтобы код компилировался не только GCC.
К расширениям языка относится, например, variable length array (который не рекомендуется использовать даже на GCC).
Расширения стандартной библиотеки легко отличить от стандартных функций и классов -- их имена начинаются с подчёркивания (одного или двух).
Например, SGI extensions для std::bitset.
Чтобы их можно было использовать, у нас есть своя копия std::bitset из libstdc++ -- в файле src/core/model/types/bitset.h.
Также с двух подчёркиваний начинаются feature-test macros.
Их использовать можно (и очень желательно использовать именно их, а не _GNUG_ и __clang__).
В libstdc++ используются inline namespaces внутри std для того, чтобы выделить детали реализации из основного namespace.
Их не нужно использовать даже на GCC:
The library uses a number of inline namespaces as implementation details that are not intended for users to refer to directly, these include
std::__detail,std::__cxx11andstd::_V2.
(из документации к libstdc++)
Известно, что зависимые имена типов должны использоваться с ключевым словом typename.
Все компиляторы выдают ошибку компиляции, если пропустить typename.
Но не все знают, что зависимые шаблонные имена должны использоваться с ключевым словом template, как в этом примере с cppreference:
template<typename T>
struct S
{
template<typename U>
void foo() {}
};
template<typename T>
void bar()
{
S<T> s;
s.foo<T>(); // error: < parsed as less than operator
s.template foo<T>(); // OK
}Даже при соблюдении предыдущих правил может получиться, что CI проходит на GCC под linux (run_tests(ubuntu-latest, gcc, ...)), но не проходит на другом компиляторе или ОС (например, run_tests(macos-latest, apple-clang, ...)).
Ниже приведены рекомендации, что делать в таком случае.
Скорее всего, ошибки компиляции будут выглядеть примерно так:
X is not member of namespace std, non-constexpr X cannot be used in a constant expression, и т. д.
Некоторые возможности реализованы ещё не во всех версиях стандартной библиотеки.
Скорее всего, возможность X относится к ним.
Найдите X в таблице "complier support" на cppreference ("C++20 library features" и "C++20 core language features") и посмотрите на столбцы GCC, Clang и Apple Clang (GCC libstdc++, Clang libc++ и Apple Clang в таблице library features).
Красные клеточки означают, что X ещё не реализована, жёлтые -- что X реализована частично.
Сюда же относятся зелёные клеточки, в которых указана слишком высокая версия компилятора (на данный момент у нас используется GCC 14, Clang 16 и Apple Clang 15).
В таком случае нужно найти способ не использовать X.
Скорее всего, вам пригодятся feature-test macros -- их можно найти внизу страницы X на cppreference и на странице feature testing.
У нас уже реализован JThread (файл util/auto_join_thread) -- замена std::jthread.
Jthread использует feature-test macro, чтобы определить, доступен ли std::jthread и, если недоступен, использует кастомную реализацию.
На GCC JThread просто будет zero-cost псевдонимом для std::jthread.
Если понадобится замена для X, то лучше сделать так же.
Если все клеточки напротив X зелёные, или в таблице нет X, то, скорее всего, вы делаете что-то не так, но, по воле случая, это работает на GCC.
Например, транзитивные include могут работать или не работать в зависимости от стандартной библиотеки:
#include <stdexcept>
// On GCC and LLVM Clang, <stdexcept> transitively includes <string>.
// But on Apple Clang, it won't compile until you uncomment the next line:
// #include <string>
class Exception: std::exception {
std::string what();
};Clang понимает, когда приватные поля не используются и выдаёт предупреждение Wunused-private-field (это правильное поведение).
Чтобы избежать этого, надо помечать такие поля как [[maybe_unused]].
Но GCC не умеет определять, когда приватные поля не используются, и на всякий случай выдаёт предупреждение Wattributes: "приватные поля и так не помечаются как unused, так что атрибут проигнорирован".
Поэтому такие поля нужно помечать как MAYBE_UNUSED_PRIVATE_FIELD из файла util/maybe_unused_private_field.h.
Здесь речь идёт про ошибки компоновки при сборке разными компиляторами локально.
Если Вы не меняли конфигурацию CI (файлы *.yml), а она падает с ld error, то, вероятно, ошибка не связана с различиями в компиляторах, либо это хитро замаскированная ошибка из предыдущего раздела.
Если Вы видите "невозможные" ошибки ld (например, не найден std::basic_string), скорее всего, дело в несовместимости ABI.
В нашем случае в первую очередь надо проверить CXXFLAGS и LDFLAGS -- они долны указывать на одну и ту же версию стандартной библиотеки, совместимую с вашей версией компилятора.
Также стандратная библиотека должна быть совместима с библиотекой C++ ABI.
Значения CXXFLAGS и LDFLAGS для различных компиляторов можно найти в README.md в разделе Dependencies.
Проверить текущие значения можно командой echo $CXXFLAGS (echo $LDFLAGS), очистить -- командой export -n CXXFLAGS (export -n LDFLAGS).
Если у Вас установлено несколько версий одного компилятора, обязательно указывайте полные пути (например, export CXXFLAGS="-stdlib=/usr/lib/llvm19/libc++").
Если "невозможные" ошибки указывают на отсутствие чего-либо в boost, то, почти наверняка, boost у Вас собран не тем компилятором.
Если тесты проходят на одной конфигурации, но не проходят на другой, то наиболее вероятно, вы где-то полагаетесь на implementation-defined behaviour (например, порядок эквивалентных элементов после std::sort) или какой-то специфический вид undefined behaviour, который пропускают UB санитайзеры (например, сужающие преобразования из float в int).
В любом случае, это поведение нужно найти (вам может помочь gdb и ubbook) и заменить на что-то определённое.
Санитайзеры Clang могут найти проблемы, которые пропустили санитайзеры GCC. Это совершенно нормально. Надо просто исправить проблемы.
UB sanitizer чаще всего довольно подробно сообщает, в чём проблема.
ADDRESS sanitizer может оказаться удобным запустить локально (см. инструкции по сборке Clang в README.md), возможно, с gdb.
Санитайзеры Clang почти никогда не выдают false positives:
If you see one [false positive], look again; most likely it is a true positive!
(из документации Clang Address Sanitizer)
Однако, такое всё же случается: например, ошибка в пакете для Ubuntu.
Если вы уверены, что перед вами false positive, можно использовать __attribute__((no_sanitize("address"))) (__attribute__((no_sanitize("undefined")))) или sanitizer ignore list (ADDRESS) (sanitizer ignore list (UB)).
Постарайтесь отключить как можно меньше проверок и убедитесь, что аналогичные проверки в санитайзерах GCC включены.
Все эти правила основаны на проблемах, которые я исправлял при портировании Desbordante на Clang для Linux и macOS. Вы можете найти реальные примеры в PR#507.
Если Вы не соблюдали какое-то из этих правил, но всё собралось и прошли тесты, то, возможно, соответствующую проблему исправили в новой версии компилятора (см. compiler support). Это очень хорошая новость -- обязательно сообщите об этом всем!
Если у Вас возникнут вопросы, спрашивайте в Технические вопросы или пишите мне в Telegram: @petua41.