[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
This commit is contained in:
Alex Zinenko 2020-01-17 18:16:07 +01:00
parent b9d2bf38e8
commit f343544b81
5 changed files with 229 additions and 8 deletions

View File

@ -49,4 +49,11 @@ class LLVM_OpBase<Dialect dialect, string mnemonic, list<OpTrait> 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<string mnemonic, list<OpTrait> traits = []> :
LLVM_OpBase<LLVM_Dialect, mnemonic, traits>;
#endif // LLVMIR_OP_BASE

View File

@ -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<string mnemonic, list<OpTrait> traits = []> :
LLVM_OpBase<LLVM_Dialect, mnemonic, traits> {
}
class LLVM_Builder<string builder> {
string llvmBuilder = builder;
}

View File

@ -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

View File

@ -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

View File

@ -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<std::string>
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<llvm::StringRef, 8> 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<llvm::StringRef, 2> traits;
if (intr.isCommutative())
traits.push_back("Commutative");
if (!intr.hasSideEffects())
traits.push_back("NoSideEffect");
// Prepare strings for operands.
llvm::SmallVector<llvm::StringRef, 8> 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);