Gentoo: от GCC/libstdc++ к LLVM/Clang + libc++. Linux статьи
Написать статью
Войдите, чтобы писать статьи

Gentoo: от GCC/libstdc++ к LLVM/Clang + libc++

2

Gentoo GCC LLVM

Материал написан пользователем сайта.

Предисловие

Официальная вики Gentoo пишет: «LLVM profiles are experimental. Most people do not want these.»

Эта статья — хроника моей реальной миграции на LLVM-тулчейн с заменой libstdc++ на libc++.

1. Что меняется и почему это может быть больно

libc++ вместо libstdc++

Ключевое изменение — ABI-несовместимость. libc++ оборачивает все свои символы в inline-namespace std::__1, libstdc++ этого не делает (у неё std::__cxx11 — лишь inline-namespace для части типов вроде std::string). Манглированные имена не совпадают, поэтому объектники и библиотеки, собранные с разными C++ stdlib, нельзя связывать друг с другом.

Следствие: недостаточно пересобрать LLVM. Нужен полный:

emerge -e @world

GCC после этого не может быть запасным C++ компилятором. C-код он всё ещё собирает, но C++ соберёт бинарники с libstdc++, которые не слинкуются с системными библиотеками на libc++.

default-lld

При использовании lld можно использовать дополнительные возможности линкера (--icf=all, -z pack-relative-relocs). Но при сборке GCC часть bootstrap-процесса игнорирует системный linker и вызывает GNU ld напрямую, поэтому эти флаги ломают сборку.

GCC остаётся в системе навсегда

Причин три:

  1. glibc не собирается clang на Gentoo — ebuild принудительно подставляет gcc через gcc-config. На практике это единственный такой пакет: при аудите моей системы из 768 установленных пакетов с CC=gcc собран только glibc.
  2. GCC — поставщик рантайм-библиотек: libstdc++.so.6 нужна бинарным пакетам (zen-bin, vesktop-bin и т.п.), libgcc_s.so.1 — всем Rust-бинарникам (об этом ниже).
  3. Полное удаление GCC невозможно без поломки системы — и не нужно: его наличие не мешает LLVM-тулчейну.

2. Подготовка

2.1. Предварительные условия — установка Clang на старом профиле

Это самый важный шаг, который нельзя пропустить. До смены профиля на LLVM у вас уже должны быть установлены и работать Clang, LLD и остальной LLVM-тулчейн.

Сейчас мы находимся на обычном профиле с GCC. Все установки выполняются до смены профиля.

Установить LLVM и Clang (собираются GCC):

emerge llvm-core/clang llvm-core/llvm llvm-core/lld \
  llvm-runtimes/compiler-rt llvm-runtimes/libunwind

Бутстрапнуть Clang — пересобрать его самим собой через package.env:

# /etc/portage/env/compiler-clang
COMMON_FLAGS="-march=native -O2 -pipe"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
CC="clang"
CXX="clang++"
LDFLAGS="-fuse-ld=lld -rtlib=compiler-rt -unwindlib=libunwind -Wl,--as-needed"

# /etc/portage/package.env
llvm-core/clang compiler-clang
llvm-core/llvm compiler-clang
llvm-core/lld compiler-clang
llvm-runtimes/compiler-rt compiler-clang
llvm-runtimes/libunwind compiler-clang
llvm-runtimes/libcxx compiler-clang
llvm-runtimes/libcxxabi compiler-clang
llvm-runtimes/compiler-rt-sanitizers compiler-clang

emerge llvm-core/clang llvm-core/llvm llvm-core/lld \
  llvm-runtimes/compiler-rt llvm-runtimes/libunwind \
  llvm-runtimes/libcxx llvm-runtimes/libcxxabi \
  llvm-runtimes/compiler-rt-sanitizers

Теперь Clang установлен и проверен в работе на старом профиле. Следующие шаги делаются уже после этого.

2.2. Смена профиля

LLVM профиль включает libc++, compiler-rt, lld и llvm-libunwind через профильные настройки USE. Вручную дублировать их обычно не требуется.

(default/linux/amd64/23.0/llvm)

eselect profile set 19

(default/linux/amd64/23.0/llvm/systemd)

eselect profile set 20
env-update && source /etc/profile

2.3. make.conf — замена тулчейна

В make.conf:

CC="clang"
CXX="clang++"
LD="ld.lld"

AR="llvm-ar"
NM="llvm-nm"
RANLIB="llvm-ranlib"
READELF="llvm-readelf"
STRIP="llvm-strip"
OBJCOPY="llvm-objcopy"
SIZE="llvm-size"
STRINGS="llvm-strings"
OBJDUMP="llvm-objdump"
ADDR2LINE="llvm-addr2line"

Вообще, LLVM-профиль уже задаёт всё это сам. Явный блок в make.conf — это осталось ещё до того, как я перешёл на LLVM профиль, так что, добавлять особой нужды нет, вреда от него нет.

LDFLAGS:

LDFLAGS="
    -fuse-ld=lld
    -rtlib=compiler-rt
    -unwindlib=libunwind
    -Wl,--as-needed
    -Wl,-O2
    -Wl,--hash-style=gnu
"

При желании можете добавить дополнительные оптимизационные флаги:

-Wl,-z,pack-relative-relocs
-Wl,--icf=all

2.4. Что НЕ трогать в make.conf

  • LLVM_TARGETS — Не изменяйте LLVM_TARGETS без необходимости. Ручное переопределение может привести к проблемам.
  • USE с default-* флагами — профиль сам установит.
  • -rtlib=compiler-rt -unwindlib=libunwind в LDFLAGS — профиль дублирует, но мешать не будут.

2.5. GCC с флагами lld

GCC при bootstrap лезет в /usr/x86_64-pc-linux-gnu/binutils-bin/.../ld (GNU ld), игнорируя системный lld. Флаги --icf= и -z pack-relative-relocs GNU ld не понимает.

Решение

cat > /etc/portage/env/gcc-clean-lf << 'EOF'
LDFLAGS="-Wl,--as-needed -Wl,-O2 -Wl,--hash-style=gnu"
EOF
echo 'sys-devel/gcc gcc-clean-lf' >> /etc/portage/package.env

Этот файл не удалять — он нужен будет при каждой пересборке GCC.

Опциональный вариант (у меня в системе именно он): собирать сам GCC компилятором clang — отдельный env-файл с CC="clang" CXX="clang++" и чистыми LDFLAGS. GCC 15 собирается clang'ом без проблем.

3. Пошаговая сборка

Фаза 1: LLVM toolchain без libc++

Сразу включить default-libcxx нельзя — cmake собран с libstdc++, упадёт с symbol lookup error.

Временно отключить default-libcxx

cat > /etc/portage/package.use/toolchain-bootstrap << 'EOF'
llvm-runtimes/clang-stdlib-config -default-libcxx
llvm-runtimes/clang-runtime -default-libcxx
llvm-core/clang-common -default-libcxx
EOF

Пересобрать тулчейн

emerge -1 --keep-going dev-build/cmake llvm-core/llvm llvm-core/clang \
  llvm-core/lld llvm-runtimes/compiler-rt llvm-runtimes/libcxx \
  llvm-runtimes/libcxxabi llvm-runtimes/libunwind sys-devel/gcc

Фаза 2: полный ребилд на libc++

rm /etc/portage/package.use/toolchain-bootstrap
emerge -e --keep-going @world

4. Верификация

clang --version
# clang version 22.1.8+libcxx → ключевое "+libcxx"

ld.lld --version
# LLD 22.1.8 (compatible with GNU linkers)

# любой пакет, написанный на C++:
readelf -d /usr/bin/btop | grep libc++
# libc++.so.1 — C++ пакеты пересобраны на libc++

Системный аудит — кто ещё ссылается на libstdc++ (ожидаем только /opt/* у бинарных пакетов и /usr/lib/gcc/* у самого GCC):

scanelf -qRn /usr /opt 2>/dev/null | grep -F 'libstdc++.so.6'

Кто реально был собран GCC (ожидаем только glibc):

for f in /var/db/pkg/*/*/environment.bz2; do
  bzcat "$f" 2>/dev/null | grep -qm1 '^declare -x CC="gcc"' && echo "$f"
done

Чем собрано ядро (если собирали его clang):

cat /proc/version
# Linux version ... (clang version 22.1.8+libcxx, LLD 22.1.8) ...

В моей системе итог аудита: из ~4400 ELF-файлов 595 слинкованы с libc++.so.1, а libstdc++.so.6 осталась только у бинарных пакетов из /opt и у собственных библиотек GCC. Ни один файл не линкует обе stdlib сразу.

5. Известные исключения

  1. glibc — собирается только GCC, так зашито в ebuild.
  2. Бинарные пакеты (www-client/zen-bin, net-im/vesktop-bin и подобные) — собраны апстримом, навсегда останутся на libstdc++. Лечится только сборкой из исходников.
  3. Rust-бинарники (fish, niri, yazi, librsvg...) — линкуют libgcc_s.so.1 by design: target-spec x86_64-unknown-linux-gnu жёстко добавляет -lgcc_s. Так выглядят даже официальные сборки Rust на любой системе.

Бонус: sanitizer-рантаймы на libc++

Даже после полной миграции shared-рантаймы санитайзеров (libclang_rt.{asan,hwasan,ubsan_standalone}-*.so) продолжают линковать libstdc++ — это дефолт апстрима (SANITIZER_CXX_ABI=default — на Linux подставляется libstdc++). Исправляется пользовательской переменной MYCMAKEARGS для cmake-ebuild'ов:

cat > /etc/portage/env/sanitizers-libcxx << 'EOF'
MYCMAKEARGS="-DSANITIZER_CXX_ABI=libc++"
EOF
echo 'llvm-runtimes/compiler-rt-sanitizers sanitizers-libcxx' >> /etc/portage/package.env
emerge -1v llvm-runtimes/compiler-rt-sanitizers

Проверка:

scanelf -n /usr/lib/clang/22/lib/linux/libclang_rt.asan-x86_64.so
# было:  libstdc++.so.6, libgcc_s.so.1, ...
# стало: libc++abi.so.1, libgcc_s.so.1, ...

Итог

Переход с GCC/libstdc++ на LLVM/Clang + libc++ — это не просто замена компилятора в make.conf, а полноценная смена C++ окружения системы с другим ABI, runtime и особенностями сопровождения.

В результате получается полностью LLVM-ориентированный toolchain:

  1. Компилятор: clang
  2. Линкер: lld
  3. C++ stdlib: libc++
  4. C++ ABI: libc++abi
  5. Рантайм: compiler-rt
  6. Unwinder: llvm-libunwind
  7. Binutils: llvm-ar/llvm-nm/llvm-objcopy/...

Но стоит понимать: переход на LLVM не является чудотворным способом ускорить систему. Тут не будет заметного прироста FPS, меньшего потребления памяти или ускорения каких-либо системных приложений.

Основные преимущества находятся в другом:

  • LLD значительно быстрее GNU ld.bfd, что особенно заметно при сборке больших проектов и частых пересборках Gentoo.
  • LLVM toolchain становится более цельным: все основные компоненты используют одну экосистему вместо смешивания GCC/binutils/libgcc.
  • Clang предоставляет удобный набор инструментов для разработки: хорошие диагностики, sanitizers, статический анализ и тесную интеграцию с LLVM-экосистемой.
  • В некоторых задачах Clang может давать отличающиеся результаты по размеру или производительности бинарников, хотя это сильно зависит от конкретного проекта.

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

Для большинства пользователей стандартный профиль с GCC/libstdc++ остаётся самым простым и предсказуемым вариантом.

Также стоит отметить, что использование Clang не требует перехода на libc++. Можно оставить стандартный libstdc++ и просто собрать LLVM/Clang toolchain поверх существующей системы. То есть можно остановиться на пункте 2.1, или следовать по инструкции с Gentoo Wiki, согласно которой LLVM используется как компилятор, но C++ ABI остаётся совместимым с привычным окружением.

Переход на libc++ — это уже отдельный шаг, который меняет ABI и значительно усложняет сопровождение системы. Поэтому для большинства пользователей разумным вариантом будет использовать Clang + LLD + compiler-rt, сохранив libstdc++, а полноценный переход на libc++ рассматривать как эксперимент или осознанный выбор.

Лайков: +2
войдите, чтобы ставить лайки
2
  • Опубликовано: 18.08.2026
  • Darllowin

Комментарии

Minor748
Активный пользователь
Активный
18.08.2026
13:50
Постоянная ссылка на комментарийПостоянная ссылка на комментарий
+1
войдите, чтобы ставить лайки
От себя могу добавить, что у меня все (или почти все) библиотеки LLVM/Clang собираться отказывались :( А учитывая, сколько каждая из них собирается (долго), это создавало проблему лишней и долговременной работы ЦП в пустую, без выхлопа.

> В некоторых задачах Clang может давать отличающиеся результаты по размеру или производительности бинарников, хотя это сильно зависит от конкретного проекта.
По ходу чтения у меня и возник вопрос — а зачем всё это нужно, зачем внутрянку копашить, ради чего? Для настоящих гентушников это может и не проблема, небольшая, но есть риск всё поломать, испортить, то есть усилия и время окажутся потраченными зря. Возвращаясь в начало моего комментария, у меня они "жили" по- соседству: GGC разных версий собран, он довольно быстро собираеется, а вот LLVM/Clang были бинарниками, потому что сборка локальная прерывалась (и 21, и 22).

Когда был на кальке, не всё собиралось через LLVM, например, браузер Librewolf собирается через него по умолчанию, но с ошибкой, меня ему use-флаг, успешно сборка проходила через gcc, то есть иногда это можно задать для пакета через флаги.
И в конце вопрос — а сразу, при установке выбрать профиль с LLVM выбрать?
Darllowin
Активный пользователь
Активный
18.08.2026
14:18
Постоянная ссылка на комментарийПостоянная ссылка на комментарийРодительский комментарийMinor748Родительский комментарий
0
войдите, чтобы ставить лайки
Можно и при установке выбрать профиль - тогда пересобирать придётся намного меньше пакетов. А ещё лучше при установке взять stage3 архив с llvm - тогда вообще не надо ничего пересобирать. Но конкретно в этой статье я рассказываю, как перейти на LLVM/Clang + libc++ в уже установленной системе с GCC.

Написать комментарий

Ник:
Текст комментария:
  • Уважать других.
  • Без оскорблений и грубости.
  • Не переходить на личности.
  • Писать на русском языке.
  • Без политики.
  • Без флуда.
  • Оффтоп запрещен.
  • Любой комментарий может быть удален без объяснения причин.
Правилаправила (наведите курсор)