Skip to content

Commit d85f6e5

Browse files
committed
[mlir][llvmir] Import intrinsics with attributes from LLVMIR.
The revision adds support to specify custom import functions for LLVM IR intrinsics with immediate arguments that translate to MLIR attributes. It takes an approach similar to the MLIR to LLVM translation that uses a tablegen defined build method. The default implementation of this newly introduced "mlirBuilder" assumes all intrinsic arguments translate to operands. Specific intrinsics, such as llvm.lifetime.start/stop then define a custom builder that converts their immediate arguments to MLIR attributes. Depends on D135349 Reviewed By: ftynse Differential Revision: https://reviews.llvm.org/D135350
1 parent 3771310 commit d85f6e5

File tree

6 files changed

+224
-63
lines changed

6 files changed

+224
-63
lines changed

mlir/include/mlir/Dialect/LLVMIR/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ add_public_tablegen_target(MLIRLLVMConversionsIncGen)
3737

3838
set(LLVM_TARGET_DEFINITIONS LLVMIntrinsicOps.td)
3939
mlir_tablegen(LLVMIntrinsicConversions.inc -gen-llvmir-conversions)
40-
mlir_tablegen(LLVMIntrinsicToLLVMIROpPairs.inc -gen-llvmintrinsic-to-llvmirop-pairs)
40+
mlir_tablegen(LLVMIntrinsicFromLLVMIRConversions.inc -gen-intr-from-llvmir-conversions)
41+
mlir_tablegen(LLVMConvertibleLLVMIRIntrinsics.inc -gen-convertible-llvmir-intrinsics)
4142
add_public_tablegen_target(MLIRLLVMIntrinsicConversionsIncGen)
4243

4344
add_mlir_dialect(NVVMOps nvvm)
@@ -55,4 +56,3 @@ add_mlir_doc(ROCDLOps ROCDLDialect Dialects/ -gen-dialect-doc -dialect=rocdl)
5556
set(LLVM_TARGET_DEFINITIONS ROCDLOps.td)
5657
mlir_tablegen(ROCDLConversions.inc -gen-llvmir-conversions)
5758
add_public_tablegen_target(MLIRROCDLConversionsIncGen)
58-

mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,20 @@ class LLVM_LifetimeBaseOp<string opName> : LLVM_ZeroResultIntrOp<opName> {
121121
let assemblyFormat = "$size `,` $ptr attr-dict `:` type($ptr)";
122122
}
123123

124-
def LLVM_LifetimeStartOp : LLVM_LifetimeBaseOp<"lifetime.start">;
125-
def LLVM_LifetimeEndOp : LLVM_LifetimeBaseOp<"lifetime.end">;
124+
def LLVM_LifetimeStartOp : LLVM_LifetimeBaseOp<"lifetime.start"> {
125+
// Custom builder to convert the size argument to an attribute.
126+
string mlirBuilder = [{
127+
$_builder.create<LLVM::LifetimeStartOp>(
128+
$_location, $_int_attr($size), $ptr);
129+
}];
130+
}
131+
def LLVM_LifetimeEndOp : LLVM_LifetimeBaseOp<"lifetime.end"> {
132+
// Custom builder to convert the size argument to an attribute.
133+
string mlirBuilder = [{
134+
$_builder.create<LLVM::LifetimeEndOp>(
135+
$_location, $_int_attr($size), $ptr);
136+
}];
137+
}
126138

127139
// Intrinsics with multiple returns.
128140

mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,9 @@ def LLVM_IntrPatterns {
302302
// to be accessed via the LLVMStructType, instead of directly via the result.
303303
// "opName" contains the name of the operation to be associated with the
304304
// intrinsic and "enumName" contains the name of the intrinsic as appears in
305-
// `llvm::Intrinsic` enum; one usually wants these to be related.
305+
// `llvm::Intrinsic` enum; one usually wants these to be related. Additionally,
306+
// the base class also defines the "mlirBuilder" field to support the inverse
307+
// translation starting from an LLVM IR intrinsic.
306308
class LLVM_IntrOpBase<Dialect dialect, string opName, string enumName,
307309
list<int> overloadedResults, list<int> overloadedOperands,
308310
list<Trait> traits, int numResults,
@@ -312,7 +314,7 @@ class LLVM_IntrOpBase<Dialect dialect, string opName, string enumName,
312314
string resultPattern = !if(!gt(numResults, 1),
313315
LLVM_IntrPatterns.structResult,
314316
LLVM_IntrPatterns.result);
315-
string llvmEnumName = enumName;
317+
string llvmEnumName = enumName;
316318
let llvmBuilder = [{
317319
llvm::Module *module = builder.GetInsertBlock()->getModule();
318320
llvm::Function *fn = llvm::Intrinsic::getDeclaration(
@@ -332,6 +334,30 @@ class LLVM_IntrOpBase<Dialect dialect, string opName, string enumName,
332334
"moduleTranslation.setAliasScopeMetadata(op, inst);",
333335
"(void) inst;")
334336
# !if(!gt(numResults, 0), "$res = inst;", "");
337+
338+
// A builder to construct the MLIR LLVM dialect operation given the matching
339+
// LLVM IR intrinsic instruction `callInst`. The following $-variables exist:
340+
// - $name - substituted by the remapped `callInst` argument using the
341+
// the index of the MLIR operation argument with the given name;
342+
// - $_int_attr - substituted by a call to an integer attribute matcher;
343+
// - $_resultType - substituted with the MLIR result type;
344+
// - $_location - substituted with the MLIR location;
345+
// - $_builder - substituted with the MLIR builder;
346+
// - $_qualCppClassName - substitiuted with the MLIR operation class name.
347+
// Additionally, `$$` can be used to produce the dollar character.
348+
// NOTE: The $name variable resolution assumes the MLIR and LLVM argument
349+
// orders match and there are no optional or variadic arguments.
350+
string mlirBuilder = [{
351+
SmallVector<llvm::Value *> operands(callInst->args());
352+
SmallVector<Type> resultTypes =
353+
}] # !if(!gt(numResults, 0),
354+
"{$_resultType};", "{};") # [{
355+
Operation *op = $_builder.create<$_qualCppClassName>(
356+
$_location,
357+
resultTypes,
358+
processValues(operands));
359+
}] # !if(!gt(numResults, 0),
360+
"instMap[callInst] = op->getResult(0);", "(void)op;");
335361
}
336362

337363
// Base class for LLVM intrinsic operations, should not be used directly. Places

mlir/lib/Target/LLVMIR/ConvertFromLLVMIR.cpp

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "mlir/IR/BuiltinOps.h"
1919
#include "mlir/IR/BuiltinTypes.h"
2020
#include "mlir/IR/MLIRContext.h"
21+
#include "mlir/IR/Matchers.h"
2122
#include "mlir/Interfaces/DataLayoutInterfaces.h"
2223
#include "mlir/Target/LLVMIR/TypeFromLLVM.h"
2324
#include "mlir/Tools/mlir-translate/Translation.h"
@@ -31,6 +32,7 @@
3132
#include "llvm/IR/Function.h"
3233
#include "llvm/IR/InlineAsm.h"
3334
#include "llvm/IR/Instructions.h"
35+
#include "llvm/IR/IntrinsicInst.h"
3436
#include "llvm/IR/Intrinsics.h"
3537
#include "llvm/IR/Type.h"
3638
#include "llvm/IRReader/IRReader.h"
@@ -42,6 +44,15 @@ using namespace mlir::LLVM;
4244

4345
#include "mlir/Dialect/LLVMIR/LLVMConversionEnumsFromLLVM.inc"
4446

47+
/// Returns true if the LLVM IR intrinsic is convertible to an MLIR LLVM dialect
48+
/// intrinsic, or false if no counterpart exists.
49+
static bool isConvertibleIntrinsic(llvm::Intrinsic::ID id) {
50+
static const DenseSet<unsigned> convertibleIntrinsics = {
51+
#include "mlir/Dialect/LLVMIR/LLVMConvertibleLLVMIRIntrinsics.inc"
52+
};
53+
return convertibleIntrinsics.contains(id);
54+
}
55+
4556
// Utility to print an LLVM value as a string for passing to emitError().
4657
// FIXME: Diagnostic should be able to natively handle types that have
4758
// operator << (raw_ostream&) defined.
@@ -173,13 +184,21 @@ class Importer {
173184
/// visited.
174185
SmallVector<Value> processValues(ArrayRef<llvm::Value *> values);
175186

187+
/// Converts `value` to an integer attribute. Asserts if the conversion fails.
188+
IntegerAttr matchIntegerAttr(Value value);
189+
176190
/// Translate the debug location to a FileLineColLoc, if `loc` is non-null.
177191
/// Otherwise, return UnknownLoc.
178192
Location translateLoc(llvm::DILocation *loc);
179193

180194
/// Converts the type from LLVM to MLIR LLVM dialect.
181195
Type convertType(llvm::Type *type);
182196

197+
/// Converts an LLVM intrinsic to an MLIR LLVM dialect operation if an MLIR
198+
/// counterpart exists. Otherwise, returns failure.
199+
LogicalResult convertIntrinsic(OpBuilder &odsBuilder,
200+
llvm::CallInst *callInst);
201+
183202
/// Imports `f` into the current module.
184203
LogicalResult processFunction(llvm::Function *f);
185204

@@ -263,6 +282,22 @@ Type Importer::convertType(llvm::Type *type) {
263282
return typeTranslator.translateType(type);
264283
}
265284

285+
LogicalResult Importer::convertIntrinsic(OpBuilder &odsBuilder,
286+
llvm::CallInst *callInst) {
287+
llvm::Function *callee = callInst->getCalledFunction();
288+
if (!callee || !callee->isIntrinsic())
289+
return failure();
290+
291+
// Check if the intrinsic is convertible to an MLIR dialect counterpart.
292+
llvm::Intrinsic::ID intrinsicID = callee->getIntrinsicID();
293+
if (!isConvertibleIntrinsic(intrinsicID))
294+
return failure();
295+
296+
#include "mlir/Dialect/LLVMIR/LLVMIntrinsicFromLLVMIRConversions.inc"
297+
298+
return failure();
299+
}
300+
266301
// We only need integers, floats, doubles, and vectors and tensors thereof for
267302
// attributes. Scalar and vector types are converted to the standard
268303
// equivalents. Array types are converted to ranked tensors; nested array types
@@ -568,6 +603,14 @@ SmallVector<Value> Importer::processValues(ArrayRef<llvm::Value *> values) {
568603
return remapped;
569604
}
570605

606+
IntegerAttr Importer::matchIntegerAttr(Value value) {
607+
IntegerAttr integerAttr;
608+
bool success = matchPattern(value, m_Constant(&integerAttr));
609+
assert(success && "expected a constant value");
610+
(void)success;
611+
return integerAttr;
612+
}
613+
571614
/// Return the MLIR OperationName for the given LLVM opcode.
572615
static StringRef lookupOperationNameFromOpcode(unsigned opcode) {
573616
// Maps from LLVM opcode to MLIR OperationName. This is deliberately ordered
@@ -649,15 +692,6 @@ static StringRef lookupOperationNameFromOpcode(unsigned opcode) {
649692
return opcMap.lookup(opcode);
650693
}
651694

652-
/// Return the MLIR OperationName for the given LLVM intrinsic ID.
653-
static StringRef lookupOperationNameFromIntrinsicID(unsigned id) {
654-
// Maps from LLVM intrinsic ID to MLIR OperationName.
655-
static const DenseMap<unsigned, StringRef> intrMap = {
656-
#include "mlir/Dialect/LLVMIR/LLVMIntrinsicToLLVMIROpPairs.inc"
657-
};
658-
return intrMap.lookup(id);
659-
}
660-
661695
static ICmpPredicate getICmpPredicate(llvm::CmpInst::Predicate p) {
662696
switch (p) {
663697
default:
@@ -959,6 +993,11 @@ LogicalResult Importer::processInstruction(llvm::Instruction *inst) {
959993
}
960994
case llvm::Instruction::Call: {
961995
llvm::CallInst *ci = cast<llvm::CallInst>(inst);
996+
997+
// For all intrinsics, try to generate to the corresponding op.
998+
if (succeeded(convertIntrinsic(b, ci)))
999+
return success();
1000+
9621001
SmallVector<llvm::Value *> args(ci->args());
9631002
SmallVector<Value> ops = processValues(args);
9641003
SmallVector<Type, 2> tys;
@@ -968,20 +1007,6 @@ LogicalResult Importer::processInstruction(llvm::Instruction *inst) {
9681007
}
9691008
Operation *op;
9701009
if (llvm::Function *callee = ci->getCalledFunction()) {
971-
// For all intrinsics, try to generate to the corresponding op.
972-
if (callee->isIntrinsic()) {
973-
auto id = callee->getIntrinsicID();
974-
StringRef opName = lookupOperationNameFromIntrinsicID(id);
975-
if (!opName.empty()) {
976-
OperationState state(loc, opName);
977-
state.addOperands(ops);
978-
state.addTypes(tys);
979-
Operation *op = b.create(state);
980-
if (!inst->getType()->isVoidTy())
981-
instMap[inst] = op->getResult(0);
982-
return success();
983-
}
984-
}
9851010
op = b.create<CallOp>(
9861011
loc, tys, SymbolRefAttr::get(b.getContext(), callee->getName()), ops);
9871012
} else {
@@ -1171,12 +1196,8 @@ LogicalResult Importer::processFunction(llvm::Function *f) {
11711196

11721197
auto functionType =
11731198
convertType(f->getFunctionType()).dyn_cast<LLVMFunctionType>();
1174-
if (f->isIntrinsic()) {
1175-
StringRef opName = lookupOperationNameFromIntrinsicID(f->getIntrinsicID());
1176-
// Skip the intrinsic decleration if we could found a corresponding op.
1177-
if (!opName.empty())
1178-
return success();
1179-
}
1199+
if (f->isIntrinsic() && isConvertibleIntrinsic(f->getIntrinsicID()))
1200+
return success();
11801201

11811202
bool dsoLocal = f->hasLocalLinkage();
11821203
CConv cconv = convertCConvFromLLVM(f->getCallingConv());

mlir/test/Target/LLVMIR/Import/intrinsic.ll

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,13 @@ define void @umin_test(i32 %0, i32 %1, <8 x i32> %2, <8 x i32> %3) {
234234
define void @vector_reductions(float %0, <8 x float> %1, <8 x i32> %2) {
235235
; CHECK: "llvm.intr.vector.reduce.add"(%{{.*}}) : (vector<8xi32>) -> i32
236236
%4 = call i32 @llvm.vector.reduce.add.v8i32(<8 x i32> %2)
237-
; CHECK: "llvm.intr.vector.reduce.and"(%{{.*}}) : (vector<8xi32>) -> i32
237+
; CHECK: "llvm.intr.vector.reduce.and"(%{{.*}}) : (vector<8xi32>) -> i32
238238
%5 = call i32 @llvm.vector.reduce.and.v8i32(<8 x i32> %2)
239239
; CHECK: "llvm.intr.vector.reduce.fmax"(%{{.*}}) : (vector<8xf32>) -> f32
240240
%6 = call float @llvm.vector.reduce.fmax.v8f32(<8 x float> %1)
241241
; CHECK: "llvm.intr.vector.reduce.fmin"(%{{.*}}) : (vector<8xf32>) -> f32
242242
%7 = call float @llvm.vector.reduce.fmin.v8f32(<8 x float> %1)
243-
; CHECK: "llvm.intr.vector.reduce.mul"(%{{.*}}) : (vector<8xi32>) -> i32
243+
; CHECK: "llvm.intr.vector.reduce.mul"(%{{.*}}) : (vector<8xi32>) -> i32
244244
%8 = call i32 @llvm.vector.reduce.mul.v8i32(<8 x i32> %2)
245245
; CHECK: "llvm.intr.vector.reduce.or"(%{{.*}}) : (vector<8xi32>) -> i32
246246
%9 = call i32 @llvm.vector.reduce.or.v8i32(<8 x i32> %2)
@@ -447,7 +447,7 @@ define void @coro_suspend(i32 %0, i1 %1, i8* %2) {
447447

448448
; CHECK-LABEL: llvm.func @coro_end
449449
define void @coro_end(i8* %0, i1 %1) {
450-
; CHECK: llvm.intr.coro.end
450+
; CHECK: llvm.intr.coro.end
451451
call i1 @llvm.coro.end(i8* %0, i1 %1)
452452
ret void
453453
}
@@ -489,11 +489,20 @@ define void @stack_restore(i8* %0) {
489489
ret void
490490
}
491491

492+
; CHECK-LABEL: llvm.func @lifetime
493+
define void @lifetime(i8* %0) {
494+
; CHECK: llvm.intr.lifetime.start 16, %{{.*}} : !llvm.ptr<i8>
495+
call void @llvm.lifetime.start.p0i8(i64 16, i8* %0)
496+
; CHECK: llvm.intr.lifetime.end 32, %{{.*}} : !llvm.ptr<i8>
497+
call void @llvm.lifetime.end.p0i8(i64 32, i8* %0)
498+
ret void
499+
}
500+
492501
; CHECK-LABEL: llvm.func @vector_predication_intrinsics
493502
define void @vector_predication_intrinsics(<8 x i32> %0, <8 x i32> %1, <8 x float> %2, <8 x float> %3, <8 x i64> %4, <8 x double> %5, <8 x i32*> %6, i32 %7, float %8, i32* %9, float* %10, <8 x i1> %11, i32 %12) {
494503
; CHECK: "llvm.intr.vp.add"(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) : (vector<8xi32>, vector<8xi32>, vector<8xi1>, i32) -> vector<8xi32>
495504
%14 = call <8 x i32> @llvm.vp.add.v8i32(<8 x i32> %0, <8 x i32> %1, <8 x i1> %11, i32 %12)
496-
; CHECK: "llvm.intr.vp.sub"(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) : (vector<8xi32>, vector<8xi32>, vector<8xi1>, i32) -> vector<8xi32>
505+
; CHECK: "llvm.intr.vp.sub"(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) : (vector<8xi32>, vector<8xi32>, vector<8xi1>, i32) -> vector<8xi32>
497506
%15 = call <8 x i32> @llvm.vp.sub.v8i32(<8 x i32> %0, <8 x i32> %1, <8 x i1> %11, i32 %12)
498507
; CHECK: "llvm.intr.vp.mul"(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) : (vector<8xi32>, vector<8xi32>, vector<8xi1>, i32) -> vector<8xi32>
499508
%16 = call <8 x i32> @llvm.vp.mul.v8i32(<8 x i32> %0, <8 x i32> %1, <8 x i1> %11, i32 %12)
@@ -585,7 +594,7 @@ define void @vector_predication_intrinsics(<8 x i32> %0, <8 x i32> %1, <8 x floa
585594
%57 = call <8 x i64> @llvm.vp.fptosi.v8i64.v8f64(<8 x double> %5, <8 x i1> %11, i32 %12)
586595
; CHECK: "llvm.intr.vp.ptrtoint"(%{{.*}}, %{{.*}}, %{{.*}}) : (!llvm.vec<8 x ptr<i32>>, vector<8xi1>, i32) -> vector<8xi64>
587596
%58 = call <8 x i64> @llvm.vp.ptrtoint.v8i64.v8p0i32(<8 x i32*> %6, <8 x i1> %11, i32 %12)
588-
; CHECK: "llvm.intr.vp.inttoptr"(%{{.*}}, %{{.*}}, %{{.*}}) : (vector<8xi64>, vector<8xi1>, i32) -> !llvm.vec<8 x ptr<i32>>
597+
; CHECK: "llvm.intr.vp.inttoptr"(%{{.*}}, %{{.*}}, %{{.*}}) : (vector<8xi64>, vector<8xi1>, i32) -> !llvm.vec<8 x ptr<i32>>
589598
%59 = call <8 x i32*> @llvm.vp.inttoptr.v8p0i32.v8i64(<8 x i64> %4, <8 x i1> %11, i32 %12)
590599
ret void
591600
}
@@ -662,7 +671,7 @@ declare <48 x float> @llvm.matrix.column.major.load.v48f32.i64(float* nocapture,
662671
declare void @llvm.matrix.column.major.store.v48f32.i64(<48 x float>, float* nocapture writeonly, i64, i1 immarg, i32 immarg, i32 immarg)
663672
declare <7 x i1> @llvm.get.active.lane.mask.v7i1.i64(i64, i64)
664673
declare <7 x float> @llvm.masked.load.v7f32.p0v7f32(<7 x float>*, i32 immarg, <7 x i1>, <7 x float>)
665-
declare void @llvm.masked.store.v7f32.p0v7f32(<7 x float>, <7 x float>*, i32 immarg, <7 x i1>)
674+
declare void @llvm.masked.store.v7f32.p0v7f32(<7 x float>, <7 x float>*, i32 immarg, <7 x i1>)
666675
declare <7 x float> @llvm.masked.gather.v7f32.v7p0f32(<7 x float*>, i32 immarg, <7 x i1>, <7 x float>)
667676
declare void @llvm.masked.scatter.v7f32.v7p0f32(<7 x float>, <7 x float*>, i32 immarg, <7 x i1>)
668677
declare <7 x float> @llvm.masked.expandload.v7f32(float*, <7 x i1>, <7 x float>)
@@ -748,3 +757,5 @@ declare <8 x i64> @llvm.vp.fptoui.v8i64.v8f64(<8 x double>, <8 x i1>, i32)
748757
declare <8 x i64> @llvm.vp.fptosi.v8i64.v8f64(<8 x double>, <8 x i1>, i32)
749758
declare <8 x i64> @llvm.vp.ptrtoint.v8i64.v8p0i32(<8 x i32*>, <8 x i1>, i32)
750759
declare <8 x i32*> @llvm.vp.inttoptr.v8p0i32.v8i64(<8 x i64>, <8 x i1>, i32)
760+
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture)
761+
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture)

0 commit comments

Comments
 (0)