Skip to content

Commit d584d1f

Browse files
bcardosolopesgysit
andauthored
[MLIR][LLVMIR] Import unregistered intrinsics via llvm.intrinsic_call (#128626)
Currently, the llvm importer can only cover intrinsics that have a first class representation in an MLIR dialect (arm-neon, etc). This PR introduces a fallback mechanism that allow "unregistered" intrinsics to be imported by using the generic `llvm.intrinsic_call` operation. This is useful in several ways: 1. Allows round-trip the LLVM dialect output lowered from other dialects (example: ClangIR) 2. Enables MLIR-linking tools to operate on imported LLVM IR without requiring to add new operations to dozen of different targets (cc @xlauko @smeenai). If multiple dialects implement this interface hook, the last one to register is the one converting all unregistered intrinsics. --------- Co-authored-by: Tobias Gysi <[email protected]>
1 parent 8fb88f5 commit d584d1f

File tree

4 files changed

+123
-14
lines changed

4 files changed

+123
-14
lines changed

mlir/include/mlir/Target/LLVMIR/LLVMImportInterface.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,18 @@ class LLVMImportInterface
155155
LogicalResult convertIntrinsic(OpBuilder &builder, llvm::CallInst *inst,
156156
LLVM::ModuleImport &moduleImport) const {
157157
// Lookup the dialect interface for the given intrinsic.
158-
Dialect *dialect = intrinsicToDialect.lookup(inst->getIntrinsicID());
158+
// Verify the intrinsic identifier maps to an actual intrinsic.
159+
llvm::Intrinsic::ID intrinId = inst->getIntrinsicID();
160+
assert(intrinId != llvm::Intrinsic::not_intrinsic);
161+
162+
// First lookup the intrinsic across different dialects for known
163+
// supported conversions, examples include arm-neon, nvm-sve, etc.
164+
Dialect *dialect = intrinsicToDialect.lookup(intrinId);
165+
166+
// No specialized (supported) intrinsics, attempt to generate a generic
167+
// version via llvm.call_intrinsic (if available).
159168
if (!dialect)
160-
return failure();
169+
return convertUnregisteredIntrinsic(builder, inst, moduleImport);
161170

162171
// Dispatch the conversion to the dialect interface.
163172
const LLVMImportDialectInterface *iface = getInterfaceFor(dialect);
@@ -224,6 +233,11 @@ class LLVMImportInterface
224233
}
225234

226235
private:
236+
/// Generate llvm.call_intrinsic when no supporting dialect available.
237+
static LogicalResult
238+
convertUnregisteredIntrinsic(OpBuilder &builder, llvm::CallInst *inst,
239+
LLVM::ModuleImport &moduleImport);
240+
227241
DenseMap<unsigned, Dialect *> intrinsicToDialect;
228242
DenseMap<unsigned, const LLVMImportDialectInterface *> instructionToDialect;
229243
DenseMap<unsigned, SmallVector<Dialect *, 1>> metadataToDialect;

mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,45 @@ static LogicalResult setIntelReqdSubGroupSizeAttr(Builder &builder,
367367
return success();
368368
}
369369

370+
LogicalResult mlir::LLVMImportInterface::convertUnregisteredIntrinsic(
371+
OpBuilder &builder, llvm::CallInst *inst,
372+
LLVM::ModuleImport &moduleImport) {
373+
StringRef intrinName = inst->getCalledFunction()->getName();
374+
375+
SmallVector<llvm::Value *> args(inst->args());
376+
ArrayRef<llvm::Value *> llvmOperands(args);
377+
378+
SmallVector<llvm::OperandBundleUse> llvmOpBundles;
379+
llvmOpBundles.reserve(inst->getNumOperandBundles());
380+
for (unsigned i = 0; i < inst->getNumOperandBundles(); ++i)
381+
llvmOpBundles.push_back(inst->getOperandBundleAt(i));
382+
383+
SmallVector<Value> mlirOperands;
384+
SmallVector<NamedAttribute> mlirAttrs;
385+
if (failed(moduleImport.convertIntrinsicArguments(
386+
llvmOperands, llvmOpBundles, false, {}, {}, mlirOperands, mlirAttrs)))
387+
return failure();
388+
389+
Type results = moduleImport.convertType(inst->getType());
390+
auto op = builder.create<::mlir::LLVM::CallIntrinsicOp>(
391+
moduleImport.translateLoc(inst->getDebugLoc()), results,
392+
StringAttr::get(builder.getContext(), intrinName),
393+
ValueRange{mlirOperands}, FastmathFlagsAttr{});
394+
395+
moduleImport.setFastmathFlagsAttr(inst, op);
396+
397+
// Update importer tracking of results.
398+
unsigned numRes = op.getNumResults();
399+
if (numRes == 1)
400+
moduleImport.mapValue(inst) = op.getResult(0);
401+
else if (numRes == 0)
402+
moduleImport.mapNoResultOp(inst);
403+
else
404+
return op.emitError(
405+
"expected at most one result from target intrinsic call");
406+
407+
return success();
408+
}
370409
namespace {
371410

372411
/// Implementation of the dialect interface that converts operations belonging

mlir/test/Target/LLVMIR/Import/import-failure.ll

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,6 @@ bb1:
3838

3939
; // -----
4040

41-
declare void @llvm.gcroot(ptr %arg1, ptr %arg2)
42-
43-
; CHECK: <unknown>
44-
; CHECK-SAME: error: unhandled intrinsic: call void @llvm.gcroot(ptr %arg1, ptr null)
45-
define void @unhandled_intrinsic() gc "example" {
46-
%arg1 = alloca ptr
47-
call void @llvm.gcroot(ptr %arg1, ptr null)
48-
ret void
49-
}
50-
51-
; // -----
52-
5341
; Check that debug intrinsics with an unsupported argument are dropped.
5442

5543
declare void @llvm.dbg.value(metadata, metadata, metadata)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
; RUN: mlir-translate -import-llvm %s -split-input-file | FileCheck %s
2+
3+
declare i64 @llvm.aarch64.ldxr.p0(ptr)
4+
5+
define dso_local void @t0(ptr %a) {
6+
%x = call i64 @llvm.aarch64.ldxr.p0(ptr elementtype(i8) %a)
7+
ret void
8+
}
9+
10+
; CHECK-LABEL: llvm.func @llvm.aarch64.ldxr.p0(!llvm.ptr)
11+
; CHECK-LABEL: llvm.func @t0
12+
; CHECK: llvm.call_intrinsic "llvm.aarch64.ldxr.p0"({{.*}}) : (!llvm.ptr) -> i64
13+
; CHECK: llvm.return
14+
; CHECK: }
15+
16+
; // -----
17+
18+
declare <8 x i8> @llvm.aarch64.neon.uabd.v8i8(<8 x i8>, <8 x i8>)
19+
20+
define dso_local <8 x i8> @t1(<8 x i8> %lhs, <8 x i8> %rhs) {
21+
%r = call <8 x i8> @llvm.aarch64.neon.uabd.v8i8(<8 x i8> %lhs, <8 x i8> %rhs)
22+
ret <8 x i8> %r
23+
}
24+
25+
; CHECK: llvm.func @t1(%[[A0:.*]]: vector<8xi8>, %[[A1:.*]]: vector<8xi8>) -> vector<8xi8> {{.*}} {
26+
; CHECK: %[[R:.*]] = llvm.call_intrinsic "llvm.aarch64.neon.uabd.v8i8"(%[[A0]], %[[A1]]) : (vector<8xi8>, vector<8xi8>) -> vector<8xi8>
27+
; CHECK: llvm.return %[[R]] : vector<8xi8>
28+
; CHECK: }
29+
30+
; // -----
31+
32+
declare void @llvm.aarch64.neon.st2.v8i8.p0(<8 x i8>, <8 x i8>, ptr)
33+
34+
define dso_local void @t2(<8 x i8> %lhs, <8 x i8> %rhs, ptr %a) {
35+
call void @llvm.aarch64.neon.st2.v8i8.p0(<8 x i8> %lhs, <8 x i8> %rhs, ptr %a)
36+
ret void
37+
}
38+
39+
; CHECK: llvm.func @t2(%[[A0:.*]]: vector<8xi8>, %[[A1:.*]]: vector<8xi8>, %[[A2:.*]]: !llvm.ptr) {{.*}} {
40+
; CHECK: llvm.call_intrinsic "llvm.aarch64.neon.st2.v8i8.p0"(%[[A0]], %[[A1]], %[[A2]]) : (vector<8xi8>, vector<8xi8>, !llvm.ptr) -> !llvm.void
41+
; CHECK: llvm.return
42+
; CHECK: }
43+
44+
; // -----
45+
46+
declare void @llvm.gcroot(ptr %arg1, ptr %arg2)
47+
define void @gctest() gc "example" {
48+
%arg1 = alloca ptr
49+
call void @llvm.gcroot(ptr %arg1, ptr null)
50+
ret void
51+
}
52+
53+
; CHECK-LABEL: @gctest
54+
; CHECK: llvm.call_intrinsic "llvm.gcroot"({{.*}}, {{.*}}) : (!llvm.ptr, !llvm.ptr) -> !llvm.void
55+
56+
; // -----
57+
58+
; Test we get the supported version, not the unregistered one.
59+
60+
declare i32 @llvm.lround.i32.f32(float)
61+
62+
; CHECK-LABEL: llvm.func @lround_test
63+
define void @lround_test(float %0, double %1) {
64+
; CHECK-NOT: llvm.call_intrinsic "llvm.lround
65+
; CHECK: llvm.intr.lround(%{{.*}}) : (f32) -> i32
66+
%3 = call i32 @llvm.lround.i32.f32(float %0)
67+
ret void
68+
}

0 commit comments

Comments
 (0)