首页 > 解决方案 > 用于生成目标代码的 LLVM 通行证导致分段错误

问题描述

我正在使用 LLVM C++ API 编写一个 C 子集编译器,并使用 pass 来生成目标代码。但是当源文件包含 if-else 块时,目标代码生成步骤会给出段错误。以下是我正在使用的代码

用于生成 if-else 块的编译器代码

llvm::Value *Conditional::generateCode(CodeKit &kit) {
    auto *cond = condition->generateCode(kit);
    if (cond == nullptr) {
        return nullptr;
    }
    llvm::Function *func = kit.builder.GetInsertBlock()->getParent();
    auto *ifBlock = llvm::BasicBlock::Create(kit.context, "if", func);
    auto *elseBlock = llvm::BasicBlock::Create(kit.context, "else");
    auto *mergeBlock = llvm::BasicBlock::Create(kit.context, "ifelsemerge");

    kit.builder.CreateCondBr(cond, ifBlock, elseBlock);
    kit.builder.SetInsertPoint(ifBlock);
    if (ifstmt != nullptr) {
        kit.symbolTable.enterScope();
        ifstmt->generateCode(kit);
        kit.symbolTable.exitScope();
    }
    kit.builder.CreateBr(mergeBlock);
    ifBlock = kit.builder.GetInsertBlock();

    func->getBasicBlockList().push_back(elseBlock);
    kit.builder.SetInsertPoint(elseBlock);
    if (elsestmt != nullptr) {
        kit.symbolTable.enterScope();
        elsestmt->generateCode(kit);
        kit.symbolTable.exitScope();
    }
    kit.builder.CreateBr(mergeBlock);
    elseBlock = kit.builder.GetInsertBlock();

    func->getBasicBlockList().push_back(mergeBlock);
    kit.builder.SetInsertPoint(mergeBlock);
    return nullptr;

从 llvm 模块生成目标代码的代码。

void emitCode(CodeKit &kit) {
    kit.module.print(llvm::outs(), nullptr);

    auto irFile = "output.bc";
    error_code ec;
    llvm::raw_fd_ostream irFileStream(irFile, ec, llvm::sys::fs::F_None);
    llvm::WriteBitcodeToFile(kit.module, irFileStream);
    irFileStream.flush();

    auto targetTriple = llvm::sys::getDefaultTargetTriple();
    llvm::InitializeAllTargetInfos();
    llvm::InitializeAllTargets();
    llvm::InitializeAllTargetMCs();
    llvm::InitializeAllAsmParsers();
    llvm::InitializeAllAsmPrinters();

    string error;
    auto target = llvm::TargetRegistry::lookupTarget(targetTriple, error);
    auto cpu = "generic";
    auto features = "";
    llvm::TargetOptions opt;
    auto rm = llvm::Optional<llvm::Reloc::Model>();
    auto targetMachine =
        target->createTargetMachine(targetTriple, cpu, features, opt, rm);

    kit.module.setDataLayout(targetMachine->createDataLayout());
    kit.module.setTargetTriple(targetTriple);

    auto objectFile = "output.o";
    llvm::raw_fd_ostream objectFileStream(objectFile, ec,
                                          llvm::sys::fs::OF_None);

    llvm::legacy::PassManager pass;
    auto fileType = llvm::CGFT_ObjectFile;
    targetMachine->addPassesToEmitFile(pass, objectFileStream, nullptr,
                                       fileType);
    pass.run(kit.module);
    objectFileStream.flush();
}

我目前正在测试的源文件

int factorial(int n) {
    if (n <= 0)
        return 1;
    else
        return n * factorial(n - 1);
}

生成的 IR 代码

; ModuleID = 'bootleg c compiler'
source_filename = "bootleg c compiler"

define i32 @factorial(i32 %n) {
entry:
  %n1 = alloca i32
  store i32 %n, i32* %n1
  %n2 = load i32, i32* %n1
  %"Less or Equal" = icmp sle i32 %n2, 0
  br i1 %"Less or Equal", label %if, label %else

if:                                               ; preds = %entry
  ret i32 1
  br label %ifelsemerge

else:                                             ; preds = %entry
  %n3 = load i32, i32* %n1
  %n4 = load i32, i32* %n1
  %Subtract = sub i32 %n4, 1
  %call = call i32 @factorial(i32 %Subtract)
  %Multiply = mul i32 %n3, %call
  ret i32 %Multiply
  br label %ifelsemerge

ifelsemerge:                                      ; preds = %else, %if
}

段错误的 GDB 堆栈跟踪

#0  0x00007ffff4386bc0 in llvm::Instruction::getNumSuccessors() const () from /lib/x86_64-linux-gnu/libLLVM-10.so.1
#1  0x00007ffff4fd643c in llvm::BranchProbabilityInfo::computePostDominatedByUnreachable(llvm::Function const&, llvm::PostDominatorTree*) ()
   from /lib/x86_64-linux-gnu/libLLVM-10.so.1
#2  0x00007ffff4fdb175 in llvm::BranchProbabilityInfo::calculate(llvm::Function const&, llvm::LoopInfo const&, llvm::TargetLibraryInfo const*) ()
   from /lib/x86_64-linux-gnu/libLLVM-10.so.1
#3  0x00007ffff4fdb9c4 in llvm::BranchProbabilityInfoWrapperPass::runOnFunction(llvm::Function&) () from /lib/x86_64-linux-gnu/libLLVM-10.so.1
#4  0x00007ffff43a7d76 in llvm::FPPassManager::runOnFunction(llvm::Function&) () from /lib/x86_64-linux-gnu/libLLVM-10.so.1
#5  0x00007ffff43a7ff3 in llvm::FPPassManager::runOnModule(llvm::Module&) () from /lib/x86_64-linux-gnu/libLLVM-10.so.1
#6  0x00007ffff43a84a0 in llvm::legacy::PassManagerImpl::run(llvm::Module&) () from /lib/x86_64-linux-gnu/libLLVM-10.so.1
#7  0x0000555555565b73 in emitCode (kit=...) at c.ast.cpp:88
#8  0x0000555555579957 in generateCode (ast=...) at cc.cpp:25
#9  0x0000555555579a83 in main (argc=2, argv=0x7fffffffdfe8) at cc.cpp:41

标签: c++compiler-constructionllvm

解决方案


  ret i32 1
  br label %ifelsemerge

是无效的 IR,您br必须retBasicBlock. 您可以通过在插入分支之前检查终止指令来避免这种情况,

    if (ifBlock->size() == 0 || !ifBlock->back().isTerminator()) {
        kit.builder.CreateBr(mergeBlock);
    }
    // ...
    if (elseBlock->size() == 0 || !elseBlock->back().isTerminator()) {
        kit.builder.CreateBr(mergeBlock);
    }

请注意,空块也是无效的,因此如果条件的两个分支都退出函数,您可能不应该生成mergeBlock. AFAIK,如果您没有指向空块的分支,则可以将其留在其中。


llvm::verifyFunction通常,您可以通过添加验证通行证或使用or来在前端捕获这些类型的错误llvm::verifyModule。使用生成的 IR 代码运行llc也会给出更详细的错误消息。


推荐阅读