diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td @@ -542,33 +542,10 @@ }); }]; let mlirBuilder = [{ - // Drop debug intrinsics with a non-empty debug expression. - // TODO: Support debug intrinsics that evaluate a debug expression. - auto *dbgIntr = cast(inst); - if (dbgIntr->hasArgList() || dbgIntr->getExpression()->getNumElements() != 0) - return success(); - // Convert the value/address operand late since it cannot be a debug - // metadata argument list at this stage. Generating the conversion using an - // argument variable would not work here, since the builder variables are - // converted before entering the builder, which would result in an error - // when attempting to convert an argument list. - - FailureOr argOperand = moduleImport.convertMetadataValue(llvmOperands[0]); - // Drop the intrinsic when its operand could not be converted. This can - // happen for use before definition cases that are allowed for debug - // intrinsics. - // TODO: Implement a two pass solution that translates the debug intrinsics - // after the entire function as been translated. - if (failed(argOperand)) - return success(); - - // Ensure that the debug instrinsic is inserted right after its operand is - // defined. Otherwise, the operand might not necessarily dominate the - // intrinsic. - OpBuilder::InsertionGuard guard($_builder); - $_builder.setInsertionPointAfterValue(*argOperand); - $_op = $_builder.create<$_qualCppClassName>($_location, - *argOperand, $_var_attr($varInfo)); + // Add debug intrindic to the list of intrinsics that need to be converted once the + // full function was converted. + moduleImport.addDebugIntrinsic(inst); + return success(); }]; let assemblyFormat = [{ qualified($varInfo) `=` $}] # argName # diff --git a/mlir/include/mlir/Target/LLVMIR/ModuleImport.h b/mlir/include/mlir/Target/LLVMIR/ModuleImport.h --- a/mlir/include/mlir/Target/LLVMIR/ModuleImport.h +++ b/mlir/include/mlir/Target/LLVMIR/ModuleImport.h @@ -23,6 +23,7 @@ namespace llvm { class BasicBlock; class CallBase; +class DbgVariableIntrinsic; class Function; class Instruction; class Value; @@ -205,12 +206,17 @@ FailureOr> lookupAliasScopeAttrs(const llvm::MDNode *node) const; + /// Adds a debug intrinsics to the list of intrinsics that should be converted + /// after the function conversion has finished. + void addDebugIntrinsic(llvm::CallInst *intrinsic); + private: /// Clears the block and value mapping before processing a new region. void clearBlockAndValueMapping() { valueMapping.clear(); noResultOpMapping.clear(); blockMapping.clear(); + debugIntrinsics.clear(); } /// Sets the constant insertion point to the start of the given block. void setConstantInsertionPointToStart(Block *block) { @@ -227,6 +233,13 @@ FlatSymbolRefAttr getPersonalityAsAttr(llvm::Function *func); /// Imports `bb` into `block`, which must be initially empty. LogicalResult processBasicBlock(llvm::BasicBlock *bb, Block *block); + /// Converts all debug intrinsics in `debugIntrinsics`. Assumes that the + /// function that these intrinsics are part of was already fully converted to + /// MLIR. + LogicalResult processDebugIntrinsics(); + /// Converts a single debug intrinsic. + LogicalResult processDebugIntrinsic(llvm::DbgVariableIntrinsic *dbgIntr, + DominanceInfo &domInfo); /// Converts an LLVM intrinsic to an MLIR LLVM dialect operation if an MLIR /// counterpart exists. Otherwise, returns failure. LogicalResult convertIntrinsic(llvm::CallInst *inst); @@ -339,6 +352,9 @@ /// operations for all operations that return no result. All operations that /// return a result have a valueMapping entry instead. DenseMap noResultOpMapping; + /// Function-local list of debug intrinsics that need to be imported after the + /// function conversion has finished. + SetVector debugIntrinsics; /// Mapping between LLVM alias scope and domain metadata nodes and /// attributes in the LLVM dialect corresponding to these nodes. DenseMap aliasScopeMapping; diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp --- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp +++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp @@ -29,6 +29,7 @@ #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringSet.h" +#include "llvm/ADT/TypeSwitch.h" #include "llvm/IR/Comdat.h" #include "llvm/IR/Constants.h" #include "llvm/IR/InlineAsm.h" @@ -482,6 +483,10 @@ return aliasScopes; } +void ModuleImport::addDebugIntrinsic(llvm::CallInst *intrinsic) { + debugIntrinsics.insert(intrinsic); +} + LogicalResult ModuleImport::convertMetadata() { OpBuilder::InsertionGuard guard(builder); builder.setInsertionPointToEnd(mlirModule.getBody()); @@ -1490,14 +1495,11 @@ // FIXME: Support uses of SubtargetData. // FIXME: Add support for call / operand attributes. // FIXME: Add support for the indirectbr, cleanupret, catchret, catchswitch, - // callbr, vaarg, landingpad, catchpad, cleanuppad instructions. + // callbr, vaarg, catchpad, cleanuppad instructions. // Convert LLVM intrinsics calls to MLIR intrinsics. - if (auto *callInst = dyn_cast(inst)) { - llvm::Function *callee = callInst->getCalledFunction(); - if (callee && callee->isIntrinsic()) - return convertIntrinsic(callInst); - } + if (auto *intrinsic = dyn_cast(inst)) + return convertIntrinsic(intrinsic); // Convert all remaining LLVM instructions to MLIR operations. return convertInstruction(inst); @@ -1736,11 +1738,78 @@ // value once a block is translated. SetVector blocks = getTopologicallySortedBlocks(func); setConstantInsertionPointToStart(lookupBlock(blocks.front())); - for (llvm::BasicBlock *bb : blocks) { + for (llvm::BasicBlock *bb : blocks) if (failed(processBasicBlock(bb, lookupBlock(bb)))) return failure(); + + // Process the debug intrinsics that require a delayed conversion after + // everything else was converted. + if (failed(processDebugIntrinsics())) + return failure(); + + return success(); +} + +LogicalResult +ModuleImport::processDebugIntrinsic(llvm::DbgVariableIntrinsic *dbgIntr, + DominanceInfo &domInfo) { + Location loc = translateLoc(dbgIntr->getDebugLoc()); + auto emitUnsupportedWarning = [&]() { + if (emitExpensiveWarnings) + emitWarning(loc) << "dropped intrinsic: " << diag(*dbgIntr); + return success(); + }; + // Drop debug intrinsics with a non-empty debug expression. + // TODO: Support debug intrinsics that evaluate a debug expression. + if (dbgIntr->hasArgList() || dbgIntr->getExpression()->getNumElements() != 0) + return emitUnsupportedWarning(); + FailureOr argOperand = convertMetadataValue(dbgIntr->getArgOperand(0)); + if (failed(argOperand)) + return failure(); + + // Ensure that the debug instrinsic is inserted right after its operand is + // defined. Otherwise, the operand might not necessarily dominate the + // intrinsic. If the defining operation is a terminator, insert the intrinsic + // into a dominated block. + OpBuilder::InsertionGuard guard(builder); + if (Operation *op = argOperand->getDefiningOp(); + op && op->hasTrait()) { + // Find a dominated block that can hold the debug intrinsic. + auto dominatedBlocks = domInfo.getNode(op->getBlock())->children(); + // If no block is dominated by the terminator, this intrinisc cannot be + // converted. + if (dominatedBlocks.empty()) + return emitUnsupportedWarning(); + // Set insertion point before the terminator, to avoid inserting something + // before landingpads. + Block *dominatedBlock = (*dominatedBlocks.begin())->getBlock(); + builder.setInsertionPoint(dominatedBlock->getTerminator()); + } else { + builder.setInsertionPointAfterValue(*argOperand); } + DILocalVariableAttr localVariableAttr = + matchLocalVariableAttr(dbgIntr->getArgOperand(1)); + Operation *op = + llvm::TypeSwitch(dbgIntr) + .Case([&](llvm::DbgDeclareInst *) { + return builder.create(loc, *argOperand, + localVariableAttr); + }) + .Case([&](llvm::DbgValueInst *) { + return builder.create(loc, *argOperand, + localVariableAttr); + }); + mapNoResultOp(dbgIntr, op); + return success(); +} +LogicalResult ModuleImport::processDebugIntrinsics() { + DominanceInfo domInfo; + for (llvm::Instruction *inst : debugIntrinsics) { + auto *intrCall = cast(inst); + if (failed(processDebugIntrinsic(intrCall, domInfo))) + return failure(); + } return success(); } @@ -1751,6 +1820,11 @@ if (failed(processInstruction(&inst))) return failure(); + // Skip additional processing when the instructions is a debug intrinsics + // that was not yet converted. + if (debugIntrinsics.contains(&inst)) + continue; + // Set the non-debug metadata attributes on the imported operation and emit // a warning if an instruction other than a phi instruction is dropped // during the import. diff --git a/mlir/test/Target/LLVMIR/Import/debug-info.ll b/mlir/test/Target/LLVMIR/Import/debug-info.ll --- a/mlir/test/Target/LLVMIR/Import/debug-info.ll +++ b/mlir/test/Target/LLVMIR/Import/debug-info.ll @@ -353,6 +353,8 @@ ; // ----- define void @dbg_use_before_def(ptr %arg) { + ; CHECK: llvm.getelementptr + ; CHECK-NEXT: llvm.intr.dbg.value call void @llvm.dbg.value(metadata ptr %dbg_arg, metadata !7, metadata !DIExpression()), !dbg !9 %dbg_arg = getelementptr double, ptr %arg, i64 16 ret void @@ -409,6 +411,67 @@ ; // ----- +declare i64 @callee() +declare i32 @personality(...) + +; CHECK-LABEL: @dbg_broken_dominance_invoke +define void @dbg_broken_dominance_invoke() personality ptr @personality { + %1 = invoke i64 @callee() + to label %b1 unwind label %b2 +b1: +; CHECK: llvm.intr.dbg.value + call void @llvm.dbg.value(metadata i64 %1, metadata !7, metadata !DIExpression()), !dbg !9 + ret void +b2: + %2 = landingpad { ptr, i32 } + cleanup + ret void +} + +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!1} +!llvm.module.flags = !{!0} +!0 = !{i32 2, !"Debug Info Version", i32 3} +!1 = distinct !DICompileUnit(language: DW_LANG_C, file: !2) +!2 = !DIFile(filename: "debug-info.ll", directory: "/") +!7 = !DILocalVariable(scope: !8, name: "var", file: !2); +!8 = distinct !DISubprogram(name: "dbg_broken_dominance_invoke", scope: !2, file: !2, spFlags: DISPFlagDefinition, unit: !1) +!9 = !DILocation(line: 1, column: 2, scope: !8) + +; // ----- + +declare i64 @callee() +declare i32 @personality(...) + +; CHECK-LABEL: @dbg_broken_dominance_invoke_reordered +define void @dbg_broken_dominance_invoke_reordered() personality ptr @personality { + %1 = invoke i64 @callee() + to label %b2 unwind label %b1 +b1: +; CHECK: landingpad +; CHECK: llvm.intr.dbg.value + %2 = landingpad { ptr, i32 } + cleanup + call void @llvm.dbg.value(metadata i64 %1, metadata !7, metadata !DIExpression()), !dbg !9 + ret void +b2: + ret void +} + +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!1} +!llvm.module.flags = !{!0} +!0 = !{i32 2, !"Debug Info Version", i32 3} +!1 = distinct !DICompileUnit(language: DW_LANG_C, file: !2) +!2 = !DIFile(filename: "debug-info.ll", directory: "/") +!7 = !DILocalVariable(scope: !8, name: "var", file: !2); +!8 = distinct !DISubprogram(name: "dbg_broken_dominance_invoke", scope: !2, file: !2, spFlags: DISPFlagDefinition, unit: !1) +!9 = !DILocation(line: 1, column: 2, scope: !8) + +; // ----- + ; CHECK-DAG: #[[NAMESPACE:.+]] = #llvm.di_namespace ; CHECK-DAG: #[[SUBPROGRAM:.+]] = #llvm.di_subprogram