Как скомпилировать и сохранить "неиспользуемые" объявления C с clang -emit-llvm
контекст
Я пишу компилятор для языка, который требует много функций времени исполнения. Я использую LLVM в качестве моего бэкэнд, поэтому для codegen нужны типы для всех этих типов времени выполнения (функции, структуры и т.д.), И вместо того, чтобы определять все из них вручную с помощью API LLVM или почерка LLVM IR, я бы хотел написать заголовки в C и скомпилировать бит-код, который компилятор может использовать с LLVMParseBitcodeInContext2
.
вопрос
Проблема, с которой я сталкиваюсь, заключается в том, что clang, похоже, не содержит никаких объявлений типов, которые не используются никакими определениями функций. У Clang есть -femit-all-decls
который звучит так, как будто он должен его решить, но, к сожалению, это не так, и Googling предлагает это неправильно, поскольку это затрагивает только неиспользуемые определения, а не декларации.
Затем я подумал, что, если я скомпилирую заголовки только в .gch
файлы, я мог бы .gch
их с помощью LLVMParseBitcodeInContext2
одинаково (поскольку документы говорят, что они используют "тот же" формат битового кода ", однако это делает ошибки с error: Invalid bitcode signature
поэтому что-то должно быть по-другому. Может быть, разница достаточно мала, чтобы обходиться?
Любые предложения или относительно легкие обходные пути, которые могут быть автоматизированы для сложной среды выполнения? Мне также было бы интересно, если у кого-то есть абсолютно альтернативное предложение о приближении к этому общему прецеденту, имея в виду, что я не хочу статически связывать тела тела выполнения для каждого отдельного файла объекта, который я генерирую, только типы. Я предполагаю, что это то, что нужно другим компиляторам, поэтому я не удивлюсь, если я подхожу к этому неправильно.
например, с учетом этого ввода:
runtime.h
struct Foo {
int a;
int b;
};
struct Foo * something_with_foo(struct Foo *foo);
Мне нужен биткодовый файл с этим эквивалентным IR
runtime.ll
; ...etc...
%struct.Foo = type { i32, i32 }
declare %struct.Foo* @something_with_foo(%struct.Foo*)
; ...etc...
Я мог бы написать все это вручную, но это было бы дублирующим, поскольку мне также нужно создавать заголовки C для другого взаимодействия, и было бы идеально, если бы не синхронизировать их вручную. Время выполнения довольно велико. Я думаю, что я мог бы также делать все наоборот: писать объявления в LLVM IR и генерировать заголовки C.
Кто-то еще спросил об этом годах назад, но предлагаемые решения довольно хаки и довольно непрактичны для среды выполнения такого размера и сложности: Clang - компиляция заголовка C для LLVM IR/bitcode
Ответы
Ответ 1
Таким образом, clang
фактически не отфильтровывает неиспользуемые объявления. Он откладывает отправку деклараций вперед до их первого использования. Всякий раз, когда используется функция, она проверяет, испущена ли она, если она не испускает объявление функции.
Вы можете посмотреть эти строки в репозитории clang.
// Forward declarations are emitted lazily on first use.
if (!FD->doesThisDeclarationHaveABody()) {
if (!FD->doesDeclarationForceExternallyVisibleDefinition())
return;
Простым исправлением здесь было бы либо прокомментировать последние две строки, либо просто добавить && false
ко второму условию.
// Forward declarations are emitted lazily on first use.
if (!FD->doesThisDeclarationHaveABody()) {
if (!FD->doesDeclarationForceExternallyVisibleDefinition() && false)
return;
Это приведет к тому, что clang
будет clang
объявление, как только он его увидит, это также может изменить порядок определения определений в ваших .ll
(или .bc
) файлах. Предполагая, что это не проблема.
Чтобы сделать его более чистым, вы также можете добавить флаг командной строки --emit-all-declarations
и проверить это перед тем, как продолжить.
Ответ 2
Реализация преампированных заголовков Clang, похоже, не выводит LLVM IR, а только AST (абстрактное синтаксическое дерево), так что заголовок не нуждается в повторном анализе:
Сам файл AST содержит сериализованное представление абстрактных синтаксических деревьев Clangs и поддерживающих структур данных, которые хранятся с использованием того же сжатого битового потока, что и формат файла биткода LLVM.
Базовый двоичный формат может быть одинаковым, но это похоже на то, что содержимое отличается, а формат бит-кода LLVM - это просто контейнер в этом случае. Это не очень понятно на странице справки на веб-сайте, поэтому я просто размышляю. Эксперт LLVM/Clang может помочь прояснить этот момент.
К сожалению, не кажется, что это элегантный способ. Что я предлагаю, чтобы свести к минимуму усилия, необходимые для достижения того, что вы хотите, это создать минимальный исходный файл C/C++, который каким-то образом использует все объявления, которые вы хотите скомпилировать для LLVM IR. Например, вам просто нужно объявить указатель на структуру, чтобы убедиться, что она не оптимизирована, и вы можете просто предоставить пустое определение функции для сохранения своей подписи.
Если у вас есть минимальный исходный файл, скомпилируйте его с помощью clang -O0 -c -emit-llvm -o precompiled.ll
чтобы получить модуль со всеми определениями в формате LLVM IR.
Пример из опубликованного фрагмента:
struct Foo {
int a;
int b;
};
// Fake function definition.
struct Foo * something_with_foo(struct Foo *foo)
{
return NULL;
}
// A global variable.
struct Foo* x;
Результат, который показывает, что определения хранятся: https://godbolt.org/g/2F89BH