From f343544b813891387add8ef01406d36b82ed0a7e Mon Sep 17 00:00:00 2001 From: Alex Zinenko Date: Fri, 17 Jan 2020 18:16:07 +0100 Subject: [PATCH] [mlir] Generator converting LLVM intrinsics defs to MLIR ODS Introduce a new generator for MLIR tablegen driver that consumes LLVM IR intrinsic definitions and produces MLIR ODS definitions. This is useful to bulk-generate MLIR operations equivalent to existing LLVM IR intrinsics, such as additional arithmetic instructions or NVVM. A test exercising the generation is also added. It reads the main LLVM intrinsics file and produces ODS to make sure the TableGen model remains in sync with what is used in LLVM. Differential Revision: https://reviews.llvm.org/D72926 --- .../include/mlir/Dialect/LLVMIR/LLVMOpBase.td | 7 + mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td | 8 - mlir/test/mlir-tblgen/llvm-intrinsics.td | 32 +++ mlir/tools/mlir-tblgen/CMakeLists.txt | 1 + mlir/tools/mlir-tblgen/LLVMIRIntrinsicGen.cpp | 189 ++++++++++++++++++ 5 files changed, 229 insertions(+), 8 deletions(-) create mode 100644 mlir/test/mlir-tblgen/llvm-intrinsics.td create mode 100644 mlir/tools/mlir-tblgen/LLVMIRIntrinsicGen.cpp diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td index ed935d5b7f78..0ae70524af7b 100644 --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td @@ -49,4 +49,11 @@ class LLVM_OpBase traits = []> : 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 index 85cc7cd4ee5c..c094f7776f74 100644 --- 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 index 000000000000..27b76cca2b33 --- /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 index 31c23b8bd387..55c360cec0d6 100644 --- a/mlir/tools/mlir-tblgen/CMakeLists.txt +++ b/mlir/tools/mlir-tblgen/CMakeLists.txt @@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS 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 index 000000000000..d58147ba3042 --- /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);