Чего ни хватишься - ничего нет

Собирая тут всякое (по длинному списку) на Mac с процессором M1 возжелал я иметь все в Universal binaries (это когда в одном флаконе бинарники/объектники для нескольких процессорных архитектур). Я так, в принципе, уже делал, вот RawDigger/Legacy собирается до сих пор с 32/64 бита в одном флаконе и ничего.

Если кто не в курсе, то маковский toolchain понимает clang -arch arm64 -arch x86_64 и в этом случае:

  • Компиляция запускается (внутри) два раза, для разных архитектур
  • Порождается один объектный файл в котором две секции для разных архитектур
  • При линковке - порождается бинарник в котором тоже две секции
  • И в простом случае все работает: две архитектуры достаточно похожи (размер типов данных, порядок байт) чтобы об этом не заморачиваться. Вот LibRaw, к примеру, так собирается, достаточно CFLAGS правильно поставить.

Все становится гораздо хуже, если внутри собираемого есть что-то сильно processor-specific. Ну к примеру SSE2 или NEON. Как правило, сборка подобных библиотек устроена так:

  • Запускаем ./configure (cmake, qmake)
  • Гоняем там какие-то тесты
  • Записываем результат в что-то вроде config.h или просто в параметры компилятора (-DHAVE_SSE2)
  • Ну и дальше компилируем.
  • Зачастую, всякие процессоро-специфичные вещи отложены в отдельные файлы (ну там blabla-sse.c для x86, blabla-neon.c для ARM) и в сборку попадает, естественно, только один из них.

Распространенные системы сборки не рассчитаны на "сборку на две архитектуры", комплект тестов гоняется только один, соответственно Makefile будет создан или только для SSE или только для NEON, но никак не для двух сразу.

Легкого решения, по всей видимости, не существует, потребуется как модернизация сборочных систем (чтобы гоняли тесты отдельно для arm, отдельно для x86 и писали бы два config.h), так и модификация исходников самих библиотек (утилит). Я это вижу примерно как-то так:

  • внешние тесты/конфигурация пишут макросы типа TRY_SSE2/TRY_NEON
  • В сборку всегда кладутся все исходники (ну точнее все, которые нужны для хотя бы одной архитектуры из собираемых)
  • Сами исходники нужно модифицировать в каком-то таком духе, чтобы
    • Всякие config.h включались бы через #ifdef __aarch64__ #include "config.arm.h" ну и так далее
    • Всякий платформенно-специфичный код проверял бы два макроса сразу, и TRY_EXTENSION и "а та ли платформа"

Да, решение есть, можно собирать отдельно и клеить при помощи lipo. Но то такое.

Понятно, что затрагивает это все только тех, кто делает бинарные файлы навынос, компилировать "для себя" надо только native и не париться.

Самое забавное тут то, что проблеме лет примерно 15: universal binaries появились на Mac OS X в момент появления маков на интеловских процессорах, но не видно чтобы кто-то всерьез почесался, ну или я про такое не знаю.

 

Comments

Я не знаю как устроен маковский стор, может там universal binaries в требованиях. Но если брать не из стора, то они не слишком-то и нужны: отсюда загружать для старых систем, оттуда для новых, а кто перепутает получит сообщение об ошибке и прочитает инструкцию еще раз. Тем более что сосуществование разных платформ недолго длится.

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

(чтобы гоняли тесты отдельно для arm, отдельно для x86 и писали бы два config.h)

На одной системе? Или система сборки становится распределённой?

Конечно на одной и одной командой.
Тулчейн то умеет в кросс-компиляцию, причем если мы на ARM гоняем - там есть (вполне рабочая) эмуляция x86

> (вполне рабочая) эмуляция

Если верить в эмуляцию, так может и одного комплекта тестов будет достаточно.

Эмуляция работает, но есть обоснованное подозрение, что native будет быстрее (при первом запуске так точно, потому что трансляция же)