diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td --- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td +++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td @@ -1191,4 +1191,39 @@ let printer = [{ printFenceOp(p, *this); }]; let verifier = "return ::verify(*this);"; } + +def AsmATT : LLVM_EnumAttrCase< + /*string cppSym=*/"AD_ATT", /*string irSym=*/"att", + /*string llvmSym=*/"AD_ATT", /*int val=*/0>; +def AsmIntel : LLVM_EnumAttrCase< + /*string cppSym=*/"AD_Intel", /*string irSym=*/"intel", + /*string llvmSym=*/"AD_Intel", /*int val=*/1>; +def AsmATTOrIntel : LLVM_EnumAttr< + /*string name=*/"AsmDialect", + /*string llvmName=*/"::llvm::InlineAsm::AsmDialect", + /*string description=*/"ATT (0) or Intel (1) asm dialect", + /*list cases=*/[AsmATT, AsmIntel]> { + let cppNamespace = "::mlir::LLVM"; +} + +def LLVM_InlineAsmOp : LLVM_ZeroResultOp<"inline_asm", []> { + let arguments = ( + ins Variadic:$operands, + StrAttr:$asm_string, + StrAttr:$constraints, + UnitAttr:$has_side_effects, + UnitAttr:$is_align_stack, + OptionalAttr< + DefaultValuedAttr>:$asm_dialect); + + let results = (outs Variadic:$res); + + let assemblyFormat = + "(`has_side_effects` $has_side_effects^)? " + "(`is_align_stack` $is_align_stack^)? " + "(`asm_dialect` `=` $asm_dialect^)? " + "attr-dict " + "$asm_string `,` $constraints " + "operands `:` functional-type(operands, results)"; +} #endif // LLVMIR_OPS diff --git a/mlir/integration_test/Dialect/LLVMIR/CPU/test-inline-asm.mlir b/mlir/integration_test/Dialect/LLVMIR/CPU/test-inline-asm.mlir new file mode 100644 --- /dev/null +++ b/mlir/integration_test/Dialect/LLVMIR/CPU/test-inline-asm.mlir @@ -0,0 +1,10 @@ +// RUN: mlir-cpu-runner %s -e entry -entry-point-result=void \ +// RUN: -shared-libs=%mlir_integration_test_dir/libmlir_c_runner_utils%shlibext +// R_UN: FileCheck %s + +llvm.func @printI64(!llvm.i64) +llvm.func @entry() { + %val = llvm.inline_asm "rdtsc", "=A,~{dirflag},~{fpsr},~{flags}" : () -> !llvm.i64 + llvm.call @printI64(%val) : (!llvm.i64) -> () + llvm.return +} diff --git a/mlir/lib/Target/LLVMIR/ConvertFromLLVMIR.cpp b/mlir/lib/Target/LLVMIR/ConvertFromLLVMIR.cpp --- a/mlir/lib/Target/LLVMIR/ConvertFromLLVMIR.cpp +++ b/mlir/lib/Target/LLVMIR/ConvertFromLLVMIR.cpp @@ -22,6 +22,7 @@ #include "llvm/IR/Attributes.h" #include "llvm/IR/Constants.h" #include "llvm/IR/Function.h" +#include "llvm/IR/InlineAsm.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Type.h" #include "llvm/IRReader/IRReader.h" diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp --- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp +++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp @@ -32,6 +32,7 @@ #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InlineAsm.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/MDBuilder.h" #include "llvm/IR/Module.h" @@ -559,6 +560,48 @@ return success(result->getType()->isVoidTy()); } + if (auto inlineAsmOp = dyn_cast(opInst)) { + // TODO: refactor function type creation which usually occurs in std-LLVM + // conversion. + SmallVector operandTypes; + operandTypes.reserve(inlineAsmOp.operands().size()); + for (auto t : inlineAsmOp.operands().getTypes()) + operandTypes.push_back(t.cast()); + + LLVM::LLVMType resultType; + if (inlineAsmOp.getNumResults() == 0) { + resultType = LLVM::LLVMType::getVoidTy(mlirModule->getContext()); + } else if (inlineAsmOp.getNumResults() == 1) { + resultType = inlineAsmOp.getResultTypes()[0].cast(); + } else { + SmallVector unpackedResultTypes; + unpackedResultTypes.reserve(inlineAsmOp.getResultTypes().size()); + for (auto t : inlineAsmOp.getResultTypes()) + unpackedResultTypes.push_back(t.cast()); + resultType = LLVM::LLVMType::getStructTy(mlirModule->getContext(), + unpackedResultTypes); + } + + auto ft = LLVM::LLVMType::getFunctionTy(resultType, operandTypes, + /*isVariadic=*/false); + auto *IA = + inlineAsmOp.asm_dialect().hasValue() + ? llvm::InlineAsm::get( + static_cast(convertType(ft)), + inlineAsmOp.asm_string(), inlineAsmOp.constraints(), + inlineAsmOp.has_side_effects(), inlineAsmOp.is_align_stack(), + convertAsmDialectToLLVM(*inlineAsmOp.asm_dialect())) + : llvm::InlineAsm::get( + static_cast(convertType(ft)), + inlineAsmOp.asm_string(), inlineAsmOp.constraints(), + inlineAsmOp.has_side_effects(), inlineAsmOp.is_align_stack()); + llvm::Value *result = + builder.CreateCall(IA, lookupValues(inlineAsmOp.operands())); + if (opInst.getNumResults() != 0) + valueMapping[opInst.getResult(0)] = result; + return success(); + } + if (auto invOp = dyn_cast(opInst)) { auto operands = lookupValues(opInst.getOperands()); ArrayRef operandsRef(operands); diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir --- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir +++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir @@ -333,3 +333,23 @@ llvm.fence release return } + +// CHECK-LABEL: @useInlineAsm +llvm.func @useInlineAsm(%arg0: !llvm.i32) { + // CHECK: llvm.inline_asm {{.*}} (!llvm.i32) -> !llvm.i8 + %0 = llvm.inline_asm "bswap $0", "=r,r" %arg0 : (!llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: llvm.inline_asm {{.*}} (!llvm.i32, !llvm.i32) -> !llvm.i8 + %1 = llvm.inline_asm "foo", "bar" %arg0, %arg0 : (!llvm.i32, !llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: llvm.inline_asm has_side_effects {{.*}} (!llvm.i32, !llvm.i32) -> !llvm.i8 + %2 = llvm.inline_asm has_side_effects "foo", "bar" %arg0, %arg0 : (!llvm.i32, !llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: llvm.inline_asm is_align_stack {{.*}} (!llvm.i32, !llvm.i32) -> !llvm.i8 + %3 = llvm.inline_asm is_align_stack "foo", "bar" %arg0, %arg0 : (!llvm.i32, !llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: llvm.inline_asm asm_dialect = 1 {{.*}} (!llvm.i32, !llvm.i32) -> !llvm.i8 + %4 = llvm.inline_asm asm_dialect = 1 "foo", "bar" %arg0, %arg0 : (!llvm.i32, !llvm.i32) -> !llvm.i8 + + llvm.return +} diff --git a/mlir/test/Dialect/Neon/roundtrip.mlir b/mlir/test/Dialect/Neon/roundtrip.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Dialect/Neon/roundtrip.mlir @@ -0,0 +1,25 @@ +// RUN: mlir-opt -verify-diagnostics %s | mlir-opt -convert-neon-to-llvm | mlir-translate --neon-mlir-to-llvmir | opt -O3 | llc -O3 -mtriple=aarch64-none-linux-gnu -mattr=+neon | FileCheck %s + +// CHECK-LABEL: .globl neon_smull +func @neon_smull(%a: vector<8xsi8>, %b: vector<8xsi8>) + -> (vector<8xsi16>, vector<4xsi32>, vector<2xsi64>) { + // CHECK: smull v0.8h, v0.8b, v1.8b + %0 = neon.smull %a, %b : vector<8xsi8> to vector<8xsi16> + + // CHECK: ext v1.16b, v0.16b, v0.16b, #6 + %00 = vector.extract_strided_slice %0 {offsets = [3], sizes = [4], strides = [1]}: + vector<8xsi16> to vector<4xsi16> + + // CHECK: smull v1.4s, v1.4h, v1.4h + %1 = neon.smull %00, %00 : vector<4xsi16> to vector<4xsi32> + + // CHECK: ext v2.16b, v1.16b, v1.16b, #4 + %11 = vector.extract_strided_slice %1 {offsets = [1], sizes = [2], strides = [1]}: + vector<4xsi32> to vector<2xsi32> + + // CHECK: smull v2.2d, v2.2s, v2.2s + %2 = neon.smull %11, %11 : vector<2xsi32> to vector<2xsi64> + + // CHECK: ret + return %0, %1, %2 : vector<8xsi16>, vector<4xsi32>, vector<2xsi64> +} diff --git a/mlir/test/Target/llvmir.mlir b/mlir/test/Target/llvmir.mlir --- a/mlir/test/Target/llvmir.mlir +++ b/mlir/test/Target/llvmir.mlir @@ -1325,3 +1325,35 @@ // CHECK-NOT: "CodeView", i32 1 module attributes {} {} + +// ----- + +// CHECK-LABEL: @useInlineAsm +llvm.func @useInlineAsm(%arg0: !llvm.i32) { + // Constraints string is checked at LLVM InlineAsm instruction construction time. + // So we can't just use "bar" everywhere, number of in/out arguments has to match. + + // CHECK-NEXT: call void asm "foo", "r"(i32 {{.*}}), !dbg !7 + llvm.inline_asm "foo", "r" %arg0 : (!llvm.i32) -> () + + // CHECK-NEXT: call i8 asm "foo", "=r,r"(i32 {{.*}}), !dbg !9 + %0 = llvm.inline_asm "foo", "=r,r" %arg0 : (!llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: call i8 asm "foo", "=r,r,r"(i32 {{.*}}, i32 {{.*}}), !dbg !10 + %1 = llvm.inline_asm "foo", "=r,r,r" %arg0, %arg0 : (!llvm.i32, !llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: call i8 asm sideeffect "foo", "=r,r,r"(i32 {{.*}}, i32 {{.*}}), !dbg !11 + %2 = llvm.inline_asm has_side_effects "foo", "=r,r,r" %arg0, %arg0 : (!llvm.i32, !llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: call i8 asm alignstack "foo", "=r,r,r"(i32 {{.*}}, i32 {{.*}}), !dbg !12 + %3 = llvm.inline_asm is_align_stack "foo", "=r,r,r" %arg0, %arg0 : (!llvm.i32, !llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: call i8 asm inteldialect "foo", "=r,r,r"(i32 {{.*}}, i32 {{.*}}), !dbg !13 + %4 = llvm.inline_asm asm_dialect = 1 "foo", "=r,r,r" %arg0, %arg0 : (!llvm.i32, !llvm.i32) -> !llvm.i8 + + // CHECK-NEXT: call { i8, i8 } asm "foo", "=r,=r,r"(i32 {{.*}}), !dbg !14 + %5 = llvm.inline_asm "foo", "=r,=r,r" %arg0 : (!llvm.i32) -> !llvm.struct<(i8, i8)> + + llvm.return +} +