Skip to content

Commit db0e7c7

Browse files
Reapply [MLIR][LLVMIR] Import unregistered intrinsics via llvm.intrin… (llvm#129174)
…sic_call Original introduced in llvm#128626, reverted in llvm#128973 Reproduced the issue on a shared lib build locally on Linux, moved content around to satisfy both static and shared lib builds. ### Original commit message 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. If multiple dialects implement this interface hook, the last one to register is the one converting all unregistered intrinsics. --------- Co-authored-by: Bruno Cardoso Lopes <bcardosolopes@users.noreply.github.com>
1 parent cb7030d commit db0e7c7

File tree

5 files changed

+145
-14
lines changed

5 files changed

+145
-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/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(LLVM_OPTIONAL_SOURCES
88
DebugImporter.cpp
99
LoopAnnotationImporter.cpp
1010
LoopAnnotationTranslation.cpp
11+
LLVMImportInterface.cpp
1112
ModuleTranslation.cpp
1213
ModuleImport.cpp
1314
TypeToLLVM.cpp
@@ -68,6 +69,7 @@ add_mlir_translation_library(MLIRTargetLLVMIRImport
6869
DebugImporter.cpp
6970
LoopAnnotationImporter.cpp
7071
ModuleImport.cpp
72+
LLVMImportInterface.cpp
7173
TypeFromLLVM.cpp
7274

7375
ADDITIONAL_HEADER_DIRS
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//===------------------------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file implements methods from LLVMImportInterface.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "mlir/Target/LLVMIR/LLVMImportInterface.h"
14+
#include "mlir/Target/LLVMIR/Import.h"
15+
#include "mlir/Target/LLVMIR/ModuleImport.h"
16+
17+
using namespace mlir;
18+
using namespace mlir::LLVM;
19+
using namespace mlir::LLVM::detail;
20+
21+
LogicalResult mlir::LLVMImportInterface::convertUnregisteredIntrinsic(
22+
OpBuilder &builder, llvm::CallInst *inst,
23+
LLVM::ModuleImport &moduleImport) {
24+
StringRef intrinName = inst->getCalledFunction()->getName();
25+
26+
SmallVector<llvm::Value *> args(inst->args());
27+
ArrayRef<llvm::Value *> llvmOperands(args);
28+
29+
SmallVector<llvm::OperandBundleUse> llvmOpBundles;
30+
llvmOpBundles.reserve(inst->getNumOperandBundles());
31+
for (unsigned i = 0; i < inst->getNumOperandBundles(); ++i)
32+
llvmOpBundles.push_back(inst->getOperandBundleAt(i));
33+
34+
SmallVector<Value> mlirOperands;
35+
SmallVector<NamedAttribute> mlirAttrs;
36+
if (failed(moduleImport.convertIntrinsicArguments(
37+
llvmOperands, llvmOpBundles, false, {}, {}, mlirOperands, mlirAttrs)))
38+
return failure();
39+
40+
Type results = moduleImport.convertType(inst->getType());
41+
auto op = builder.create<::mlir::LLVM::CallIntrinsicOp>(
42+
moduleImport.translateLoc(inst->getDebugLoc()), results,
43+
StringAttr::get(builder.getContext(), intrinName),
44+
ValueRange{mlirOperands}, FastmathFlagsAttr{});
45+
46+
moduleImport.setFastmathFlagsAttr(inst, op);
47+
48+
// Update importer tracking of results.
49+
unsigned numRes = op.getNumResults();
50+
if (numRes == 1)
51+
moduleImport.mapValue(inst) = op.getResult(0);
52+
else if (numRes == 0)
53+
moduleImport.mapNoResultOp(inst);
54+
else
55+
return op.emitError(
56+
"expected at most one result from target intrinsic call");
57+
58+
return success();
59+
}

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)