Apple llvm compiler clang

LLVM Compiler Overview

LLVM Compiler Overview

The LLVM compiler is the next-generation compiler, introduced in Xcode 3.2 for Snow Leopard, based on the open source LLVM.org project. The LLVM.org project employs a unique approach of building compiler technologies as a set of libraries. Capable of working together or independently, these libraries enable rapid innovation and the ability to attack problems never before solved by compilers. Multiple technology groups within Apple are active contributors within the LLVM.org community, and they use LLVM technology to make Apple platforms faster and more secure.

In Xcode, the LLVM compiler uses the Clang front end (a C-based languages project on LLVM.org) to parse source code and turn it into an interim format. Then the LLVM code generation layer (back end) turns that interim format into final machine code. Xcode also includes the LLVM GCC compiler, which uses the GCC compiler front end for maximum compatibility, and the LLVM back end, which takes advantage of LLVM’s advanced code generator. This shows the flexibility of a library-based approach to compiler development. There are many other features, such as link-time optimization, more detailed diagnostic information, and even static analysis, that are made available to Xcode due to the adoption of LLVM.

Watch the videos listed on this page to gain a detailed introduction to LLVM technology. The following videos were taken at the 2011 LLVM Developers’ Meeting:

LLVM MC in Practice

Register Allocation in LLVM 3.0

These videos were captured at the 2010 LLVM Developers’ Meeting:

The LLVM Assembler and Machine Code Infrastructure

The LLDB Modular Debugging Infrastructure

libclang: Thinking Beyond the Compiler

libc++: A Standard Library for C++0x

These videos were captured at the 2009 LLVM Developers’ Meeting:

Future Works in LLVM Register Allocation

ScalarEvolution and Optimization in LLVM

OpenCL and LLVM

Additional resources about LLVM technologies are available at the home page for the LLVM.org project.

Copyright © 2018 Apple Inc. All rights reserved. Terms of Use | Privacy Policy | Updated: 2012-12-13

Источник

Осваиваем кросс-компиляцию с помощью Clang и LLVM

Каждый, кто когда-либо пробовал собрать программу на C/C++ через кросс-компиляцию знает, насколько болезненным может быть этот процесс. Главными причинами столь печального положения вещей являются недружелюбность систем сборки при конфигурации кросс-компиляции, а также запутанность процесса настройки набора утилит (тулчейна).

Одним из основных виновников этих проблем, по моему опыту, является тулчейн GNU — древний мамонт, на котором много десятилетий строится весь мир POSIX. Подобно многим компиляторам былых времён, семейство GCC и binutils никогда не ориентировалось на поддержку множества различных целей сборки в одной установке, и единственным способом хоть как-то добиться желаемого была настройка полной кросс-билд-системы для каждой целевой платформы на каждом хосте.

Например, если вы хотите собрать что-то для FreeBSD на машине под Linux с помощью GCC, вам потребуется:

  • Установленный GCC + binutils для вашего сборочной платформы (т.е. x86_64-pc-linux-gnu или подобное);
  • Полностью установленный GCC + binutils для вашей целевой платформы (т.е. x86_64-unknown-freebsd12.2-gcc , as , nm и т.д.)
  • Sysroot со всеми необходимыми библиотеками и заголовочными файлами, который вы можете собрать самостоятельно, либо утащить из работающей FreeBSD.

Некоторые дистрибутивы Linux, а также некоторые разработчики оборудования могут облегчить этот процесс, предоставляя готовые наборы для компиляции, однако их никогда не хватает, ввиду огромного количества различных комбинаций сборочных/целевых платформ. Зачастую это означает, что вам необходимо самостоятельно полностью собрать тулчейн с нуля, что приводит к масштабным затратам времени и ресурсов, если конечно у вас не самый мощный процессор.

Clang как кросс-компилятор

Эти досадные ограничения побудили меня обратить внимание на LLVM (и Clang), который изначально создан как полноценный тулчейн для кросс-компиляции, и при этом практически полностью совместим с GNU. Один единственный инстанс LLVM способен собирать и компилировать код для каждой поддерживаемой платформы; помимо него для сборки нужен лишь sysroot.

Несмотря на то, что он ещё не может сравниться по удобству с тулчейнами современных языков (такими, как gc и GOARCH / GOOS в Go), это всё же настоящий глоток свежего воздуха по сравнению со сложностями настроек тулчейнов GNU. Вы можете просто установить его из пакетов для вашего дистрибутива (если только он не сильно старый), и сразу избежать всех сложностей с множественными установками GCC.

Ещё несколько лет назад весь процесс не был настолько хорошо настроен. Поскольку LLVM ещё не включал в себя весь тулчейн, вам нужно было по-прежнему откуда-то брать binutils , специфичный для вашей целевой платформы. И даже хотя решить эту проблему было гораздо проще, чем собрать весь компилятор целиком ( binutils собирается значительно быстрее), этот факт всё же добавлял хлопот. Однако к настоящему моменту llvm-mc (интегрированный ассемблер LLVM) и lld (универсальный линкер) уже стабильны и настолько же гибки, как весь остальной LLVM.

Читайте также:  Как русифицировать телеграмм для айфона

Когда весь тулчейн доступен, для компиляции и сборки вашего проекта необходим ещё sysroot, содержащий все необходимые библиотеки и заголовочные файлы.

Добываем sysroot

Быстрее всего раздобыть работающую системную папку для нужной ОС можно, скопировав её напрямую из существующей системы (для этого зачастую подходит контейнер Docker). Например, вот так я скопировал работающий sysroot из виртуалки с FreeBSD 13-CURRENT AArch64 с помощью tar и ssh

При копировании по сети, а не локально, неплохо также сжать получившийся tarball

Запускаем кросс-компилятор

Когда всё готово, остаётся лишь запустить Clang с правильными аргументами:

В приведённом примере я собрал исходник C++ в программу для платформы AArch64 FreeBSD, и всё это с использованием лишь clang и lld , которые я уже установил на своей машине GNU/Linux.

  1. —target переключает целевую платформу LLVM по умолчанию ( x86_64-pc-linux-gnu ) в aarch64-pc-freebsd , таким образом включая кросс-компиляцию.
  2. —sysroot заставляет Clang использовать указанный путь как корневой для поиска библиотек и заголовочных файлов, вместо обычных путей. Заметьте, что иногда этого ключа недостаточно, особенно если проект использует GCC, а Clang не может определить путь к нему. Такая проблема может быть легко исправлена указанием ключа —gcc-toolchain , который указывает, где найти установленный GCC.
  3. -fuse-ld=lld указывает Clang использовать lld вместо любого другого линковщика, используемого в системе. Скорее всего системный линковщик, доступный по умолчанию, не сможет собрать «чужую» программу, в то время как LLD поддерживает напрямую практически все форматы исполняемых файлов и операционных систем.
  4. -stdlib=libc++ необходимо указать, поскольку Clang не может сам определить, что FreeBSD на платформе AArch64 использует библиотеку libc++ из LLVM, а не libstdc++ из GCC.
  5. -lz добавлено, чтобы показать, как Clang умеет без проблем находить в sysroot другие библиотеки, в данном случае zlib .

К сожалению, macOS больше не поддерживается в LLD, ввиду того, что поддержка формата Mach-O была заброшена несколько лет назад. В связи с этим единственным способом собрать исполняемый файл формата Mach-O является использование линкера ld64 (нативного, или в кросс-системе, если вы сами его соберёте). Хотя утилита ld.bfd из binutils всё ещё его поддерживает.

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

Всё получилось! Теперь мы можем использовать этот кросс-тулчейн для сборки более крупных программ, и ниже я расскажу, как использовать его для сборки реальных проектов.

Опционально: создаём папку с тулчейном LLVM

LLVM предоставляет практически полностью совместимые альтернативы для всех утилит, входящих в binutils (за исключением разве что as ), с префиксом llvm- в имени.

llvm-mc можно использовать как (очень громоздкий) ассемблер, но он плохо документирован. Подобно gcc , фронтэнд clang также может использоваться как ассемблер, делая as зачастую ненужным.

Наиболее критичной из них является LLD, который полностью подменяет системный линковщик для целевой платформы и может заменить одновременно как ld.bfd из GNU, или gold из GNU/Linux или BSD, так и LINK.EXE от Microsoft при сборке под MSVC. Он поддерживает сборку на (практически) каждой платформе, поддерживаемой LLVM, таким образом устраняя необходимость в нескольких специфических линковщиках.

Оба компилятора, GCC и Clang поддерживают использование ld.lld вместо системного линковщика (которым также может быть и lld , например, на FreeBSD) с помощью ключа -fuse-ld=lld .

На опыте я сталкивался с тем, что драйвер Clang иногда не может найти верный линковщик для некоторых редких платформ, особенно до версии 11.0. По какой-то причине иногда clang открыто игнорирует ключ -fuse-ld=lld и вызывает системный линковщик (в моём случае ld.bfd ), который не умеет собирать для AArch64.

Быстрым решением для этой проблемы будет создание папки тулчейна, содержащей символические ссылки, которые переименовывают утилиты LLVM в стандартные программы из binutils :

Также можно использовать ключ -B , который заставляет Clang (или GCC) искать нужные утилиты в этой папке, так что подобная проблема даже не возникает:

Опционально: создаём обёртки Clang для упрощения кросс-компиляции

Я заметил, что некоторые системы сборки (и под «некоторыми» я имею в виду некоторые кривые Makefile , и иногда Autotools) склонны ломаться, если значения переменных окружения $CC , $CXX или $LD содержат пробелы или несколько параметров. Такое может периодически случаться, если нам необходимо вызвать clang с несколькими аргументами (не говоря уже о тех преступниках, которые хардкодят вызовы gcc в свои билд-скрипты. Впрочем, это уже совсем другая история.)

Понимая, насколько громоздко и сложно каждый раз не забыть выписать все параметры корректно в каждом из случаев, я обычно пишу короткие обёртки для clang и clang++ с целью упростить сборку для конкретной целевой платформы:

Если этот скрипт доступен внутри $PATH, его можно повсеместно использовать как отдельную команду:

Читайте также:  Iphone 6 plus case from apple

Кросс-сборка с помощью Autotools, Cmake и Meson

Autotools, Cmake и Meson, возможно, самые популярные системы сборки для open-source проектов на C и C++ (извини, SCons). Все три поддерживают кросс-компиляцию прямо «из коробки», хотя с некоторыми особенностями.

Autotools

В течение многих лет Autotools славится своей ужасной неуклюжестью и хрупкостью. И хотя эту репутацию он заработал вполне обосновано, он по-прежнему широко используется для большинства крупных проектов GNU. Будучи в строю уже не одно десятилетие, многие его проблемы, если что-то вдруг пошло криво, можно разрешить поиском в сети (хотя если вы пишете свой .ac файл, это не всегда так). По сравнению с более современными системами, для кросс-компиляции ему не нужен специфичный файл тулчейна или какая-то особая конфигурация; всё настраивается исключительно параметрами командной строки.

Скрипт ./configure (созданный утилитой autoconf, или включённый в тарболл с исходниками) обычно поддерживает флаг —host , позволяя пользователю задать триплет целевой системы, на которой предполагается запускать собранные программы и прочие артефакты.

Этот флаг активирует кросс-компиляцию, после чего множество утилит auto-что-то-там начинают выяснять правильный компилятор для целевой платформы, который, как правило, зовётся по именам some-triple-gcc или some-triple-g++ .

Попробуем сконфигурировать сборку binutils версии 2.35.1 для платформы aarch-pc-freebsd , используя написанную выше обёртку для вызова Clang:

Вызов скрипта ./configure выше означает, что я хочу от autotools следующего:

  1. Сконфигурировать сборку на платформе x86_64-pc-linux-gnu (которую я задал с помощью ключа —build );
  2. Собрать программы, которые будут исполняться на платформе aarch64-pc-freebsd , что задаётся ключом —host ;
  3. В качестве компиляторов C и C++ использовать обёртки Clang, описанные выше.
  4. В качестве целевой утилиты ar использовать llvm-ar .

Я также задал сборку линковщика Gold, который написан на С++ и может использоваться как неплохой тест на то, как наш импровизированный тулчейн способен компилировать программы на C++.

Если стадия конфигурации не сломается по какой-то причине (вроде не должно), мы можем далее запустить GNU Make и собрать binutils :

Здесь мы должны получить исполнимые файлы и библиотеки внутри целевой папки, созданной с помощью make install . Быстрая проверка с помощью file подтверждает, что все программы корректно собраны для платформы aarch64-pc-freebsd :

CMake

Проще всего настроить CMake для сборки на конкретную платформу можно с помощью файла тулчейна. Он обычно состоит из настроек, которые указывают CMake, как он должен работать с данным тулчейном, определяя такие параметры, как целевая операционная система, архитектура CPU, имя компилятора C++ и т.д.

Для сборки под триплет aarch64-pc-freebsd тулчейн файл может быть таким:

В этом файле я назначил вышеупомянутую обёртку как кросс-компилятор кода C и C++ для целевой платформы. Также можно использовать напрямую Clang с подходящими параметрами, но это не настолько прямолинейно и потенциально более подвержено ошибкам.

В любом случае, исключительно важно задать правильные значения для переменных CMAKE_SYSROOT и CMAKE_FIND_ROOT_PATH_MODE_* , иначе CMake может ошибочно взять пакеты от текущей платформы, с закономерно неверным результатом.

Далее остаётся лишь указать путь к этому файлу, при конфигурировании сборки, через переменную CMAKE_TOOLCHAIN_FILE , либо через параметр —toolchain . Для примера я соберу пакет (это удивительная библиотека C++, которую вам непременно стоит попробовать) для платформы aarch64-pc-freebsd :

По сравнению с Autotools, командная строка, переданная в cmake очень проста и не требует дополнительных объяснений. После конфигурирования остаётся лишь скомпилировать проект, а затем запустить ninja или make , чтобы установить куда-нибудь получившиеся артефакты.

Meson

Подобно CMake, Meson полагается на файлы тулчейнов (так называемые «cross files»), которые определяют, какие программы должны быть использованы для сборки под текущую целевую платформу. Благодаря тому, что они написаны на TOML-подобном языке, они очень просты и понятны:

Этот кросс-файл далее нужно указать при вызове meson setup с помощью ключа —cross-file . Остальные настройки абсолютно такие же, как для любой другой сборки с помощью Meson.

Подобным образом можно настроить нативный тулчейн на текущей машине, используя нативный файл с ключом —native-file .

И на этом, пожалуй, всё: подобно CMake весь процесс относительно безболезненен и безошибочен. Для полноты расскажу, как собрать dav1d , декодер VideoLAN AV1, для платформы aarch64-pc-freebsd :

Бонус: статическая сборка с musl и Alpine Linux

Статическая сборка программ на C и C++ иногда может спасти вас от множества проблем совместимости библиотек, особенно когда вы не можете контролировать, что именно будет установлено на той целевой платформе, под которую вы планируете сборку. Однако сборка статических бинарников на GNU/Linux довольно непроста, поскольку Glibc всячески препятствует попыткам слинковаться с ним статически.

Система разрешения имён (NSS), встроенная в glibc, является одной из главных причин интенсивного использования функций dlopen()/dlsym(). Это связано с использованием сторонних плагинов, вроде mDNS, используемых для разрешения имён.

Musl — это альтернанивный вариант стандартной библиотеки для Linux, которая гораздо терпимее к статической линковке, и нынче включена в большинство крупных дистрибутивов. Этих пакетов зачастую вполне достаточно для статической сборки вашего кода, по крайней мере пока вы планируете оставаться в рамках чистого C.

Однако если вы планируете собирать C++, либо если вам необходимы дополнительные компоненты, ситуация становится гораздо сложнее и интереснее. Любая библиотека, входящая в GNU/Linux (такая, как libstdc++ , libz , libffi и другие) обычно собрана только с Glibc, что означает, что любая библиотека, которую вы хотите использовать, должна быть пересобрана для цели Musl. Это так и для libstdc++ , что неизбежно означает либо перекомпиляцию GCC, либо сборку копии libc++ от LLVM.

Читайте также:  Iphone при зарядке мигает только яблоко

К счастью, существует несколько дистрибутивов, собранных для платформы «Musl-plus-Linux», наиболее известный из которых Alpine Linux. А потому возможно использовать освоенный нами подход: извлекаем sysroot для платформы x86_64-pc-linux-musl , в котором находится полный комплект библиотек и пакетов собранных для Musl, а затем используем его вместе с Clang для сборки 100% статических программ.

Настройка контейнера с Alpine

Хорошей отправной точкой является тарболл с minirootfs от Alpine, который предназначен специально для контейнеров, а потому очень маленький:

Теперь мы можем сделать chroot внутрь образа

/alpine_tree и настроить его, установив все нужные нам пакеты. Обычно я предпочитаю использовать systemd-nspawn вместо chroot , поскольку это значительно проще и меньше склоняет к ошибкам.

system-nspawn может также служить лёгкой альтернативой виртуальным машинам. С ключом —boot он умеет запускать процесс init внутри контейнера. Вот в этом весьма полезном gist можно научиться создавать загрузочный контейнер для дистрибутивов, основанных на OpenRC (например, Alpine)

Здесь мы можем (по желанию) отредактировать /etc/apk/repositories , чтобы переключиться на бранч edge с самыми свежими пакетами, а затем установить нужные пакеты Alpine, содержащие любые статические библиотеки для сборки кода, который мы хотим собрать:

В данном примере я установил g++ и libc-dev , чтобы получить статические копии libstdc++ , статическую libc.a (Musl), и их соответствующие заголовочные файлы. Я также установил zlib-dev и zlib-static , чтобы получить заголовки zlib и библиотеку zlib.a . Как общее правило — Alpine обычно предоставляет статические версии в пакетах *-static , и заголовочные файлы в пакетах somepackage-dev .

К сожалению, по неизвестным мне причинам Alpine не предоставляет статические версии некоторых библиотек (например, libfmt ). Поэтому внедрять копию зависимостей в проект является вполне обычной практикой для C++, и потому это довольно несложно.

Также не забывайте каждый раз запускать apk upgrade внутри sysroot, чтобы поддерживать локальную копию Alpine в актуальном состоянии.

Сборка статических программ на C++

Когда всё готово, остаётся лишь вызвать clang++ с верным —target и —sysroot :

Дополнительный ключ —gcc-toolchain в данном случае опционален. Он поможет, если Clang не найдёт внутри sysroot GCC и различные файлы crt*.o. Дополнительный ключ -L для /lib необходим, поскольку в Alpine библиотеки лежат как в /usr/lib , так и в /lib , а последний не рассматривается по умолчанию в clang , который ожидает, что все библиотеки находятся только в $SYSROOT/usr/lib .

Написание обёртки для статической линковки с Musl и Clang

В пакетах Musl обычно есть готовые обёртки musl-gcc и musl-clang , которые вызывают системные компиляторы для сборки и линковки с альтернативным libc. Для удобства я по-быстрому набросал вот такой скрипт на Perl:

Это более «продвинутый» вариант обёртки, чем тот, что я привёл выше для FreeBSD AArch64. Например, она подразумевает компиляцию кода на С++, если вызвана по имени clang++ , или всегда добавляет ключ -static , если вызвана по символической ссылке, содержащей в имени слово static :

Таким образом, теперь возможно заставить Clang линковать только с ключом -static , если установить $CC в musl-clang-static , что может быть полезно для систем сборки, которые не очень хорошо работают со статической линковкой. По опыту могу сказать, что больше всего проблем с этим доставляют Autotools (иногда), а также кривые Makefiles.

Заключение

Кросс-компиляция кода на C и C++ является, и скорее всего навсегда останется непростой задачей, однако она стала значительно проще, покуда LLVM достиг стабильности и стал широко распространён. Параметр -target в Clang избавил меня от траты бесчисленного множества человеко-часов на бесконечную сборку и пересборку GCC и Binutils.

К сожалению, не всё то золото, что блестит, и так бывает довольно часто. До сих пор встречаются костыли, которые собираются только с помощью GCC из-за грязных GNUизмов (да-да, Glibc, это про тебя). Кросс-компиляция под Windows/MSVC также невозможна ввиду сильной запутанности всего тулчейна Visual Studio.

Кроме того, хотя сборка под целевую платформу путём указания в Clang верного триплета стала гораздо проще, чем когда-то была, она всё равно нервно курит, поглядывая на то, насколько тривиальна кросс-компиляция в случае с Rust или Go.

Из новых языков отдельного упоминания заслуживает Zig, поскольку его целью является также облегчение сборки кода на C и C++ под другие платформы.

Команды zig cc и zig c++ потенциально могут стать удивительным швейцарским ножом для кросс-компиляции, благодаря тому, что Zig содержит в себе clang и множество частей из разных проектов, такие как Glibc, Musl, libc++ и MinGW. Благодаря этому любая нужная библиотека собирается, если надо, прямо на лету:

Пока, я думаю, это выглядит не так совершенно, однако уже работает прямо как магия. Осмелюсь утверждать, что это настоящая киллер-фича Zig, и она достойна внимания даже тех, кому совсем не интересен сам язык.

Источник

Оцените статью