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 возможных решений:

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

TL; DR

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

1 ответов


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

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

  1. вместо компиляция заголовка напрямую,#include it (или, скорее, его предварительно обработанная версия или несколько заголовков) из a .C файл, содержащий весь код" uses". Достаточно просто:

    // foo.c
    #include "foo.h"
    void doThings(void) {
        ...
    }
    
  2. вам не нужна подробная информация о типе для создания конкретных использований имен, сопоставления экземпляров структуры с параметрами и всей этой сложности, как в коде "использует" выше. на самом деле вам не нужно собирать подписи функций себя!--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.

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