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 @@ -541,35 +541,7 @@ llvm::MetadataAsValue::get(ctx, llvm::DIExpression::get(ctx, std::nullopt)), }); }]; - 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)); - }]; + let delayedImport = 1; let assemblyFormat = [{ qualified($varInfo) `=` $}] # argName # [{ `:` qualified(type($}] # argName # [{)) attr-dict diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td @@ -304,6 +304,9 @@ // "aliasAttrs" list contains the arguments required by the access group and // alias analysis interfaces. Derived intrinsics should append the "aliasAttrs" // to their argument list if they set one of the flags. +// If the `delayedImport` bit is set to 1, the intrinsic's `mlirBuilder` will +// be ignored and the intrinsic needs to be manually added to the delayed +// intrinsic import. class LLVM_IntrOpBase overloadedResults, list overloadedOperands, list traits, int numResults, @@ -362,6 +365,7 @@ }] # !if(!gt(requiresFastmath, 0), "moduleImport.setFastmathFlagsAttr(inst, op);", "") # !if(!gt(numResults, 0), "$res = op;", "$_op = op;"); + bit delayedImport = 0; } // Base class for LLVM intrinsic operations, should not be used directly. Places diff --git a/mlir/include/mlir/Target/LLVMIR/LLVMImportInterface.h b/mlir/include/mlir/Target/LLVMIR/LLVMImportInterface.h --- a/mlir/include/mlir/Target/LLVMIR/LLVMImportInterface.h +++ b/mlir/include/mlir/Target/LLVMIR/LLVMImportInterface.h @@ -22,6 +22,7 @@ #include "mlir/Support/LogicalResult.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/Support/FormatVariadic.h" namespace llvm { @@ -52,6 +53,14 @@ return failure(); } + /// Hook for derived dialect interfaces to implement the delayed import of + /// intrinsics into MLIR. + virtual LogicalResult + convertDelayedIntrinsic(OpBuilder &builder, llvm::CallInst *inst, + LLVM::ModuleImport &moduleImport) const { + return failure(); + } + /// Hook for derived dialect interfaces to implement the import of metadata /// into MLIR. Attaches the converted metadata kind and node to the provided /// operation. @@ -66,6 +75,14 @@ /// returns the list of supported intrinsic identifiers. virtual ArrayRef getSupportedIntrinsics() const { return {}; } + /// Hook for derived dialect interfaces to publish the supported intrinsics + /// that should be imported after all instructions of a function were + /// imported. As every LLVM IR intrinsic has a unique integer identifier, the + /// function returns the list of supported intrinsic identifiers. + virtual ArrayRef getSupportedDelayedIntrinsics() const { + return {}; + } + /// Hook for derived dialect interfaces to publish the supported metadata /// kinds. As every metadata kind has a unique integer identifier, the /// function returns the list of supported metadata identifiers. @@ -103,6 +120,10 @@ // Add a mapping for all supported intrinsic identifiers. for (unsigned id : iface.getSupportedIntrinsics()) intrinsicToDialect[id] = iface.getDialect(); + // Add a mapping for all supported intrinsic identifiers whose conversion + // should be delayed. + for (unsigned id : iface.getSupportedDelayedIntrinsics()) + delayedIntrinsicToDialect[id] = iface.getDialect(); // Add a mapping for all supported metadata kinds. for (unsigned kind : iface.getSupportedMetadata()) metadataToDialect[kind].push_back(iface.getDialect()); @@ -126,12 +147,43 @@ return iface->convertIntrinsic(builder, inst, moduleImport); } + /// Converts the LLVM intrinsic to an MLIR operation assuming all other + /// instructions in the function were already converted. Returns failure, if + /// no conversion exists. + LogicalResult + convertDelayedIntrinsic(OpBuilder &builder, llvm::CallInst *inst, + LLVM::ModuleImport &moduleImport) const { + // Lookup the dialect interface for the given intrinsic. + Dialect *dialect = delayedIntrinsicToDialect.lookup(inst->getIntrinsicID()); + if (!dialect) + return failure(); + + // Dispatch the conversion to the dialect interface. + const LLVMImportDialectInterface *iface = getInterfaceFor(dialect); + assert(iface && "expected to find a dialect interface"); + return iface->convertDelayedIntrinsic(builder, inst, moduleImport); + } + /// Returns true if the given LLVM IR intrinsic is convertible to an MLIR /// operation. bool isConvertibleIntrinsic(llvm::Intrinsic::ID id) { return intrinsicToDialect.count(id); } + /// Returns true if the given LLVM IR intrinsic's conversion should be + /// delayed. + bool isDelayedConvertibleIntrinsic(llvm::Intrinsic::ID id) { + return delayedIntrinsicToDialect.count(id); + } + + /// Returns true if `inst` is an intrinsic and its conversion should be + /// delayed. + bool isDelayedConvertibleIntrinsic(const llvm::Instruction *inst) { + auto *intrinsic = dyn_cast(inst); + return intrinsic && + isDelayedConvertibleIntrinsic(intrinsic->getIntrinsicID()); + } + /// Attaches the given LLVM metadata to the imported operation if a conversion /// to one or more MLIR dialect attributes exists and succeeds. Returns /// success if at least one of the conversions is successful and failure if @@ -166,6 +218,7 @@ private: DenseMap intrinsicToDialect; + DenseMap delayedIntrinsicToDialect; DenseMap> metadataToDialect; }; 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 @@ -19,6 +19,7 @@ #include "mlir/Target/LLVMIR/Import.h" #include "mlir/Target/LLVMIR/LLVMImportInterface.h" #include "mlir/Target/LLVMIR/TypeFromLLVM.h" +#include "llvm/ADT/SmallVector.h" namespace llvm { class BasicBlock; @@ -225,8 +226,16 @@ LogicalResult convertGlobalCtorsAndDtors(llvm::GlobalVariable *globalVar); /// Returns personality of `func` as a FlatSymbolRefAttr. FlatSymbolRefAttr getPersonalityAsAttr(llvm::Function *func); - /// Imports `bb` into `block`, which must be initially empty. - LogicalResult processBasicBlock(llvm::BasicBlock *bb, Block *block); + /// Imports `bb` into `block`, which must be initially empty. Additionally, + /// collects all intrinsic instructions, whose conversion should be delayed, + /// and adds them to `delayedIntrinsics`. + LogicalResult + processBasicBlock(llvm::BasicBlock *bb, Block *block, + SmallVector &delayedIntrinsics); + /// Converts all intrinsics in `delayedIntrinsics`. Assumes that the function + /// that these intrinsics are part of was already fully converted to MLIR. + LogicalResult + processDelayedIntrinsics(SmallVector &delayedIntrinsics); /// Converts an LLVM intrinsic to an MLIR LLVM dialect operation if an MLIR /// counterpart exists. Otherwise, returns failure. LogicalResult convertIntrinsic(llvm::CallInst *inst); diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp --- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp +++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp @@ -50,6 +50,14 @@ return convertibleIntrinsics; } +/// Returns the list of LLVM IR intrinsic identifiers that can be converted to +/// MLIR LLVM dialect after the function they are part of is converted. +static ArrayRef getSupportedDelayedIntrinsicsImpl() { + static const SmallVector convertibleDelayedIntrinsics = { + llvm::Intrinsic::dbg_declare, llvm::Intrinsic::dbg_value}; + return convertibleDelayedIntrinsics; +} + /// Converts the LLVM intrinsic to an MLIR LLVM dialect operation if a /// conversion exits. Returns failure otherwise. static LogicalResult convertIntrinsicImpl(OpBuilder &odsBuilder, @@ -68,6 +76,62 @@ return failure(); } +/// Converts the LLVM intrinsic to an MLIR LLVM dialect operation after all +/// other instructions in the function were converted. Returns failure if no +/// conversion exists. +static LogicalResult +convertDelayedIntrinsicImpl(OpBuilder &odsBuilder, llvm::CallInst *inst, + LLVM::ModuleImport &moduleImport) { + auto *dbgIntr = dyn_cast(inst); + if (!dbgIntr) + return failure(); + // 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 success(); + FailureOr argOperand = + moduleImport.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 successor block that is dominated by the terminator. + OpBuilder::InsertionGuard guard(odsBuilder); + if (Operation *op = argOperand->getDefiningOp(); + op && op->hasTrait()) { + // Find a successor that has only a single predecessor, to ensure it is + // dominated by this terminator. + auto it = llvm::find_if(op->getSuccessors(), [&](Block *successor) { + return successor->getUniquePredecessor(); + }); + // If no successor is dominated, this intrinisc cannot be converted. + if (it == op->getSuccessors().end()) + return success(); + // Set insertion point before the terminator, to avoid inserting something + // before landingpads. + odsBuilder.setInsertionPoint((*it)->getTerminator()); + } else { + odsBuilder.setInsertionPointAfterValue(*argOperand); + } + Location loc = moduleImport.translateLoc(dbgIntr->getDebugLoc()); + DILocalVariableAttr localVariableAttr = + moduleImport.matchLocalVariableAttr(dbgIntr->getArgOperand(1)); + return llvm::TypeSwitch(dbgIntr) + .Case([&](llvm::DbgDeclareInst *t) { + odsBuilder.create(loc, *argOperand, + localVariableAttr); + return success(); + }) + .Case([&](llvm::DbgValueInst *dbgValue) { + odsBuilder.create(loc, *argOperand, + localVariableAttr); + return success(); + }) + .Default([](auto) { return failure(); }); +} + /// Returns the list of LLVM IR metadata kinds that are convertible to MLIR LLVM /// dialect attributes. static ArrayRef getSupportedMetadataImpl() { @@ -241,6 +305,15 @@ return convertIntrinsicImpl(builder, inst, moduleImport); } + /// Converts the LLVM intrinsic to an MLIR LLVM dialect operation, assuming + /// that all other instructions in the function containing the intrinsics were + /// already converted. Returns failure, if no conversion exists. + LogicalResult + convertDelayedIntrinsic(OpBuilder &builder, llvm::CallInst *inst, + LLVM::ModuleImport &moduleImport) const final { + return convertDelayedIntrinsicImpl(builder, inst, moduleImport); + } + /// Attaches the given LLVM metadata to the imported operation if a conversion /// to an LLVM dialect attribute exists and succeeds. Returns failure /// otherwise. @@ -271,6 +344,10 @@ return getSupportedIntrinsicsImpl(); } + ArrayRef getSupportedDelayedIntrinsics() const final { + return getSupportedDelayedIntrinsicsImpl(); + } + /// Returns the list of LLVM IR metadata kinds that are convertible to MLIR /// LLVM dialect attributes. ArrayRef getSupportedMetadata() const final { 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 @@ -1490,14 +1490,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,18 +1733,44 @@ // value once a block is translated. SetVector blocks = getTopologicallySortedBlocks(func); setConstantInsertionPointToStart(lookupBlock(blocks.front())); - for (llvm::BasicBlock *bb : blocks) { - if (failed(processBasicBlock(bb, lookupBlock(bb)))) + SmallVector delayedIntrinsics; + for (llvm::BasicBlock *bb : blocks) + if (failed(processBasicBlock(bb, lookupBlock(bb), delayedIntrinsics))) return failure(); - } + // Process the intrinsics that require a delayed conversion after everything + // else was converted. + if (failed(processDelayedIntrinsics(delayedIntrinsics))) + return failure(); + + return success(); +} + +LogicalResult ModuleImport::processDelayedIntrinsics( + SmallVector &delayedIntrinsics) { + for (llvm::CallInst *intrCall : delayedIntrinsics) { + if (failed(iface.convertDelayedIntrinsic(builder, intrCall, *this))) + return failure(); + if (Operation *op = lookupOperation(intrCall)) { + setNonDebugMetadataAttrs(intrCall, op); + } else if (emitExpensiveWarnings) { + Location loc = debugImporter->translateLoc(intrCall->getDebugLoc()); + emitWarning(loc) << "dropped intrinsic: " << diag(*intrCall); + } + } return success(); } -LogicalResult ModuleImport::processBasicBlock(llvm::BasicBlock *bb, - Block *block) { +LogicalResult ModuleImport::processBasicBlock( + llvm::BasicBlock *bb, Block *block, + SmallVector &delayedIntrinsics) { builder.setInsertionPointToStart(block); for (llvm::Instruction &inst : *bb) { + if (iface.isDelayedConvertibleIntrinsic(&inst)) { + // Collect all intrinsics whose conversion should be delayed. + delayedIntrinsics.push_back(cast(&inst)); + continue; + } if (failed(processInstruction(&inst))) return failure(); 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_subprogramgetValueAsBit("delayedImport")) + continue; if (failed(emitOneMLIRBuilder(*def, os, emitIntrCond))) return true; } @@ -566,8 +570,13 @@ static bool emitConvertibleIntrinsics(const RecordKeeper &recordKeeper, raw_ostream &os) { for (const Record *def : - recordKeeper.getAllDerivedDefinitions("LLVM_IntrOpBase")) + recordKeeper.getAllDerivedDefinitions("LLVM_IntrOpBase")) { + // Intrinsics with a a delayed import are not convertible by the normal + // intrinsic conversion, so skip them. + if (def->getValueAsBit("delayedImport")) + continue; emitOneIntrinsic(*def, os); + } return false; }