Эльбрус/lcc

Материал из ALT Linux Wiki
< Эльбрус
Версия от 00:06, 25 января 2021; MichaelShigorin (обсуждение | вклад) (→‎ссылки: уточнение по Compiler Explorer ("поддержка C в зачаточном состоянии, не делал"))

lcc на e2k

Сразу оговорюсь: речь именно о родном режиме работы lcc, кроссовым (собирать для e2k, сидя на x86) мы не пользуемся.

Основная часть проблем, возникающих при сборке рассчитанного на gcc программного обеспечения сводится к тому, что lcc -- это всё же не gcc, несмотря на выставленный __GNUC__[1]; в патчах можно проверять взведённый/заполненный __LCC__[2], хотя порой даже проще прикинуться __ICC или __clang__, у которых во многом схожие ограничения -- начиная с того, что они тоже не gcc.

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

В то же время компилятор предоставляет богатые возможности оптимизации под весьма отзывчивую на них VLIW-платформу, причём от ветки к ветке производительность одного и того же исходного кода на одной и той же аппаратуре в среднем растёт; обратите также внимание на библиотеку EML.

проблемы

фронтэнд

Надо понимать, что МЦСТ применяет в lcc сторонний фронтэнд разработки Edison Design Group (EDG), как несколько раньше делал и Intel в своём icc.

Собственно, в основном проблемы здесь -- и с новыми версиями стандартов вроде C++20 (стабилизируется lcc 1.25[3]), и с отсутствием поддержки как некоторых расширений GNU (в первую очередь вложенных функций -- nested functions, и массивов переменной длины в структуре -- variable length array in structure, VLAIS), так и ряда языков -- Objective C, D, Ada, Go -- либо конкретных опций, специфичных для gcc или других компиляторов.

Стоит отметить, что часть "проблем" на самом деле относится именно к собираемому софту и находится в нём -- просто gcc или смотрит сквозь пальцы, ограничиваясь предупреждениями, или не делает даже их, что позволяет фактическим ошибкам оставаться в коде даже с -Werror.

бэкенд

С ним бывают проблемы в основном двух типов: неверная оптимизация или сбой самого оптимизатора.

misoptimization

Обычно замечается по странным сбоям в работе программы (особенно Illegal instruction, оно же SIGILL); диагностируется по корректности работы собранного с -O0 и/или -g0 кода; исправляется в компиляторе или обходится в коде.

segfault

работа /opt/mcst/lcc-home/1.23.12/e2k-v3-linux/bin/ecf_opt64 завершена по сигналу Segmentation fault (11)

При падении компилятора остаётся только вешать отчёт об ошибке в lcc.

линкер

Между сборками binutils в ОС Эльбрус[4] и ОС Альт есть существенная разница в поведении компоновщика по умолчанию (#3675): в альтовой из соображений безопасности не задана опция -Wl,--no-warn-shared-textrel, которая отключает предупреждения о создании релокаций и совместно с опцией -Wl,--fatal-warnings может привести к сбоям сборки вида:

/usr/bin/ld: CMakeFiles/KF5CoreAddons.dir/plugin/desktopfileparser.cpp.o: warning: relocation against `_ZTISt9bad_alloc' in readonly section `.gcc_except_table'.
/usr/bin/ld: warning: creating a DT_TEXTREL in a shared object.

либо

/usr/bin/ld: CMakeFiles/openbabel.dir/mcdlutil.cpp.o: предупреждение: перемещение указывает на «_ZTISt9exception» из раздела только для чтения «.gcc_except_table».
/usr/bin/ld: предупреждение: создаётся DT_TEXTREL в общем объекте.

Проверив, что компилятору (в момент получения объектного файла) передана опция -fPIC, в качестве обходной меры можно передать -Wl,--no-warn-shared-textrel явно: в некоторых версиях компилятора известна нефатальная ошибка, которая может приводить к подобным сбоям.

OpenMP

В lcc <= 1.23 доступна реализация OpenMP 2.5, начиная с lcc-1.24 реализована OpenMP 3.1 с некоторыми ограничениями (см. /opt/mcst/doc/openmp.html). Кроме того, в старших версиях компилятора есть исправление ряда ошибок и расширение функциональности, поэтому рекомендуется использовать их.

Для собираемости могут потребоваться положенное -fopenmp вместо явного -lgomp (mcst#2483) и хак в виде подстановки переменных, содержащих значение выражения, вместо выражения:

-#pragma omp parallel sections if (a > b)
+  int c = a > b;
+#pragma omp parallel sections if (c)

Наткнувшись на ошибку "omp-регион не является замкнутым", запрашивайте обновление компилятора до 1.23.20 или выше (mcst#3639).

howto

Маленький сборник проверенных на пакетах для e2k-alt-linux рецептов.

UTF-8 BOM

Проблема (#2418): строгий фронтэнд с негодованием спотыкается на трёхбайтном маркере в начале файла, указывающем, что используется кодировка UTF-8 (обычно оставлен текстовым редактором); изменение этого поведения ожидается в версии 1.24[5], а до того может понадобиться:

%ifarch %e2k
# strip UTF-8 BOM for lcc < 1.24
find -type f -print0 -name '*.cpp' -o -name '*.hpp' -o -name '*.cc' -o -name '*.h' |
      xargs -r0 sed -ri 's,^\xEF\xBB\xBF,,'
%endif

Такие пакеты при обходе проблемы в альте обычно получают подобную запись в %changelog:

- E2K: strip UTF-8 BOM for lcc < 1.24

-std=c++11

Ошибки могут быть довольно разнообразными; скажем,

"nullptr" не определен

По умолчанию в lcc 1.23 идёт -std=c++03, как и в gcc 5.5; если код подразумевает более новый стандарт без учёта этого в системе сборки -- включаем явно:

%ifarch %e2k
# -std=c++03 by default as of lcc 1.23.20
%add_optflags -std=c++11
%endif
- E2K: explicit -std=c++11

В lcc 1.24 по умолчанию -std=c++11.

-O

Некоторые пакеты указывают уровень оптимизации сверх специфицированных (-O6, -O9, -O20): gcc такое допускает, хотя реально ставит -O3, а EDG -- нет (#2266); понижаем до заданного:

%ifarch %e2k
sed -i 's/-O6/-O%_optlevel/g' configure*
%endif
- E2K: fix superfluous optimization level

Аналогично в случаях, когда гвоздиком прибит -O2, а нам с lcc надо выше.

- E2K: fix hardwired optimization level

optimize("O0")

Проблема (#4061): lcc до версии 1.24.03 воспринимает другой вариант синтаксиса таких атрибутов -- численный[6]; обход:

%ifarch %e2k
# lcc before 1.24.03 can't do that (mcst#4061)
find -type f -print0 -name '*.c' |
       xargs -r0 sed -i 's,optimize("-O3"),optimize(3),g'
%endif

В случаях вроде

__attribute__((optimize("-fno-fast-math")))

придётся переносить опцию на уровень файла или проекта.

символьные константы

Проблема (#3940): по умолчанию символьные константы в UTF-8 не будут разобраны фронтэндом:

lcc: "static_unicode_sets.h", строка 111: ошибка: слишком
          много символов в символьной
          константе
      {RUPEE_SIGN, u'₨'},
                   ^

Добавим опцию -finput-charset=utf8:

%ifarch %e2k
# lcc 1.23.12 doesn't grok u'’' by default
%add_optflags -finput-charset=utf8
%endif
- E2K: expect UTF-8 input

__builtin

В lcc 1.23 не поддерживается ряд типично ожидаемых от заявленного gcc5 builtin'ов[7], в т.ч.: __builtin_mul_overflow_p, __builtin_constant_p, __builtin_uadd_overflow, __builtin_sub_overflow, __builtin_add_overflow.

Смысл патча обычно заключается в добавлении проверки на lcc <= 1.23 -- например, для включающих gnulib проектов:

-#if 5 <= __GNUC__ && !defined __ICC
+#if 5 <= __GNUC__ && !defined __ICC && !(defined __LCC__ && __LCC__ <= 123)
...
-#elif 5 <= __GNUC__ && !defined __ICC && !__STRICT_ANSI__
+#elif 5 <= __GNUC__ && !defined __ICC && \
+         !(defined __LCC__ && __LCC__ <= 123) && !__STRICT_ANSI__

Также не поддерживается vector_shuffle (#3982 о libfreetype >= 2.9) -- пока неясно, будет ли реализация в lcc.

int128

int128_t/uint128_t поддержаны начиная с lcc 1.24 (#1802); для 1.23 и более ранних веток применяем аналогичные вышеизложенным для __builtin_* обходы либо прикидываемся 32-битной платформой с максимум 64-битными целыми, смотря по ситуации (такие в сизифе оказались довольно редки, апстрим libtommath патчик уже принял).

FP*/Decimal*

См. обсуждение на форуме:

Аппаратно поддержаны FP32 (float), FP64 (double), FP80 (long double, __float80). У FP128 (__float128) поддержка программная. Всё симметрично во всех моделях процессоров.

Для FP256 и Decimal'ов (Decimal64, Decimal128...) и вообще для какого угодно формата можно было бы сделать программную поддержку, но проблема упирается в то, что покупной фронтенд edg, на базе которого построен компилятор lcc, данные типы не поддерживает.

bugreport

Пишем на user@mcst.ru заявку на регистрацию в системе отслеживания ошибок (с рабочего адреса и указав серийный номер используемого "Эльбруса" либо сообщив о применении удалённого доступа).

Если проблема с неподдерживаемой опцией -- следует вешать одно сообщение об ошибке на одну опцию; максимум одно сообщение с перечислением нескольких связанных между собой опций в течение одного дня (это связано с порядком обработки багрепортов компиляторщиками и экономит им силы на синхронизации обстановки с внутренним багтрекером).

Если произошёл сбой компиляции, к отчёту об ошибке стоит приложить препроцессированный исходник (-E) и строчку запуска -- например,

g++ -Wall -O2  -DNDEBUG -std=c++11 -c -I ./include/   ./core/xhtmlgenerator.cpp

...превращается в:

$ g++ -Wall -O2 -DNDEBUG -std=c++11 -c -I ./include/   ./core/xhtmlgenerator.cpp -E -o test.pp.cpp

Если в исходной строке запуска была указана опция -o с именем бинарного объекта, её стоит удалить.

особенности

libcxa

lcc до 1.23 требовал явной линковки libcxa к C++-программам, иначе можно было получить один из характерных симптомов (#1811):

undefined reference to `__cxa_vec_ctor'

либо в случае подключаемых модулей, так или иначе слинкованных с C++-кодом (особенность ветки 1.21):

cannot allocate memory in static TLS block

Рекомендуемый разработчиками компилятора обход обеих проблем при невозможности перехода на lcc >= 1.23 -- принудительная линковка такой программы с -lcxa; в случае плагинов линковать требуется то, к чему они линкуются, причём "до упора" (т.е. если имеем C++-плагин к mod_php к apache, то линковать так придётся именно apache). Подчас оказывалось достаточно export LIBS=-lcxa перед запуском autoreconf и configure.

В целом же лучше перейти на 1.23+, где помимо доработок по части zero cost exceptions и поддержки стандартов внедрено множество иных улучшений и оптимизаций. Обратите внимание, что добавленные -lcxa в этом случае обязательно убрать.

Обратите внимание, что для некоторых случаев C++-кода, который написан так, чтобы линковаться gcc вместо g++[8], на 1.23 может потребоваться другой обход -- компоновка с -lsupc++ -lgcc_eh -llcc из libstdc++5-devel-static.

оптимизация

По умолчанию lcc собирает без оптимизации (-O0), что удобно для отладки. Для релизов и предназначенных для использования версий настоятельно рекомендуется -O3; при этом прыгать на четвёртый уровень бездумно не стоит, т.к. там выключен gos-solver, что может привести к сильному раздуванию кода и обратному эффекту -- понижению производительности полученного бинарника; посмотрите сперва внимательней на -fwhole.

При отладке стоит понижать уровень оптимизации до -O0 и порой отключать генерацию отладочной информации (-g0, см. выше).

-O3

Сборочные системы приложений обычно предполагают -O2, а порой вдобавок игнорируют выставленные CFLAGS/CXXFLAGS; собранные так программы могут работать на e2k медленней, чем способны при -O3.

-fwhole

Данный режим работы объединяет все модули программы в один большой модуль, что позволяет обходить ограничения классической помодульной сборки проекта и применять межпроцедурные оптимизации для процедур, находящихся в разных модулях. Режим похож на -flto у gcc и llvm, но обладает совершенно иной технической реализацией; по этой причине всё связанное с -flto (например, -fuse-linker-plugin, #4020) следует удалять из сборки под e2k.

Оптимизация очень сильная: разница между -O3 и -O3 -fwhole обычно больше, чем между -O2 и -O3. Но эту опцию нельзя просто так применять: в зависимости от ситуации нужно выбирать между -fwhole и -fwhole-shared для конкретных файлов, иногда вовсе нельзя: -fwhole можно применять для исполняемых файлов, но нельзя для динамических библиотек (-shared); для последних есть -fwhole-shared, но в ней есть смысл только совместно с -fvisibility=protected, что встречается редко. Опция обязательно должна подаваться не только на стадии компиляции, но и на финальной линковке. Несовместима с -g (#5104) и некоторыми расширениями GNU, см. документацию компилятора.

С точки зрения оптимизаций данный режим имеет большое влияние на любые межпроцедурные оптимизации: подстановка функций, анализы указателей, распространение констант, девиртуализация и т.д. Косвенным образом влияет и на внутрипроцедурные оптимизации, т.к. расширяет количество информации о коде. Для этого режима важно соблюдать ODR (One Definition Rule), т.е. не допускать наличия классов или объектов с одним именем, но различной реализацией.

Lcc-performance.jpg

версии

На том же самом оборудовании более новые версии lcc, как правило, позволяют добиться большей производительности -- поэтому есть прямой смысл обновлять сборки приложений, ключевых библиотек, а также ядра и окружения применяемой ОС.

ссылки

примечания

  1. соответственно заявленной в `lcc -v` совместимой версии
  2. __LCC__ <= 123, например
  3. см. тж. вкладку "Характеристики" на mcst.ru/lcc:
    1.24 поддерживает C++11/C++14 и частично C++17;
    1.23 -- C++11 и частично C++14,
    1.21 -- частично C++11
  4. ...где "как в апстриме"
  5. lcc 1.23.17 обучен опции --ignore-utf8-bom, но её не будет в 1.24.x.
  6. это ограничение соответствующей версии фронтэнда EDG -- или символьный, или целочисленный вариант должен быть выбран при сборке компилятора; в случае lcc исторически был выбран численный
  7. исправлено в 1.24, но тот представляется gcc7, от которого ожидают ещё более новых builtin'ов, в свою очередь
  8. и которому это удаётся из-за умения GCC встраивать вызов __cxa_vec_ctor; например, libgraphite2