LLVM C-API Жизненные циклы основных объектов

Я начал играть с LLVM, создавая любимый язык. Я использую C-API. У меня есть синтаксический анализатор и базовый AST, но я немного затрудняюсь с LLVM.

Ниже приведена уменьшенная версия моего кода, иллюстрирующая мою текущую проблему:

      #include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "llvm-c/Core.h"
#include "llvm-c/ExecutionEngine.h"
#include "llvm-c/Target.h"
#include "llvm-c/Analysis.h"
#include "llvm-c/BitWriter.h"

static LLVMModuleRef mod;
static LLVMBuilderRef builder;
static LLVMExecutionEngineRef engine;

typedef struct oper_t {
    const char * name;
    
    LLVMTypeRef args[2];
    LLVMTypeRef ret; 
    LLVMValueRef val;
} oper_t;

#define NUM_OPER 2
static oper_t oper[NUM_OPER] = {
    { .name = "function1" },
    { .name = "function2" },
};

void codegen_init(const char * filename)
{
    char *error;
 
    mod = LLVMModuleCreateWithName(filename);
    builder = LLVMCreateBuilder();
    
    error = NULL;
    LLVMVerifyModule(mod, LLVMAbortProcessAction, &error);
    if(error) printf("LLVM init Verify message \"%s\"\n", error);
    LLVMDisposeMessage(error);
    
    error = NULL;
    LLVMLinkInMCJIT();
    LLVMInitializeNativeTarget();
    LLVMInitializeNativeAsmPrinter();
    if (LLVMCreateExecutionEngineForModule(&engine, mod, &error) != 0)
    {
        fprintf(stderr, "LLVM failed to create execution engine\n");
        abort();
    }
    if(error) 
    {
        printf("LLVM Execution Engine message %s\n", error);
        LLVMDisposeMessage(error);
        exit(EXIT_FAILURE);
    }
}

int runOper(oper_t * o, long a, long b) 
{
    LLVMValueRef v, l, r;
    
    o->args[0] = LLVMInt32Type();
    o->args[1] = LLVMInt32Type();
    
    o->ret = LLVMFunctionType(LLVMInt32Type(), o->args, 2, 0);
    o->val = LLVMAddFunction(mod, o->name, o->ret);
    
    LLVMBasicBlockRef entry = LLVMAppendBasicBlock(o->val, "entry");
    LLVMPositionBuilderAtEnd(builder, entry);
    
    l = LLVMConstInt(LLVMInt32Type(), a, 0); 
    r = LLVMConstInt(LLVMInt32Type(), b, 0); 
    v = LLVMBuildAdd(builder, l, r, "add");
    
    LLVMBuildRet(builder, v);
    
    char *error = NULL;
    LLVMVerifyModule(mod, LLVMAbortProcessAction, &error);
    if(error) printf("LLVM func Verify message \"%s\"\n", error);
    LLVMDisposeMessage(error);
    
    LLVMGenericValueRef g = LLVMRunFunction(engine, o->val, 0, NULL);
    
    printf("LLVM func executed without crash\n");
    
    LLVMDeleteFunction(o->val);
    
    return (long)LLVMGenericValueToInt(g, 1);
}

int main(int argc, char const *argv[])
{
    long val;
    
    codegen_init("test");

    val = runOper(&oper[0], 3, 4);
    printf("3 + 4 is %ld\n", val);
    
    val = runOper(&oper[1], 6, 7);
    printf("6 + 7 is %ld\n", val);
}

Я могу скомпилировать это с помощью команды:

      gcc test.c `llvm-config --cflags --cppflags --ldflags --libs core executionengine mcjit interpreter analysis native bitwriter --system-libs` -o test.exe

Или, альтернативно, я также пробовал:

      gcc `llvm-config --cflags --cppflags` -c test.c
g++ test.o `llvm-config --cxxflags --ldflags --libs core executionengine mcjit interpreter analysis native bitwriter --system-libs` -o test.exe

В любом случае я получаю этот результат:

      $ ./test.exe
LLVM init Verify message ""
LLVM func Verify message ""
LLVM func executed without crash
3 + 4 is 7
LLVM func Verify message ""
Segmentation fault

Я также пытался использовать clang просто для хорошей меры.

Ясно, что я неправильно использую LLVM C-API. В основном я изо всех сил пытаюсь понять, когда безопасно вызывать функции API, а также когда я могу безопасно освободить/удалить память, на которую ссылается LLVM. Например, LLVMTypeRef args[2]параметр, который я вижу в исходном коде LLVM C-API для LLVMFunctionTypeчто он создает ArrayRef для параметра args. Это означает, что я должен придерживаться параметра args, пока LLVM не закончит с ним. Я не могу точно сказать, когда именно. (Планирую выделить эту память в куче)

Проще говоря, мне бы хотелось, чтобы кто-нибудь не просто объяснил, что я делаю неправильно в этом примере, но более фундаментально объяснил, как мне понять, что я делаю неправильно.

В документации LLVM C-API дается подробная разбивка функций, доступных в API, но я не нашел подробного описания того, как должны вызываться функции API, т.е. какой порядок является безопасным/ожидаемым.

Я также нашел эту документацию полезной, так как в ней можно легко найти прототипы отдельных функций. Но опять же, он не дает контекста или примеров того, как использовать C-API.

Наконец, я должен сослаться на блог Пола Смита , он немного устарел, но именно поэтому я зашел так далеко.

PS Я не ожидаю, что мне все распишут, я просто хочу посоветовать, как самостоятельно изучить LLVM

1 ответ

Базовый дизайн легче всего понять в C++: если вы передаете указатель на объект y в качестве аргумента конструктора, т.е. x=new Foo(…, y, …), то y должен жить дольше, чем x . Это также относится к оберткам, таким как CallInst::Create()и , оба из которых принимают указатели на объекты и возвращают сконструированные объекты.

Но есть еще кое-что. Некоторые объекты берут на себя владение созданными объектами, поэтому вам вообще не разрешается удалять созданные объекты. Например, вам не разрешено удалять то, что ConstantInt::get()возвращается. Как правило, все, что называется create… в C++ API, возвращает что-то, что вы можете удалить, а все, что называется get…, возвращает что-то, принадлежащее другому объекту LLVM. Я уверен, что есть исключения.

Возможно, вам будет полезно создать отладочную версию LLVM, если только вы не умнее меня. Дополнительные утверждения — это здорово.

Другие вопросы по тегам