Clang - компиляция заголовка C для LLVM IR/bitcode
Скажем, у меня есть следующий тривиальный заголовочный файл C:
// foo1.h
typedef int foo;
typedef struct {
foo a;
char const* b;
} bar;
bar baz(foo*, bar*, ...);
Моя цель - взять этот файл и создать модуль LLVM, который выглядит примерно так::
%struct.bar = type { i32, i8* }
declare { i32, i8* } @baz(i32*, %struct.bar*, ...)
Другими словами, преобразуйте файл C .h
с декларациями в эквивалентный LLVM IR, включая разрешение по типу, расширение макроса и т.д.
Передача этого через Clang для генерации LLVM IR создает пустой модуль (так как ни одно из определений не используется):
$ clang -cc1 -S -emit-llvm foo1.h -o -
; ModuleID = 'foo1.h'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.3.0"
!llvm.ident = !{!0}
!0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}
Мой первый инстинкт состоял в том, чтобы обратиться к Google, и я столкнулся с двумя связанными вопросами: один из списка рассылки, и один из StackOverflow. Оба предложили использовать флаг -femit-all-decls
, поэтому я попробовал это:
$ clang -cc1 -femit-all-decls -S -emit-llvm foo1.h -o -
; ModuleID = 'foo1.h'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.3.0"
!llvm.ident = !{!0}
!0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}
Тот же результат.
Я также попытался отключить оптимизацию (как с -O0
, так и -disable-llvm-optzns
), но это не имело никакого значения для вывода. Используя следующий вариант, получим желаемый ИК:
// foo2.h
typedef int foo;
typedef struct {
foo a;
char const* b;
} bar;
bar baz(foo*, bar*, ...);
void doThings() {
foo a = 0;
bar myBar;
baz(&a, &myBar);
}
Затем выполняется:
$ clang -cc1 -S -emit-llvm foo2.h -o -
; ModuleID = 'foo2.h'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.3.0"
%struct.bar = type { i32, i8* }
; Function Attrs: nounwind
define void @doThings() #0 {
entry:
%a = alloca i32, align 4
%myBar = alloca %struct.bar, align 8
%coerce = alloca %struct.bar, align 8
store i32 0, i32* %a, align 4
%call = call { i32, i8* } (i32*, %struct.bar*, ...)* @baz(i32* %a, %struct.bar* %myBar)
%0 = bitcast %struct.bar* %coerce to { i32, i8* }*
%1 = getelementptr { i32, i8* }* %0, i32 0, i32 0
%2 = extractvalue { i32, i8* } %call, 0
store i32 %2, i32* %1, align 1
%3 = getelementptr { i32, i8* }* %0, i32 0, i32 1
%4 = extractvalue { i32, i8* } %call, 1
store i8* %4, i8** %3, align 1
ret void
}
declare { i32, i8* } @baz(i32*, %struct.bar*, ...) #1
attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}
Помимо метки-заполнителя doThings
, это именно то, что я хочу, чтобы результат выглядел! Проблема в том, что для этого требуется 1.) использование модифицированной версии заголовка и 2.) знание заранее определенных типов вещей. Что заставляет меня...
Почему?
В принципе, я создаю реализацию для языка, использующего LLVM для генерации кода. Реализация должна поддерживать C interop, указывая только файлы заголовков C и связанные с ними библиотеки (без ручных деклараций), которые затем будут использоваться компилятором до времени ссылки, чтобы гарантировать, что вызовы функций соответствуют их подписям. Следовательно, я сузил проблему до двух возможных решений:
- Поверните файлы заголовков в LLVM IR/bitcode, который затем может получить подпись типа каждой функции
- Используйте
libclang
для синтаксического анализа заголовков, а затем запросите типы из полученного АСТ (мое "последнее средство" в случае отсутствия достаточного ответа на этот вопрос)
TL; DR
Мне нужно взять файл заголовка C (например, выше foo1.h
) и, не меняя его, сгенерировать вышеупомянутый ожидаемый LLVM IR с помощью Clang, OR, найти другой способ получить сигнатуры функций из заголовка C файлы (предпочтительнее использовать libclang
или создать парсер C)
Ответы
Ответ 1
Возможно, менее элегантное решение, но оставаясь с идеей функции doThings
, которая заставляет компилятор испускать ИК, потому что используются определения:
Две проблемы, которые вы идентифицируете с этим подходом, заключаются в том, что она требует изменения заголовка и требует более глубокого понимания типов, используемых для генерации "использования" для ввода функции. Оба они могут быть преодолены относительно просто:
-
Вместо того, чтобы напрямую компилировать заголовок, #include
он (или, скорее, его предварительно обработанная версия или несколько заголовков) из файла .c, который содержит весь код "использует". Достаточно прямо:
// foo.c
#include "foo.h"
void doThings(void) {
...
}
-
Вам не нужна подробная информация о типе, чтобы генерировать определенные применения имен, сопоставляя экземпляры структуры с параметрами и всю эту сложность, как у вас в коде "использует" выше. Вам фактически не нужно собирать подписи функций самостоятельно.
Все, что вам нужно, это список самих имен и отслеживать, предназначены ли они для функции или для типа объекта. Затем вы можете переопределить функцию "uses", чтобы выглядеть так:
void * doThings(void) {
typedef void * (*vfun)(void);
typedef union v { void * o; vfun f; } v;
return (v[]) {
(v){ .o = &(bar){0} },
(v){ .f = (vfun)baz },
};
}
Это значительно упрощает "использование" имени для того, чтобы либо применить его к унифицированному типу функции (и вместо того, чтобы называть его указатель), либо обернуть его в &(
и ){0}
(копируя его независимо от что это). Это означает, что вам не нужно хранить фактическую информацию о типе вообще, только тот вид контекста, из которого вы извлекли имя в заголовке.
(очевидно, что фиктивная функция и типы заполнителей расширяют уникальные имена, поэтому они не сталкиваются с кодом, который вы действительно хотите сохранить)
Это значительно упрощает шаг синтаксического анализа, так как вам нужно только признать контекст объявления struct/union или function, не требуя при этом очень большой информации об окружающей среде.
Простая, но хакерская отправная точка (которую я, вероятно, буду использовать, потому что у меня низкие стандарты: D) может быть:
- grep через заголовки для директив
#include
, которые принимают аргумент с угловой скобкой (т.е. установленный заголовок, который вы также не хотите генерировать объявления).
- используйте этот список, чтобы создать пустую папку include со всеми необходимыми входящими файлами, но пустыми
- предварительно обработать его в надежде на упрощение синтаксиса (
clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h
или что-то подобное)
- grep through для
struct
или union
, за которым следует имя, }
, за которым следует имя, или name (
, и используйте этот смехотворно упрощенный неанализ для создания списка применений в фиктивной функции, и испускать код для .c файла.
Он не поймает всех возможностей; но с небольшим количеством настроек и расширения, вероятно, он будет иметь дело с большим подмножеством реалистического кода заголовка. Вы можете заменить его специальным упрощенным синтаксическим анализатором (одним из которых является только просмотр шаблонов контекстов, которые вам нужны) на более позднем этапе.