仅当方法声明具有参数时,LLVM IR codegen 才会在退出期间出现段错误

LLVM IR codegen segfaults during exit only when method declarations have parameters

说明

我正在使用 yacc/bison、flex 和使用 LLVM C++ API 的 LLVM 工具链 (LLVM 12) 创建类 C 语言的编译器。 我一直在 Ubuntu 版本 20.04.3 LTS (Focal Fossa) 和 macOS 11.6 Big Sur 上进行开发和测试。 目前,问题是当方法声明具有方法参数时退出程序时程序段错误,例如:

func test(x int) void {}

LLVM IR 将正确打印为

; ModuleID = 'Test'
source_filename = "Test"

define void @test(i32 %x) {
entry:
  %x1 = alloca i32, align 4
  store i32 %x, i32* %x1, align 4
  ret void
}

之后会立即发生段错误。

这样的方法声明
func test() int {
    var x int;
    x = 5;
    return (x);
}

不会出现段错误。

GDB 报告段错误发生在 llvm::LLVMContextImpl::~LLVMContextImpl() 期间。 Valgrind 报告 ~LLVMContextImpl() 进行大小为 8 的无效读取。

编辑:与无效读取相关的 Valgrind 输出

==10254== Invalid read of size 8
==10254==    at 0x5553C30: llvm::LLVMContextImpl::~LLVMContextImpl() (in /usr/lib/x86_64-linux-gnu/libLLVM-12.so.1)
==10254==    by 0x5552130: llvm::LLVMContext::~LLVMContext() (in /usr/lib/x86_64-linux-gnu/libLLVM-12.so.1)
==10254==    by 0xA44AA26: __run_exit_handlers (exit.c:108)
==10254==    by 0xA44ABDF: exit (exit.c:139)
==10254==    by 0xA4280B9: (below main) (libc-start.c:342)
==10254==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==10254== 
==10254== 
==10254== Process terminating with default action of signal 11 (SIGSEGV)
==10254==  Access not within mapped region at address 0x0
==10254==    at 0x5553C30: llvm::LLVMContextImpl::~LLVMContextImpl() (in /usr/lib/x86_64-linux-gnu/libLLVM-12.so.1)
==10254==    by 0x5552130: llvm::LLVMContext::~LLVMContext() (in /usr/lib/x86_64-linux-gnu/libLLVM-12.so.1)
==10254==    by 0xA44AA26: __run_exit_handlers (exit.c:108)
==10254==    by 0xA44ABDF: exit (exit.c:139)
==10254==    by 0xA4280B9: (below main) (libc-start.c:342)
==10254==  If you believe this happened as a result of a stack
==10254==  overflow in your program's main thread (unlikely but
==10254==  possible), you can try to increase the size of the
==10254==  main thread stack using the --main-stacksize= flag.
==10254==  The main thread stack size used in this run was 8388608.

我希望通过在这里提问,我可以得到一些关于如何努力解决这个问题的提示。我已经坚持了好几天了。

源代码片段

我的代码中与方法声明和方法参数相关的部分如下,对于长度,我深表歉意:

程序的 Bison 语法规则

program: extern_list decafpackage
    { 
        ProgramAST *prog = new ProgramAST((decafStmtList*), (PackageAST*)); 
        if (printAST) {
            cout << getString(prog) << endl;
        }
        prog->Codegen();
        delete prog;
    }
    ;

方法声明的Bison语法规则

method_decl: T_FUNC T_ID T_LPAREN params T_RPAREN method_type method_block 
    {
        $$ = new Method(*, ->str(), , );
        delete ; 
        delete ;
    }

方法参数的 Bison 语法规则

param: T_ID type { $$ = new VarDef(*, ->str()); delete ; delete ; }
    ;

C++ Method::Codegen() 参数处理

llvm::Function *func = llvm::Function::Create(
            llvm::FunctionType::get(returnTy, args, false),
            llvm::Function::ExternalLinkage,
            name,
            TheModule
        );

llvm::BasicBlock *BB = llvm::BasicBlock::Create(TheContext, "entry", func);
Builder.SetInsertPoint(BB);

. . .

for (auto &Arg : func->args()) {
            llvm::AllocaInst* Alloca = CreateEntryBlockAlloca(func, Arg.getName().str());
            Builder.CreateStore(&Arg, Alloca);
            sTStack->enter_symtbl(Arg.getName().str(), Alloca);
        }

C++ VarDef::Codegen()

llvm::Value *Codegen() {
        llvm::Type* ty = getLLVMType(type);
        llvm::AllocaInst* V = Builder.CreateAlloca(ty, 0, name);
        V->setName(name);
        sTStack->enter_symtbl(name, V);
        return V;
        return nullptr;
    }

野牛主菜

int main() {
  // Setup
  llvm::LLVMContext &Context = TheContext;
  TheModule = new llvm::Module("Test", Context);
  FPM = std::make_unique<llvm::legacy::FunctionPassManager>(TheModule);
  FPM->add(llvm::createPromoteMemoryToRegisterPass());
  FPM->add(llvm::createInstructionCombiningPass());
  FPM->add(llvm::createReassociatePass());
  FPM->add(llvm::createGVNPass());
  FPM->add(llvm::createCFGSimplificationPass());
  FPM->doInitialization();

  int retval = yyparse();

  TheModule->print(llvm::errs(), nullptr);
  return(retval >= 1 ? EXIT_FAILURE : EXIT_SUCCESS);
}

解法:

问题出在未包含的代码行中。 llvm::Function::Create 需要 llvm::FunctionType 可以通过用 llvm::Type* 对象填充向量来提供。我写了一个函数来做到这一点:

void getLLVMTypes(vector<llvm::Type*>* v) {
    for (auto* i : stmts) {
        llvm::Type* type = getLLVMType(i->getType());
        ((llvm::Value*)(type))->setName(i->getName()); // Problem
        v->push_back(type);
    }
}

问题是将每个 llvm::Type* 对象转换为 llvm::Value* 并使用 llvm::Value::setName 设置其名称。我这样做是为了解决我之前遇到的未设置参数名称的问题。我不完全确定问题出在哪里,我在使用调试标志从源代码编译 LLVM 时遇到了问题,但这是一行看起来很粗糙的代码并将其删除,同时使用另一种方法来保留方法参数名称,解决了这个问题。