Эльбрус/lcc

Материал из ALT Linux Wiki

См. тж. Руководство по эффективному программированию на платформе «Эльбрус»

lcc на e2k

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

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

С версии 1.25 LCC также взводит __MCST__.

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

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

проблемы

фронтэнд

Надо понимать, что МЦСТ применяет в lcc сторонний фронтэнд EDG[3], как несколько раньше делал и Intel в своём icc.

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

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

бэкенд

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

misoptimization

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

Без отключения оптимизаций отладочная информация будет бесполезна: lcc её построит, но она не будет ассоциирована с кодом.

При точечном поиске может пригодиться навесить на конкретную функцию __attribute__((optimize(0)))[5] или __attribute__((optimize("-O0"))).

Также могут пригодиться #pragma diag_suppress и #pragma diag_default.

segfault

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

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

линкер

Между сборками binutils в ОС Эльбрус[6] и ОС Альт есть существенная разница в поведении компоновщика по умолчанию (mcst#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 явно: в некоторых версиях компилятора известна нефатальная ошибка, которая может приводить к подобным сбоям.

В lcc 1.28 данная нефатальная ошибка компилятора исправлена.

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)
+    int n_gt_128 = n > 128;
 #pragma omp parallel for num_threads(CHOLMOD_OMP_NUM_THREADS) \
-    if ( n > 128 ) schedule (static)
+    if ( n_gt_128 ) schedule (static)

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

sed-патч от ilyakurdyukov@ на примере siril:

%ifarch %e2k
sed -i -E "/^[[:space:]]*#pragma omp.*\\\\$/N;\
/^[[:space:]]*#pragma omp .*(num_threads| if)\(/{s/#/for(long &/;\
s/(#.*num_threads\()([^()]*)\)/_xxxn=\\2,\\1_xxxn)/;\
s/(#.*if
*\()([^()]*(\([^()]*(\([^()]*\)[^()]*)*\)[^()]*)*)\)/_xxxi=\\2,\\1_xxxi)/;\
s/(#.*schedule\([^()]*, *)([^()]*)\)/_xxxs=\\2,\\1_xxxs)/;\
s/#/_xxxc=1;_xxxc;_xxxc=0)\n&/}" \
 SOURCES.c
%endif

howto

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

UTF-8 BOM

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

%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.25 по умолчанию -std=gnu++14; в каждом конкретном случае можно справиться с man lcc по -std=; сводка поддерживаемых стандартов доступна на страничке mcst.ru/lcc во вкладке "Характеристики".

-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

цитата разработчика

<helce> На O3 работает резолвер[8], который на основе эвристик определяет реально необходимый уровень оптимизаций. И фактически выставляет в O2 или O3.
<helce> И если что, нет никакой стандартизации. Есть gnu, и есть все остальное. Lcc не gnu, он всего лишь gnu-совместимый
<helce> В gcc уровень оптимизаций включает строго определенные линейки оптимизаций. И да на O3 O4 есть действительно агрессивные. Lcc работает иначе.
<helce> Ну и есть все тот же clang, который точно также кладет болт на стандарты gcc и в optimize режиме использует O3

optimize("O0")

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

%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'ов[10], в т.ч.: __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, данные типы не поддерживает.

R_E2K_32_ABS

При столкновении с ошибкой вида

relocation truncated to fit: R_E2K_32_ABS against `.debug_info'

попытайтесь заменить во флагах компиляции -g на -g0 (либо добавить -g0 после остальных флагов), чтобы снизить объём отладочной информации, или вовсе отключить её порождение (также бывает необходимо убрать -ggdb из конфигурации сборочной системы проекта).

В альтовом spec-файле это может выглядеть так, если сборочная система проекта обращает внимание на CFLAGS / CXXFLAGS (или так):

# due to R_E2K_32_ABS debuginfo truncation, cf.: webkit
%remove_optflags -g
%add_optflags -g0

Для новых сборок пакетов с библиотеками полное отключение отладочной информации может быть неприятным вдвойне, т.к. на их debuginfo-части наверняка будут зависимости у других пакетов и может потребоваться каскадное отключение *-debuginfo. По возможности просто снижайте уровень (на "ругань" %remove_optflags можно не обращать внимания).

Начиная с lcc-1.26.06 в компиляторе будет доступна опция --dwarf2-64bit, по которой при построении отладочной информации будут задействованы возможности из стандарта DWARF-3, которые позволяют формировать секции с отладочной информацией, которые после линковки превысят размер в 4 гигабайта.

-Wno-номер

Если необходимо подавить, скажем, warning #2506 (для разных версий номер одного и того же предупреждения разный):

lcc --version, получаем версию LCC; допустим, это 1.23.45 (1.MAJOR.MINOR), тогда:

// code
#if (__LCC__ == 123 && __LCC_MINOR__ == 45) // change to correct 100+MAJOR and MINOR correspondingly
#pragma diag_suppress 2506
#endif
// code that generates the warning
#if (__LCC__ == 123 && __LCC_MINOR__ == 45) // change to correct 100+MAJOR and MINOR correspondingly
#pragma diag_default 2506
#endif
// other code

precompile_header

При ошибках qmake-сборки плюсового кода вроде такой:

lcc: Command-line error #1696: cannot open source file "release/qcadecmaapi"

попробуйте отключить прекомпиляцию заголовков, если включена:

%ifarch %e2k
sed -i '/CONFIG += precompile_header/d' src/scripting/ecmaapi/ecmaapi.pro
%endif

пропавший нестатический inline

Из mcst#6575 (вкратце -- бывает в lcc 1.25 и 1.26, исправлено в lcc 1.27):

>> Функция audio_linear_dither по недосмотру объявлена inline без static.
>> Что в итоге приводит к ошибке при линковке:
>> ld: mad.o: in function `output':
>> mpg321-0.3.2-orig/mad.c:941: undefined reference to `audio_linear_dither'

> Объясните мне, каким образом неподставленный inline'ом вызов функции
> приводит к ошибке линковки?

Тело функции audio_linear_dither - это то, что в GNU C по смыслу эквивалентно extern inline'у. Т.е. функцию нужно либо про'inline'ить, либо удалить тело из кода. При этом в другом модуле должно быть объявлено "нормальное" тело функции без inline'а

Это довольно массовая проблема, которая вылезала в момент появления стандарта C11. Связано с тем, что стандарт C11 ввёл таким образом ключевое слово inline, что оно начало конфликтовать с существовавшим на тот момент поведением слова inline в языке GNU C. Для этого gnu'шники и вводили опцию -fgnu89-inline на переходный период.

Обход:

%add_optflags -fgnu89-inline

Исправление:

sed -i "s/^inline/static &/" mad.c

Комментарий:
inline без static в C[11] -- это признак дурного тона или очень старого кода. В современном коде все inline должны быть со static, нестатичный инлайн это редкая и никому не нужная фича, и лучше бы её не было во избежание подобных проблем.[12]

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 -I ./include/   ./core/xhtmlgenerator.cpp -E -o test.pp.cpp

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

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

особенности

система программирования

см. тж. mcst.ru/sdk

Для корректной работы следует придерживаться проверенных МЦСТ комбинаций Linux, glibc, lcc и binutils -- смежные версии могут работать (особенно в окрестностях точек перехода), но далее разработчики закладываются на особенности новых lcc в новых ядрах и т.д.; например, вот эта диагностика похожа на признак "перекоса" установки (получена от lcc 1.25 под Linux 4.9):

Assembler messages: Error: literals cannot be packed into a wide command
Error: cannot place literals
ядро Linux glibc lcc binutils
6.1 2.35 1.29 2.41
5.10 2.35 1.26 2.39
5.4 2.29 1.25 2.34
4.19 2.29 1.24 2.34
4.9 2.23 1.23 2.29
3.14 2.23 1.21 2.26

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++[13], на 1.23 может потребоваться другой обход -- компоновка с -lsupc++ -lgcc_eh -llcc из libstdc++5-devel-static.

ассемблерные вставки

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

оптимизация

Основная статья: эльбрус/оптимизация

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

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

-O3

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

Учтите, что lcc в некоторых случаях, как и gcc, может выдавать при -O3 менее эффективный код, чем в режиме -O2. Поэтому на данный момент рекомендуется внимательно замерять и сопоставлять итоговую производительность.

-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. ...соответственно взводит __EDG__
  4. см. тж. вкладку "Характеристики" на mcst.ru/lcc:
    1.25 поддерживает C++11/C++14 и частично C++17/C++20;
    1.24 -- C++11/C++14 и частично C++17;
    1.23 -- C++11 и частично C++14,
    1.21 -- частично C++11
  5. см. тж. mcst#4061, lcc <= 1.24.05 принимал здесь только численный аргумент
  6. ...где "как в апстриме"
  7. lcc 1.23.17 обучен опции --ignore-utf8-bom, но её не будет в 1.24.x.
  8. O4, если что, его тупо отрубает и всегда использует O3, насколько я понимаю
  9. это ограничение соответствующей версии фронтэнда EDG -- или символьный, или целочисленный вариант должен быть выбран при сборке компилятора; в случае lcc исторически был выбран численный
  10. исправлено в 1.24, но тот представляется gcc7, от которого ожидают ещё более новых builtin'ов, в свою очередь
  11. семантика в C++ совсем иная
  12. Это проблема и GCC, но он почти всегда инлайнит функции, а LCC часто не инлайнит крупные и не очень. Поэтому для GCC большинство подобных кривых инлайнов проходят незамечеными, а на LCC проявляется. (и если бы код изначально оказался загнут под LCC, а не GCC -- на последнем полезли бы симметричные проблемы; как и в других аналогичных случаях недетерминированного поведения)
  13. и которому это удаётся из-за умения GCC встраивать вызов __cxa_vec_ctor; например, libgraphite2