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, указывая только заголовочные файлы C и связанные с ними библиотеки (без деклараций вручную), которые затем будут использоваться компилятором до времени ссылки, чтобы гарантировать, что вызовы функций соответствуют их сигнатурам. Следовательно, я сузил проблему до 2 возможных решений:
- Поверните заголовочные файлы в LLVM IR / bitcode, который затем может получить подпись типа каждой функции
- использовать
libclang
чтобы проанализировать заголовки, затем запросить типы из результирующего AST (мое "последнее средство" в случае, если нет достаточного ответа на этот вопрос)
TL; DR
мне нужно принять файл заголовка C (например,foo1.h
) и, не меняя его, сгенерируйте вышеупомянутый ожидаемый LLVM IR с помощью Clang или найдите другой способ получить сигнатуры функций из файлов заголовков C (желательно с помощью libclang
или построение парсера C)
1 ответов
возможно, менее элегантное решение, но оставаясь с идеей doThings
функция, которая заставляет компилятор испускать ИК, потому что используются определения:
две проблемы, которые вы идентифицируете с этим подходом, заключаются в том, что он требует изменения заголовка и более глубокого понимания типов, участвующих в создании "использования" для ввода функции. И то и другое можно преодолеть относительно просто:
-
вместо компиляция заголовка напрямую,
#include
it (или, скорее, его предварительно обработанная версия или несколько заголовков) из a .C файл, содержащий весь код" uses". Достаточно просто:// foo.c #include "foo.h" void doThings(void) { ... }
-
вам не нужна подробная информация о типе для создания конкретных использований имен, сопоставления экземпляров структуры с параметрами и всей этой сложности, как в коде "использует" выше. на самом деле вам не нужно собирать подписи функций себя!--24-->.
все, что вам нужно, это список самих имен и отслеживать, являются ли они для функции или для типа объекта. Затем вы можете переопределить свою функцию "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}
(инстанцируя его независимо от того, что он is). Это означает, что вам не нужно хранить фактическую информацию о типе вообще, только вид контекст из которого вы извлекли имя в заголовке.(очевидно, дайте фиктивную функцию и типы заполнителей расширенные уникальные имена, чтобы они не конфликтовали с кодом, который вы действительно хотите сохранить)
это значительно упрощает шаг синтаксического анализа, так как вам нужно только распознать контекст структуры / объединения или функции декларации, не нужно делать очень много с окружающей информацией.
простая, но хакерская отправная точка (которую я, вероятно, буду использовать, потому что у меня низкие стандарты: D) может быть:
- grep через заголовки для
#include
директивы, которые принимают аргумент в квадратных скобках (т. е. установленный заголовок, для которого вы также не хотите создавать объявления). - используйте этот список для создания пустой папки со всеми необходимо включить файлы, присутствующие, но пустые
- предварительно обработайте его в надежде, что это упростит синтаксис (
clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h
или что-то подобное) - grep Через для
struct
илиunion
за ним следует имя,}
с последующим именем илиname (
, и используйте этот смехотворно упрощенный non-parse, чтобы построить список применений в фиктивной функции и выдать код для .файл c.
он не поймает все возможности; но с немного настройки и расширение, вероятно, на самом деле будет иметь дело с большим подмножеством реалистичного кода заголовка. Вы можете заменить это выделенным упрощенным синтаксическим анализатором (который построен только для просмотра шаблонов контекстов, которые вам нужны) на более позднем этапе.