llvm::BasicBlock::isLandingPad 未按预期运行
llvm::BasicBlock::isLandingPad not behaving as expected
我对 LLVM 中 BasicBlock
上的 isLandingPad
有点困惑。我有以下代码,我在其中创建一个空的 BasicBlock,然后对其调用 isLandingPad
:
#include "llvm/IR/IRBuilder.h"
#include <assert.h>
using namespace llvm;
int main(void)
{
// Start with a LLVM context.
LLVMContext TheContext;
// Make a module.
Module *TheModule = new Module("mymod", TheContext);
// Make a function
std::vector<Type*> NoArgs = {};
Type *u32 = Type::getInt32Ty(TheContext);
FunctionType *FT = FunctionType::get(u32, NoArgs, false);
Function *F = Function::Create(FT, Function::ExternalLinkage, "main", TheModule);
// Make an empty block
IRBuilder<> Builder(TheContext);
BasicBlock *BB = BasicBlock::Create(TheContext, "entry", F);
Builder.SetInsertPoint(BB);
auto fnp = BB->getFirstNonPHI();
assert(fnp == nullptr);
// I think this should crash.
auto islp = BB->isLandingPad();
printf("isLP = %d\n", islp);
// If we inline the implementation of the above call, we have the following
// (which *does* crash).
auto islp2 = isa<LandingPadInst>(BB->getFirstNonPHI());
printf("isLP2 = %d\n", islp2);
return 0;
}
输出:
isLP = 0
codegen: /usr/lib/llvm-7/include/llvm/Support/Casting.h:106: static bool llvm::isa_impl_cl<llvm::LandingPadInst, const llvm::Instruction *>::doit(const From *) [To = llvm::LandingPadInst, From = const llvm::Instruction *]: Assertion `Val && "isa<> used on a null pointer"' failed.
根据 isLandingPad
(https://llvm.org/doxygen/BasicBlock_8cpp_source.html#l00470) 的 LLVM 源,当 BasicBlock 为空时这应该会出现段错误(因为我们在 nullptr
上调用 isa
)。但是,当我 运行 这个程序时,对 isLandingPad
的调用成功并且 returns false
。有趣的是,当我内联 isLandingPad
的函数定义时(如下所示),它按预期崩溃了。
我显然在这里做错了什么,但我看不出 BB->isLandingPad()
调用与内联版本有何不同,以及为什么 isLandingPad
不会崩溃,当它应该根据来源。
如果代码 "should segfault",这似乎意味着代码在运行时调用 未定义的行为 (UB)。编译器可能会基于 UB 未出现在您的程序中的错误假设进行优化,并且此错误假设会导致您观察到的错误结果 isLP == false
。
你永远不应该调用未定义的行为并重构你的代码,永远不要调用带有可以调用 UB 的参数的函数。 (例如,在调用 isa<LandingPadInst>
或 isLandingPad
之前检查 getFirstNonPHI
的结果。
具体来说,您不应该假设 UB(例如取消引用 nullptr
或其附近的地址)具有明确定义的效果,例如 "it will segfault",因为编译器可能会重新组织您的代码(假设 UB永远不会发生)以消除您期望的效果的方式(例如,它将生成不尝试从 nullptr
加载的代码)。
内联和优化级别对生成的代码有很大影响,这就是为什么您在不同情况下会看到不同结果(无效 return 值与段错误)。
关于未定义行为的更多信息:
- Undefined, unspecified and implementation-defined behavior
- What Every C Programmer Should Know About Undefined Behavior
- https://en.cppreference.com/w/cpp/language/ub(请参阅页面底部的链接以获取更多参考)
LLVM 本身(至少在我的系统上)是在禁用断言的情况下编译的,因此不会触发断言。当您在代码中内联它时,您正在编译时启用断言,因此它确实会触发。
请注意,由于 isa<...>
是一个模板,它将被编译到它被实例化为一部分的编译单元中。在这种情况下,至少有两个:一个在 LLVM 中,另一个包含您的程序。严格来说,它们应该是相同的("one definition rule"),否则你有 UB。在这种情况下的实际结果是,从任一编译单元调用 isa<...>()
可能最终会调用另一个编译单元中实例化的版本。但是,在 isa<...>()
的情况下,调用可能是内联的,即您最终得到一个特定于实例化它的每个编译单元的 isa<...>()
版本。
我对 LLVM 中 BasicBlock
上的 isLandingPad
有点困惑。我有以下代码,我在其中创建一个空的 BasicBlock,然后对其调用 isLandingPad
:
#include "llvm/IR/IRBuilder.h"
#include <assert.h>
using namespace llvm;
int main(void)
{
// Start with a LLVM context.
LLVMContext TheContext;
// Make a module.
Module *TheModule = new Module("mymod", TheContext);
// Make a function
std::vector<Type*> NoArgs = {};
Type *u32 = Type::getInt32Ty(TheContext);
FunctionType *FT = FunctionType::get(u32, NoArgs, false);
Function *F = Function::Create(FT, Function::ExternalLinkage, "main", TheModule);
// Make an empty block
IRBuilder<> Builder(TheContext);
BasicBlock *BB = BasicBlock::Create(TheContext, "entry", F);
Builder.SetInsertPoint(BB);
auto fnp = BB->getFirstNonPHI();
assert(fnp == nullptr);
// I think this should crash.
auto islp = BB->isLandingPad();
printf("isLP = %d\n", islp);
// If we inline the implementation of the above call, we have the following
// (which *does* crash).
auto islp2 = isa<LandingPadInst>(BB->getFirstNonPHI());
printf("isLP2 = %d\n", islp2);
return 0;
}
输出:
isLP = 0
codegen: /usr/lib/llvm-7/include/llvm/Support/Casting.h:106: static bool llvm::isa_impl_cl<llvm::LandingPadInst, const llvm::Instruction *>::doit(const From *) [To = llvm::LandingPadInst, From = const llvm::Instruction *]: Assertion `Val && "isa<> used on a null pointer"' failed.
根据 isLandingPad
(https://llvm.org/doxygen/BasicBlock_8cpp_source.html#l00470) 的 LLVM 源,当 BasicBlock 为空时这应该会出现段错误(因为我们在 nullptr
上调用 isa
)。但是,当我 运行 这个程序时,对 isLandingPad
的调用成功并且 returns false
。有趣的是,当我内联 isLandingPad
的函数定义时(如下所示),它按预期崩溃了。
我显然在这里做错了什么,但我看不出 BB->isLandingPad()
调用与内联版本有何不同,以及为什么 isLandingPad
不会崩溃,当它应该根据来源。
如果代码 "should segfault",这似乎意味着代码在运行时调用 未定义的行为 (UB)。编译器可能会基于 UB 未出现在您的程序中的错误假设进行优化,并且此错误假设会导致您观察到的错误结果 isLP == false
。
你永远不应该调用未定义的行为并重构你的代码,永远不要调用带有可以调用 UB 的参数的函数。 (例如,在调用 isa<LandingPadInst>
或 isLandingPad
之前检查 getFirstNonPHI
的结果。
具体来说,您不应该假设 UB(例如取消引用 nullptr
或其附近的地址)具有明确定义的效果,例如 "it will segfault",因为编译器可能会重新组织您的代码(假设 UB永远不会发生)以消除您期望的效果的方式(例如,它将生成不尝试从 nullptr
加载的代码)。
内联和优化级别对生成的代码有很大影响,这就是为什么您在不同情况下会看到不同结果(无效 return 值与段错误)。
关于未定义行为的更多信息:
- Undefined, unspecified and implementation-defined behavior
- What Every C Programmer Should Know About Undefined Behavior
- https://en.cppreference.com/w/cpp/language/ub(请参阅页面底部的链接以获取更多参考)
LLVM 本身(至少在我的系统上)是在禁用断言的情况下编译的,因此不会触发断言。当您在代码中内联它时,您正在编译时启用断言,因此它确实会触发。
请注意,由于 isa<...>
是一个模板,它将被编译到它被实例化为一部分的编译单元中。在这种情况下,至少有两个:一个在 LLVM 中,另一个包含您的程序。严格来说,它们应该是相同的("one definition rule"),否则你有 UB。在这种情况下的实际结果是,从任一编译单元调用 isa<...>()
可能最终会调用另一个编译单元中实例化的版本。但是,在 isa<...>()
的情况下,调用可能是内联的,即您最终得到一个特定于实例化它的每个编译单元的 isa<...>()
版本。