Clang — Компиляция заголовка C в LLVM IR / битовый код

Скажем, у меня есть следующий тривиальный заголовочный файл 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 файл с объявлениями в эквивалентный IR LLVM, включая разрешение типа, расширение макроса и т. д.

Передача этого через 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 возможных решений:

  1. Преврати заголовочные файлы в LLVM IR / bitcode, который затем может получить сигнатуру типа каждой функции
  2. использование libclang чтобы проанализировать заголовки, затем запросить типы из результирующего AST (мой «последний вариант» в случае, если нет достаточного ответа на этот вопрос)

TL; DR

Мне необходимо возьмите файл заголовка C (такой как выше foo1.h) и, не изменяя его, сгенерируйте вышеупомянутый ожидаемый LLVM IR, используя Clang, OR, найдите другой способ получить сигнатуры функций из заголовочных файлов C (желательно с использованием libclang или построение парсера C)

21

Решение

Возможно, менее изящное решение, но оставаясь с идеей doThings функция, которая заставляет компилятор испускать IR, потому что используются определения:

Две проблемы, которые вы отождествляете с этим подходом, заключаются в том, что он требует изменения заголовка и что ему требуется более глубокое понимание типов, используемых для генерации «использования» для добавления в функцию. Оба из них могут быть преодолены относительно просто:

  1. Вместо того, чтобы компилировать заголовок напрямую, #include это (или, более вероятно, его предварительно обработанная версия или несколько заголовков) из файла .c, который содержит весь код «использует». Достаточно просто:

    // foo.c
    #include "foo.h"void doThings(void) {
    ...
    }
    
  2. Вам не нужна подробная информация о типе для генерации конкретного использования имен, сопоставления структурных реализаций с параметрами и всей той сложности, которую вы имели в приведенном выше коде «использования». Вам на самом деле не нужно собирать сигнатуры функций самостоятельно.

    Все, что вам нужно, это список самих имен и отслеживать, являются ли они для функции или для типа объекта. Затем вы можете переопределить вашу функцию «использует», чтобы она выглядела следующим образом:

    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} (создание его независимо от того, что это). Это означает, что вам вообще не нужно хранить фактическую информацию о типе, только вид контекст из которого вы извлекли имя в шапке.

    (очевидно, дайте фиктивной функции и типам заполнителей расширенные уникальные имена, чтобы они не конфликтовали с кодом, который вы действительно хотите сохранить)

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


Простая, но хакерская отправная точка (которую я, вероятно, использовал бы, потому что у меня низкие стандарты: D) может быть:

  • перебрать заголовки для #include директивы, которые принимают аргумент в угловых скобках (т.е. установленный заголовок, для которого вы также не хотите создавать объявления).
  • используйте этот список, чтобы создать фиктивную включаемую папку со всеми необходимыми включенными файлами, присутствующими, но пустыми
  • предварительно обработать его в надежде, что упростит синтаксис (clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h или что-то подобное)
  • прояснить для struct или же union сопровождаемый именем, } сопровождаемый именем, или name (и используйте этот нелепо упрощенный неанализ для построения списка применений в фиктивной функции и выдачи кода для файла .c.

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

3

Другие решения


По вопросам рекламы [email protected]