Use LLVM verifier in lowering (#5733)

Suggested by zygoloid while looking at #5678 

```
CHECK failure at toolchain/lower/context.cpp:62: !llvm::verifyModule(*llvm_module_, &errs): Verifier errors: Instruction does not dominate all uses!
  %.loc17_46.1.temp = alloca { i1, i32, i32 }, align 8, !dbg !13
  %tuple.elem0.loc17_46.2.tuple.elem = getelementptr inbounds nuw { i1, i32, i32 }, ptr %.loc17_46.1.temp, i32 0, i32 0, !dbg !13
Instruction does not dominate all uses!
  %.loc17_46.1.temp = alloca { i1, i32, i32 }, align 8, !dbg !13
  %tuple.elem1.loc17_46.2.tuple.elem = getelementptr inbounds nuw { i1, i32, i32 }, ptr %.loc17_46.1.temp, i32 0, i32 1, !dbg !13
Instruction does not dominate all uses!
  %.loc17_46.1.temp = alloca { i1, i32, i32 }, align 8, !dbg !13
  %tuple.elem2.loc17_46.2.tuple.elem = getelementptr inbounds nuw { i1, i32, i32 }, ptr %.loc17_46.1.temp, i32 0, i32 2, !dbg !13
```

Adds a `--llvm-verifier` flag to be able to turn this off easily,
particularly for debugging the LLVM IR.

The call workaround is due to a verifier requirement `inlinable function
call in a function with debug info must have a !dbg location`. It
specifically comes up for the `++x` case, with `%1 = call i32
@"_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4"(i32
%other)`. I think #5397 is in the direction of a fix for that, but #5397
was set aside because it puts the debug info in too many places.
Instead, address this by adding a stub location for calls that don't
have a good location. I'm deliberately putting this next to the TODO so
that it's easier to understand the association.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
This commit is contained in:
Jon Ross-Perkins
2025-06-26 21:34:09 +00:00
committed by GitHub
co-authored by Dana Jansens
parent fcf445b517
commit c3b0c2e425
9 changed files with 123 additions and 9 deletions
+15 -2
View File
@@ -294,6 +294,17 @@ Whether to emit DWARF debug information.
arg_b.Default(true);
arg_b.Set(&include_debug_info);
});
b.AddFlag(
{
.name = "verify-llvm-ir",
.help = R"""(
Whether to run the LLVM verifier on modules.
)""",
},
[&](auto& arg_b) {
arg_b.Default(true);
arg_b.Set(&run_llvm_verifier);
});
}
static constexpr CommandLine::CommandInfo SubcommandInfo = {
@@ -712,8 +723,10 @@ auto CompilationUnit::RunLower() -> void {
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> subtrees =
cache_->tree_and_subtrees_getters();
module_ = Lower::LowerToLLVM(
*llvm_context_, driver_env_->fs, options_->include_debug_info, subtrees,
input_filename_, *sem_ir_, &inst_namer, vlog_stream_);
*llvm_context_, driver_env_->fs,
options_->run_llvm_verifier ? driver_env_->error_stream : nullptr,
options_->include_debug_info, subtrees, input_filename_, *sem_ir_,
&inst_namer, vlog_stream_);
});
if (vlog_stream_) {
CARBON_VLOG("*** llvm::Module ***\n");
+1
View File
@@ -65,6 +65,7 @@ struct CompileOptions {
bool builtin_sem_ir = false;
bool prelude_import = false;
bool include_debug_info = true;
bool run_llvm_verifier = true;
llvm::SmallVector<llvm::StringRef> exclude_dump_file_prefixes;
};
+9 -1
View File
@@ -6,7 +6,9 @@
#include "common/check.h"
#include "common/growing_range.h"
#include "common/raw_string_ostream.h"
#include "common/vlog.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
#include "toolchain/lower/file_context.h"
#include "toolchain/sem_ir/inst_namer.h"
@@ -49,11 +51,17 @@ auto Context::LowerPendingDefinitions() -> void {
}
}
auto Context::Finalize() && -> std::unique_ptr<llvm::Module> {
auto Context::Finalize(
llvm::raw_ostream*
llvm_verifier_stream) && -> std::unique_ptr<llvm::Module> {
LowerPendingDefinitions();
file_contexts_.ForEach(
[](auto, auto& file_context) { file_context->Finalize(); });
if (llvm_verifier_stream) {
CARBON_CHECK(!llvm::verifyModule(*llvm_module_, llvm_verifier_stream));
}
return std::move(llvm_module_);
}
+4 -2
View File
@@ -62,8 +62,10 @@ class Context {
}
// Finishes lowering and takes ownership of the LLVM module. The context
// cannot be used further after calling this.
auto Finalize() && -> std::unique_ptr<llvm::Module>;
// cannot be used further after calling this. If `llvm_verifier_stream` is
// non-null, verifies the LLVM module before returning it.
auto Finalize(llvm::raw_ostream*
llvm_verifier_stream) && -> std::unique_ptr<llvm::Module>;
// Returns location information for use with DebugInfo.
auto GetLocForDI(SemIR::AbsoluteNodeId abs_node_id) -> LocForDI;
+6
View File
@@ -255,6 +255,12 @@ auto FunctionContext::GetDebugLoc(SemIR::InstId inst_id) -> llvm::DebugLoc {
// duplicated from the original signature.
//
// TODO: Handle this case better.
if (sem_ir().insts().Is<SemIR::Call>(inst_id)) {
// Return a stub location for calls, because they may be inlineable (an
// LLVM verifier issue).
return llvm::DILocation::get(builder_.getContext(), -1, -1,
di_subprogram_);
}
return llvm::DebugLoc();
}
return llvm::DILocation::get(builder_.getContext(), loc.line_number,
+3 -2
View File
@@ -14,7 +14,8 @@ namespace Carbon::Lower {
auto LowerToLLVM(
llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, bool want_debug_info,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
llvm::raw_ostream* llvm_verifier_stream, bool want_debug_info,
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
llvm::StringRef module_name, const SemIR::File& sem_ir,
const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream)
@@ -22,7 +23,7 @@ auto LowerToLLVM(
Context context(llvm_context, std::move(fs), want_debug_info,
tree_and_subtrees_getters, module_name, vlog_stream);
context.GetFileContext(&sem_ir, inst_namer).LowerDefinitions();
return std::move(context).Finalize();
return std::move(context).Finalize(llvm_verifier_stream);
}
} // namespace Carbon::Lower
+5 -1
View File
@@ -15,9 +15,13 @@
namespace Carbon::Lower {
// Lowers SemIR to LLVM IR.
//
// `llvm_verifier_stream` should be non-null when verification is desired.
// TODO: Switch to a struct for parameters.
auto LowerToLLVM(
llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, bool want_debug_info,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
llvm::raw_ostream* llvm_verifier_stream, bool want_debug_info,
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
llvm::StringRef module_name, const SemIR::File& sem_ir,
const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream)
+2 -1
View File
@@ -209,7 +209,7 @@ fn Test(a: A) -> C {
// CHECK:STDOUT: %.loc9_19.2.temp = alloca { i32 }, align 8, !dbg !11
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 4, ptr %.loc9_19.1.temp), !dbg !11
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 4, ptr %.2.temp)
// CHECK:STDOUT: call void @"_CConvert.A.Main:ImplicitAs.Core"(ptr %.2.temp, ptr %a)
// CHECK:STDOUT: call void @"_CConvert.A.Main:ImplicitAs.Core"(ptr %.2.temp, ptr %a), !dbg !12
// CHECK:STDOUT: call void @"_CF.X.Main:I.Main"(ptr %.loc9_19.1.temp, ptr %.2.temp), !dbg !11
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 4, ptr %.loc9_19.2.temp), !dbg !11
// CHECK:STDOUT: call void @"_CConvert.B.Main:ImplicitAs.Core"(ptr %.loc9_19.2.temp, ptr %.loc9_19.1.temp), !dbg !11
@@ -243,6 +243,7 @@ fn Test(a: A) -> C {
// CHECK:STDOUT: !9 = !DILocation(line: 9, column: 21, scope: !4)
// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "F", linkageName: "_CF:thunk.X.Main:I.Main", scope: null, file: !3, line: 9, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !11 = !DILocation(line: 9, column: 3, scope: !10)
// CHECK:STDOUT: !12 = !DILocation(line: 4294967295, scope: !10)
// CHECK:STDOUT: ; ModuleID = 'call_thunk_for_imported_interface.carbon'
// CHECK:STDOUT: source_filename = "call_thunk_for_imported_interface.carbon"
// CHECK:STDOUT:
+78
View File
@@ -0,0 +1,78 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/full.carbon
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/operators/increment.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/operators/increment.carbon
// --- increment.carbon
library "[[@TEST_NAME]]";
fn IncrSigned() {
var from: i32 = 0;
++from;
}
// CHECK:STDOUT: ; ModuleID = 'increment.carbon'
// CHECK:STDOUT: source_filename = "increment.carbon"
// CHECK:STDOUT:
// CHECK:STDOUT: define void @_CIncrSigned.Main() !dbg !4 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: %from.var = alloca i32, align 4, !dbg !7
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(i64 4, ptr %from.var), !dbg !7
// CHECK:STDOUT: store i32 0, ptr %from.var, align 4, !dbg !7
// CHECK:STDOUT: call void @"_COp.Int.Core:Inc.Core.be1e879c1ad406d8"(ptr %from.var), !dbg !8
// CHECK:STDOUT: ret void, !dbg !9
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
// CHECK:STDOUT:
// CHECK:STDOUT: define linkonce_odr void @"_COp.Int.Core:Inc.Core.be1e879c1ad406d8"(ptr %self) !dbg !10 {
// CHECK:STDOUT: call void @"_COp:thunk.Int.Core:AddAssignWith.Core.25a9a5e901f5b032"(ptr %self, i32 1), !dbg !12
// CHECK:STDOUT: ret void, !dbg !13
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define linkonce_odr void @"_COp:thunk.Int.Core:AddAssignWith.Core.25a9a5e901f5b032"(ptr %self, i32 %other) !dbg !14 {
// CHECK:STDOUT: %1 = call i32 @"_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4"(i32 %other), !dbg !15
// CHECK:STDOUT: %2 = load i32, ptr %self, align 4, !dbg !16
// CHECK:STDOUT: %3 = add i32 %2, %1, !dbg !16
// CHECK:STDOUT: store i32 %3, ptr %self, align 4, !dbg !16
// CHECK:STDOUT: ret void, !dbg !16
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: define linkonce_odr i32 @"_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4"(i32 %self) !dbg !17 {
// CHECK:STDOUT: ret i32 %self, !dbg !19
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
// CHECK:STDOUT:
// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
// CHECK:STDOUT:
// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
// CHECK:STDOUT: !3 = !DIFile(filename: "increment.carbon", directory: "")
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "IncrSigned", linkageName: "_CIncrSigned.Main", scope: null, file: !3, line: 4, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
// CHECK:STDOUT: !6 = !{}
// CHECK:STDOUT: !7 = !DILocation(line: 5, column: 3, scope: !4)
// CHECK:STDOUT: !8 = !DILocation(line: 6, column: 3, scope: !4)
// CHECK:STDOUT: !9 = !DILocation(line: 4, column: 1, scope: !4)
// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "Op", linkageName: "_COp.Int.Core:Inc.Core.be1e879c1ad406d8", scope: null, file: !11, line: 331, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !11 = !DIFile(filename: "{{.*}}/prelude/types/int.carbon", directory: "")
// CHECK:STDOUT: !12 = !DILocation(line: 333, column: 5, scope: !10)
// CHECK:STDOUT: !13 = !DILocation(line: 331, column: 3, scope: !10)
// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "Op", linkageName: "_COp:thunk.Int.Core:AddAssignWith.Core.25a9a5e901f5b032", scope: null, file: !11, line: 267, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !15 = !DILocation(line: 4294967295, scope: !14)
// CHECK:STDOUT: !16 = !DILocation(line: 267, column: 3, scope: !14)
// CHECK:STDOUT: !17 = distinct !DISubprogram(name: "Convert", linkageName: "_CConvert.8b3d5d6a6c17be04:ImplicitAs.Core.b88d1103f417c6d4", scope: null, file: !18, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !18 = !DIFile(filename: "{{.*}}/prelude/operators/as.carbon", directory: "")
// CHECK:STDOUT: !19 = !DILocation(line: 18, column: 38, scope: !17)