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 @@ -49,4 +49,11 @@ string llvmBuilder = ""; } +// Base class for LLVM operations. All operations get an "llvm." prefix in +// their name automatically. LLVM operations have either zero or one result, +// this class is specialized below for both cases and should not be used +// directly. +class LLVM_Op traits = []> : + LLVM_OpBase; + #endif // LLVMIR_OP_BASE diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td @@ -15,14 +15,6 @@ include "mlir/Dialect/LLVMIR/LLVMOpBase.td" -// Base class for LLVM operations. All operations get an "llvm." prefix in -// their name automatically. LLVM operations have either zero or one result, -// this class is specialized below for both cases and should not be used -// directly. -class LLVM_Op traits = []> : - LLVM_OpBase { -} - class LLVM_Builder { string llvmBuilder = builder; } diff --git a/mlir/test/mlir-tblgen/llvm-intrinsics.td b/mlir/test/mlir-tblgen/llvm-intrinsics.td new file mode 100644 --- /dev/null +++ b/mlir/test/mlir-tblgen/llvm-intrinsics.td @@ -0,0 +1,32 @@ +// This checks that we can consume LLVM's Intrinsic definitions from TableGen +// files and produce ODS. Unlike MLIR, LLVM's main Intrinsics.td file that +// contains the definition of the Intrinsic class also includes files for +// platform-specific intrinsics, so we need to give it to TableGen instead of +// writing a local test source. We filter out platform-specific intrinsic +// includes from the main file to avoid unnecessary dependencies and decrease +// the test cost. The command-line flags further ensure a specific intrinsic is +// processed and we only check the ouptut below. +// +// RUN: cat %S/../../../llvm/include/llvm/IR/Intrinsics.td \ +// RUN: | grep -v "llvm/IR/Intrinsics" \ +// RUN: | mlir-tblgen -gen-llvmir-intrinsics -I %S/../../../llvm/include/ --llvmir-intrinsics-filter=vastart \ +// RUN: | FileCheck %s + +// CHECK-LABEL: def LLVM_vastart +// CHECK: LLVM_Op<"intr +// CHECK: Arguments<(ins +// CHECK: Results<(outs + +//---------------------------------------------------------------------------// + +// This checks that the ODS we produce can be consumed by MLIR tablegen. We only +// make sure the entire process does not fail and produces some C++. The shape +// of this C++ code is tested by ODS tests. + +// RUN: cat %S/../../../llvm/include/llvm/IR/Intrinsics.td \ +// RUN: | grep -v "llvm/IR/Intrinsics" \ +// RUN: | mlir-tblgen -gen-llvmir-intrinsics -I %S/../../../llvm/include/ --llvmir-intrinsics-filter=vastart \ +// RUN: | mlir-tblgen -gen-op-decls -I %S/../../include \ +// RUN: | FileCheck --check-prefix=ODS %s + +// ODS-LABEL: class vastart diff --git a/mlir/tools/mlir-tblgen/CMakeLists.txt b/mlir/tools/mlir-tblgen/CMakeLists.txt --- a/mlir/tools/mlir-tblgen/CMakeLists.txt +++ b/mlir/tools/mlir-tblgen/CMakeLists.txt @@ -6,6 +6,7 @@ add_tablegen(mlir-tblgen MLIR EnumsGen.cpp LLVMIRConversionGen.cpp + LLVMIRIntrinsicGen.cpp mlir-tblgen.cpp OpDefinitionsGen.cpp OpDocGen.cpp diff --git a/mlir/tools/mlir-tblgen/LLVMIRIntrinsicGen.cpp b/mlir/tools/mlir-tblgen/LLVMIRIntrinsicGen.cpp new file mode 100644 --- /dev/null +++ b/mlir/tools/mlir-tblgen/LLVMIRIntrinsicGen.cpp @@ -0,0 +1,189 @@ +//===- LLVMIntrinsicGen.cpp - TableGen utility for converting intrinsics --===// +// +// Part of the MLIR Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is a TableGen generator that converts TableGen definitions for LLVM +// intrinsics to TableGen definitions for MLIR operations. +// +//===----------------------------------------------------------------------===// + +#include "mlir/Support/STLExtras.h" +#include "mlir/TableGen/GenInfo.h" + +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/Signals.h" +#include "llvm/TableGen/Error.h" +#include "llvm/TableGen/Main.h" +#include "llvm/TableGen/Record.h" +#include "llvm/TableGen/TableGenBackend.h" + +static llvm::cl::OptionCategory IntrinsicGenCat("Intrinsics Generator Options"); + +static llvm::cl::opt + nameFilter("llvmir-intrinsics-filter", + llvm::cl::desc("Only keep the intrinsics with the specified " + "substring in their record name"), + llvm::cl::cat(IntrinsicGenCat)); + +namespace { +/// A wrapper for LLVM's Tablegen class `Intrinsic` that provides accessors to +/// the fields of the record. +class LLVMIntrinsic { +public: + LLVMIntrinsic(const llvm::Record &record) : record(record) {} + + /// Get the name of the operation to be used in MLIR. Uses the appropriate + /// field if not empty, constructs a name by replacing underscores with dots + /// in the record name otherwise. + std::string getOperationName() const { + llvm::StringRef name = record.getValueAsString(fieldName); + if (!name.empty()) + return name.str(); + + name = record.getName(); + assert(name.startswith("int_") && + "LLVM intinsic names are expected to start with 'int_'"); + name = name.drop_front(4); + llvm::SmallVector chunks; + name.split(chunks, '_'); + return llvm::join(chunks, "."); + } + + /// Get the name of the record without the "intrinsic" prefix. + llvm::StringRef getProperRecordName() const { + llvm::StringRef name = record.getName(); + assert(name.startswith("int_") && + "LLVM intinsic names are expected to start with 'int_'"); + return name.drop_front(4); + } + + /// Get the number of operands. + unsigned getNumOperands() const { + auto operands = record.getValueAsListOfDefs(fieldOperands); + for (const llvm::Record *r : operands) { + (void)r; + assert(r->isSubClassOf("LLVMType") && + "expected operands to be of LLVM type"); + } + return operands.size(); + } + + /// Get the number of results. Note that LLVM does not support multi-value + /// operations so, in fact, multiple results will be returned as a value of + /// structure type. + unsigned getNumResults() const { + auto results = record.getValueAsListOfDefs(fieldResults); + for (const llvm::Record *r : results) { + (void)r; + assert(r->isSubClassOf("LLVMType") && + "expected operands to be of LLVM type"); + } + return results.size(); + } + + /// Return true if the intrinsic may have side effects, i.e. does not have the + /// `IntrNoMem` property. + bool hasSideEffects() const { + auto props = record.getValueAsListOfDefs(fieldTraits); + for (const llvm::Record *r : props) { + if (r->getName() == "IntrNoMem") + return true; + } + return false; + } + + /// Return true if the intrinsic is commutative, i.e. has the respective + /// property. + bool isCommutative() const { + auto props = record.getValueAsListOfDefs(fieldTraits); + for (const llvm::Record *r : props) { + if (r->getName() == "Commutative") + return true; + } + return false; + } + +private: + /// Names of the fileds in the Intrinsic LLVM Tablegen class. + const char *fieldName = "LLVMName"; + const char *fieldOperands = "ParamTypes"; + const char *fieldResults = "RetTypes"; + const char *fieldTraits = "IntrProperties"; + + const llvm::Record &record; +}; +} // namespace + +/// Emits C++ code constructing an LLVM IR intrinsic given the generated MLIR +/// operation. In LLVM IR, intrinsics are constructed as function calls. +static void emitBuilder(const LLVMIntrinsic &intr, llvm::raw_ostream &os) { + os << " llvm::Module *module = builder.GetInsertBlock()->getModule();\n"; + os << " llvm::Function *fn = llvm::Intrinsic::getDeclaration(\n"; + os << " module, llvm::Intrinsic::" << intr.getProperRecordName() + << ");\n"; + os << " auto operands = llvm::to_vector<8, Value *>(\n"; + os << " opInst.operand_begin(), opInst.operand_end());\n"; + os << " " << (intr.getNumResults() > 0 ? "$res = " : "") + << "builder.CreateCall(fn, operands);\n"; + os << " "; +} + +/// Emits ODS (TableGen-based) code for `record` representing an LLVM intrinsic. +/// Returns true on error, false on success. +static bool emitIntrinsic(const llvm::Record &record, llvm::raw_ostream &os) { + LLVMIntrinsic intr(record); + + // Prepare strings for traits, if any. + llvm::SmallVector traits; + if (intr.isCommutative()) + traits.push_back("Commutative"); + if (!intr.hasSideEffects()) + traits.push_back("NoSideEffect"); + + // Prepare strings for operands. + llvm::SmallVector operands(intr.getNumOperands(), + "LLVM_Type"); + + // Emit the definition. + os << "def LLVM_" << intr.getProperRecordName() << " : LLVM_Op<\"intr." + << intr.getOperationName() << "\", ["; + mlir::interleaveComma(traits, os); + os << "]>, Arguments<(ins" << (operands.empty() ? "" : " "); + mlir::interleaveComma(operands, os); + os << ")>, Results<(outs" + << (intr.getNumResults() == 0 ? "" : " LLVM_Type:$res") << ")> {\n" + << " let llvmBuilder = [{\n"; + emitBuilder(intr, os); + os << "}];\n"; + os << "}\n\n"; + + return false; +} + +/// Traverses the list of TableGen definitions derived from the "Intrinsic" +/// class and generates MLIR ODS definitions for those intrinsics that have +/// the name matching the filter. +static bool emitIntrinsics(const llvm::RecordKeeper &records, + llvm::raw_ostream &os) { + llvm::emitSourceFileHeader("Operations for LLVM intrinsics", os); + os << "include \"mlir/Dialect/LLVMIR/LLVMOpBase.td\"\n\n"; + + auto defs = records.getAllDerivedDefinitions("Intrinsic"); + for (const llvm::Record *r : defs) { + if (!nameFilter.empty() && !r->getName().contains(nameFilter)) + continue; + if (emitIntrinsic(*r, os)) + return true; + } + + return false; +} + +static mlir::GenRegistration genLLVMIRIntrinsics("gen-llvmir-intrinsics", + "Generate LLVM IR intrinsics", + emitIntrinsics);