如何追踪 LLVM verifyFunction 错误 "Expected no forward declarations!"?
How to track down LLVM verifyFunction error "Expected no forward declarations!"?
我正在 LLVM 中为我的一种新语言开发编译器,在生成调试信息时遇到了 运行 问题。
我还没有找到很多关于如何使用 DIBuilder 实际生成调试信息的文档,所以我很可能做错了一些事情。
我主要查看 Kaleidoscope 示例,因为它是我发现的唯一一个使用调试信息的示例。我还没有打开 Clang 来查看他们是如何使用它的,但我很想听听曾经使用过它的人的意见。
我已经能够使用一些更复杂的示例编译和 运行 我的语言,但我从一些基础知识开始重新添加调试支持。这是我正在尝试编译的简单脚本:
double my_main()
{
return 0.0;
}
这是我从 verifyFunction、verifyModule 和转储模块的输出。
编辑:在下面的编辑中我指出转储是在调用最终确定之后正确删除临时文件的。
Failed To Verify Function: my_main error: Expected no forward declarations!
!8 = <temporary!> !{}
Failed To Verify Module: test.str error: Expected no forward declarations!
!8 = <temporary!> !{}
; ModuleID = 'test.str'
define double @my_main() !dbg !6 {
entry:
br label %block, !dbg !10
block: ; preds = %entry
ret double 0.000000e+00, !dbg !10
}
!llvm.module.flags = !{!0, !1}
!llvm.dbg.cu = !{!2}
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = !{i32 2, !"Dwarf Version", i32 2}
!2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "Test Compiler", isOptimized: false, runtimeVersion: 0, emissionKind: 1, enums: !4, subprograms: !5)
!3 = !DIFile(filename: "test.str", directory: ".")
!4 = !{}
!5 = !{!6}
!6 = distinct !DISubprogram(name: "my_main", scope: !3, file: !3, line: 10, type: !7, isLocal: false, isDefinition: true, scopeLine: 10, isOptimized: false, variables: !4)
!7 = !DISubroutineType(types: !8)
!8 = !{!9}
!9 = !DIBasicType(name: "double", size: 64, align: 8, encoding: DW_ATE_float)
!10 = !DILocation(line: 9, column: 11, scope: !6)
在 LLVM 代码库中搜索错误消息可在 Verifier.cpp:
中找到源代码
void Verifier::visitMDNode(const MDNode &MD) {
// Only visit each node once. Metadata can be mutually recursive, so this
// avoids infinite recursion here, as well as being an optimization.
if (!MDNodes.insert(&MD).second)
return;
switch (MD.getMetadataID()) {
default:
llvm_unreachable("Invalid MDNode subclass");
case Metadata::MDTupleKind:
break;
#define HANDLE_SPECIALIZED_MDNODE_LEAF(CLASS) \
case Metadata::CLASS##Kind: \
visit##CLASS(cast<CLASS>(MD)); \
break;
#include "llvm/IR/Metadata.def"
}
for (unsigned i = 0, e = MD.getNumOperands(); i != e; ++i) {
Metadata *Op = MD.getOperand(i);
if (!Op)
continue;
Assert(!isa<LocalAsMetadata>(Op), "Invalid operand for global metadata!",
&MD, Op);
if (auto *N = dyn_cast<MDNode>(Op)) {
visitMDNode(*N);
continue;
}
if (auto *V = dyn_cast<ValueAsMetadata>(Op)) {
visitValueAsMetadata(*V, nullptr);
continue;
}
}
// Check these last, so we diagnose problems in operands first.
Assert(!MD.isTemporary(), "Expected no forward declarations!", &MD);
Assert(MD.isResolved(), "All nodes should be resolved!", &MD);
}
我断言我有一些元数据仍然被认为是“临时的”,但我想知道如何追踪创建它的内容。
我正在像示例一样创建我的类型:
// here dbuilder is a DIBuilder* and alignment is coming from
// my Module's getDataLayout().getABITypeAlignment(t);
// where t is Type::getDoubleTy(context.getLLVMContext());
// the context object is my own type
dbuilder->createBasicType("double", 64, alignment, dwarf::DW_ATE_float);
我的调试函数创建逻辑在示例的另一个调用中使用了这种类型:
// the argument is of type: SmallVector<Metadata *, 8> returnPlusParams;
dbuilder->createSubroutineType(dbuilder->getOrCreateTypeArray(returnPlusParams));
我还在我的 IRBuilder 上从我的 AST 节点设置行号和列号:
_mBuilder->SetCurrentDebugLocation(DebugLoc::get(node->line, node->column, currentDebugScope()));
我也在阅读 SourceLevelDebugging 上的页面,但这并没有像调试 IR 格式那样谈论 C++ API 到 LLVM。
如果有人注意到我的模块转储中有明显的问题或有任何进一步的建议,我将不胜感激。
编辑:添加示例 IR
我又做了一些测试,想 post 使用以下命令从 Clang 输出类似函数的输出:
clang -cc1 hello_llvm.c -emit-llvm
编辑:我还发现 this post 可以将调试信息添加到输出中。
此代码:
double main() {
return 0.0;
}
编译为:
; ModuleID = 'hello_llvm.c'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin15.0.0"
; Function Attrs: nounwind
define double @main() #0 {
entry:
ret double 0.000000e+00
}
attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-features"="+mmx,+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = !{!"clang version 3.8.0 (http://llvm.org/git/clang.git 80803f026ba7160f7cfa122c7ef829ab42abc3bf) (http://llvm.org/git/llvm.git 1bb03c5884405c428c3ab54631c0528b6cedeb54)"}
它给出了将 main
的 return 类型更改为 int 的明显警告。我也制作了一个生成 alloca 的 int 版本:
; ModuleID = 'hello_llvm.c'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin15.0.0"
; Function Attrs: nounwind
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
store i32 0, i32* %retval, align 4
ret i32 0
}
attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-features"="+mmx,+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = !{!"clang version 3.8.0 (http://llvm.org/git/clang.git 80803f026ba7160f7cfa122c7ef829ab42abc3bf) (http://llvm.org/git/llvm.git 1bb03c5884405c428c3ab54631c0528b6cedeb54)"}
注意: 在我的示例中 double
被选为任意 return 类型,但是 int
也失败了。在我的一些初始测试中,我实际上用 main
和 argv/argc 包装了 my_main,并且能够从终端编译和 运行。
编辑 2:'finalize'
我在之前的 IR 中没有看到任何太明显的东西,所以我决定在模块 finalize
调用之后 运行 verifyModule。这是成功的,并反映在我们上面看到的 IR 转储中。
然后我决定在完成之前转储模块。这次你可以看到它抱怨的温度。
Failed To Verify Module: test.str error: Expected no forward declarations!
!8 = <temporary!> !{}
; ModuleID = 'test.str'
define i32 @my_main() !dbg !4 {
entry:
br label %block, !dbg !9
block: ; preds = %entry
ret i32 0, !dbg !9
}
!llvm.module.flags = !{!0, !1}
!llvm.dbg.cu = !{!2}
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = !{i32 2, !"Dwarf Version", i32 2}
!2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "Test Compiler", isOptimized: false, runtimeVersion: 0, emissionKind: 1)
!3 = !DIFile(filename: "test.str", directory: ".")
!4 = distinct !DISubprogram(name: "my_main", scope: !3, file: !3, line: 10, type: !5, isLocal: false, isDefinition: true, scopeLine: 10, isOptimized: false, variables: !8)
!5 = !DISubroutineType(types: !6)
!6 = !{!7}
!7 = !DIBasicType(name: "int32", size: 32, align: 4, encoding: DW_ATE_signed)
!8 = <temporary!> !{}
!9 = !DILocation(line: 9, column: 11, scope: !4)
所以我想问题是...
在验证清理此临时文件之前是否必须执行某些操作?还是我只是错误地创建了类型?哪些操作会隐式创建临时对象?
我终于开始研究 Clang 并注意到它们似乎根本没有使用 verifyFunction
并且可以选择添加 verifyModule
作为传递。
CODEGENOPT(VerifyModule , 1, 1) ///< Control whether the module should be run
///< through the LLVM Verifier.
从我所看到的情况来看,我的问题甚至可能无效,因为它最终通过了 verifyModule
。我现在在这里选择 "do as Clang does"。
我仍然愿意接受建议。
我在使用 IRBuilder 和 DIBuilder 时遇到了同样的问题。我这边的问题是我在完成 DIBuilder (llvm::DIBuilder::finalize()
) 之前调用了 llvm::ExecutionEngine::getFunctionAddress
(触发代码生成)。
因此,请确保在 llvm::Module 的任何代码生成之前调用 dbuilder->finalize()
(实际上是在调用验证模块之前)。
我正在使用 LLVM C-API 并且遇到了同样的问题。
它是通过移动 LLVMDIBuilderFinalize 解决的,因此它比任何 LLVMVerifyModule 更早执行并且优化通过。
我正在 LLVM 中为我的一种新语言开发编译器,在生成调试信息时遇到了 运行 问题。
我还没有找到很多关于如何使用 DIBuilder 实际生成调试信息的文档,所以我很可能做错了一些事情。
我主要查看 Kaleidoscope 示例,因为它是我发现的唯一一个使用调试信息的示例。我还没有打开 Clang 来查看他们是如何使用它的,但我很想听听曾经使用过它的人的意见。
我已经能够使用一些更复杂的示例编译和 运行 我的语言,但我从一些基础知识开始重新添加调试支持。这是我正在尝试编译的简单脚本:
double my_main()
{
return 0.0;
}
这是我从 verifyFunction、verifyModule 和转储模块的输出。
编辑:在下面的编辑中我指出转储是在调用最终确定之后正确删除临时文件的。
Failed To Verify Function: my_main error: Expected no forward declarations!
!8 = <temporary!> !{}
Failed To Verify Module: test.str error: Expected no forward declarations!
!8 = <temporary!> !{}
; ModuleID = 'test.str'
define double @my_main() !dbg !6 {
entry:
br label %block, !dbg !10
block: ; preds = %entry
ret double 0.000000e+00, !dbg !10
}
!llvm.module.flags = !{!0, !1}
!llvm.dbg.cu = !{!2}
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = !{i32 2, !"Dwarf Version", i32 2}
!2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "Test Compiler", isOptimized: false, runtimeVersion: 0, emissionKind: 1, enums: !4, subprograms: !5)
!3 = !DIFile(filename: "test.str", directory: ".")
!4 = !{}
!5 = !{!6}
!6 = distinct !DISubprogram(name: "my_main", scope: !3, file: !3, line: 10, type: !7, isLocal: false, isDefinition: true, scopeLine: 10, isOptimized: false, variables: !4)
!7 = !DISubroutineType(types: !8)
!8 = !{!9}
!9 = !DIBasicType(name: "double", size: 64, align: 8, encoding: DW_ATE_float)
!10 = !DILocation(line: 9, column: 11, scope: !6)
在 LLVM 代码库中搜索错误消息可在 Verifier.cpp:
中找到源代码void Verifier::visitMDNode(const MDNode &MD) { // Only visit each node once. Metadata can be mutually recursive, so this // avoids infinite recursion here, as well as being an optimization. if (!MDNodes.insert(&MD).second) return; switch (MD.getMetadataID()) { default: llvm_unreachable("Invalid MDNode subclass"); case Metadata::MDTupleKind: break; #define HANDLE_SPECIALIZED_MDNODE_LEAF(CLASS) \ case Metadata::CLASS##Kind: \ visit##CLASS(cast<CLASS>(MD)); \ break; #include "llvm/IR/Metadata.def" } for (unsigned i = 0, e = MD.getNumOperands(); i != e; ++i) { Metadata *Op = MD.getOperand(i); if (!Op) continue; Assert(!isa<LocalAsMetadata>(Op), "Invalid operand for global metadata!", &MD, Op); if (auto *N = dyn_cast<MDNode>(Op)) { visitMDNode(*N); continue; } if (auto *V = dyn_cast<ValueAsMetadata>(Op)) { visitValueAsMetadata(*V, nullptr); continue; } } // Check these last, so we diagnose problems in operands first. Assert(!MD.isTemporary(), "Expected no forward declarations!", &MD); Assert(MD.isResolved(), "All nodes should be resolved!", &MD); }
我断言我有一些元数据仍然被认为是“临时的”,但我想知道如何追踪创建它的内容。
我正在像示例一样创建我的类型:
// here dbuilder is a DIBuilder* and alignment is coming from
// my Module's getDataLayout().getABITypeAlignment(t);
// where t is Type::getDoubleTy(context.getLLVMContext());
// the context object is my own type
dbuilder->createBasicType("double", 64, alignment, dwarf::DW_ATE_float);
我的调试函数创建逻辑在示例的另一个调用中使用了这种类型:
// the argument is of type: SmallVector<Metadata *, 8> returnPlusParams;
dbuilder->createSubroutineType(dbuilder->getOrCreateTypeArray(returnPlusParams));
我还在我的 IRBuilder 上从我的 AST 节点设置行号和列号:
_mBuilder->SetCurrentDebugLocation(DebugLoc::get(node->line, node->column, currentDebugScope()));
我也在阅读 SourceLevelDebugging 上的页面,但这并没有像调试 IR 格式那样谈论 C++ API 到 LLVM。
如果有人注意到我的模块转储中有明显的问题或有任何进一步的建议,我将不胜感激。
编辑:添加示例 IR
我又做了一些测试,想 post 使用以下命令从 Clang 输出类似函数的输出:
clang -cc1 hello_llvm.c -emit-llvm
编辑:我还发现 this post 可以将调试信息添加到输出中。
此代码:
double main() {
return 0.0;
}
编译为:
; ModuleID = 'hello_llvm.c'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin15.0.0"
; Function Attrs: nounwind
define double @main() #0 {
entry:
ret double 0.000000e+00
}
attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-features"="+mmx,+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = !{!"clang version 3.8.0 (http://llvm.org/git/clang.git 80803f026ba7160f7cfa122c7ef829ab42abc3bf) (http://llvm.org/git/llvm.git 1bb03c5884405c428c3ab54631c0528b6cedeb54)"}
它给出了将 main
的 return 类型更改为 int 的明显警告。我也制作了一个生成 alloca 的 int 版本:
; ModuleID = 'hello_llvm.c'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin15.0.0"
; Function Attrs: nounwind
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
store i32 0, i32* %retval, align 4
ret i32 0
}
attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-features"="+mmx,+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = !{!"clang version 3.8.0 (http://llvm.org/git/clang.git 80803f026ba7160f7cfa122c7ef829ab42abc3bf) (http://llvm.org/git/llvm.git 1bb03c5884405c428c3ab54631c0528b6cedeb54)"}
注意: 在我的示例中 double
被选为任意 return 类型,但是 int
也失败了。在我的一些初始测试中,我实际上用 main
和 argv/argc 包装了 my_main,并且能够从终端编译和 运行。
编辑 2:'finalize'
我在之前的 IR 中没有看到任何太明显的东西,所以我决定在模块 finalize
调用之后 运行 verifyModule。这是成功的,并反映在我们上面看到的 IR 转储中。
然后我决定在完成之前转储模块。这次你可以看到它抱怨的温度。
Failed To Verify Module: test.str error: Expected no forward declarations!
!8 = <temporary!> !{}
; ModuleID = 'test.str'
define i32 @my_main() !dbg !4 {
entry:
br label %block, !dbg !9
block: ; preds = %entry
ret i32 0, !dbg !9
}
!llvm.module.flags = !{!0, !1}
!llvm.dbg.cu = !{!2}
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = !{i32 2, !"Dwarf Version", i32 2}
!2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "Test Compiler", isOptimized: false, runtimeVersion: 0, emissionKind: 1)
!3 = !DIFile(filename: "test.str", directory: ".")
!4 = distinct !DISubprogram(name: "my_main", scope: !3, file: !3, line: 10, type: !5, isLocal: false, isDefinition: true, scopeLine: 10, isOptimized: false, variables: !8)
!5 = !DISubroutineType(types: !6)
!6 = !{!7}
!7 = !DIBasicType(name: "int32", size: 32, align: 4, encoding: DW_ATE_signed)
!8 = <temporary!> !{}
!9 = !DILocation(line: 9, column: 11, scope: !4)
所以我想问题是...
在验证清理此临时文件之前是否必须执行某些操作?还是我只是错误地创建了类型?哪些操作会隐式创建临时对象?
我终于开始研究 Clang 并注意到它们似乎根本没有使用 verifyFunction
并且可以选择添加 verifyModule
作为传递。
CODEGENOPT(VerifyModule , 1, 1) ///< Control whether the module should be run
///< through the LLVM Verifier.
从我所看到的情况来看,我的问题甚至可能无效,因为它最终通过了 verifyModule
。我现在在这里选择 "do as Clang does"。
我仍然愿意接受建议。
我在使用 IRBuilder 和 DIBuilder 时遇到了同样的问题。我这边的问题是我在完成 DIBuilder (llvm::DIBuilder::finalize()
) 之前调用了 llvm::ExecutionEngine::getFunctionAddress
(触发代码生成)。
因此,请确保在 llvm::Module 的任何代码生成之前调用 dbuilder->finalize()
(实际上是在调用验证模块之前)。
我正在使用 LLVM C-API 并且遇到了同样的问题。
它是通过移动 LLVMDIBuilderFinalize 解决的,因此它比任何 LLVMVerifyModule 更早执行并且优化通过。