| //===- ArmSMEToLLVM.cpp - Convert ArmSME to LLVM dialect ------------------===// | |
| // | |
| // Part of the LLVM 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 file implements lowering of ArmSME operations to LLVM intrinsics. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "mlir/Conversion/ArmSMEToLLVM/ArmSMEToLLVM.h" | |
| #include "mlir/Conversion/LLVMCommon/ConversionTarget.h" | |
| #include "mlir/Conversion/LLVMCommon/Pattern.h" | |
| #include "mlir/Dialect/Arith/IR/Arith.h" | |
| #include "mlir/Dialect/ArmSME/IR/ArmSME.h" | |
| #include "mlir/Dialect/ArmSME/Transforms/Transforms.h" | |
| #include "mlir/Dialect/ArmSME/Utils/Utils.h" | |
| #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" | |
| #include "mlir/Dialect/LLVMIR/LLVMDialect.h" | |
| #include "mlir/Dialect/MemRef/IR/MemRef.h" | |
| #include "mlir/Dialect/Vector/IR/VectorOps.h" | |
| #include "mlir/Pass/Pass.h" | |
| #include "mlir/Transforms/DialectConversion.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| namespace mlir { | |
| #define GEN_PASS_DEF_CONVERTARMSMETOLLVM | |
| #include "mlir/Conversion/Passes.h.inc" | |
| } // namespace mlir | |
| using namespace mlir; | |
| namespace { | |
| static constexpr StringLiteral kInMemoryTileIdAttr("arm_sme.in_memory_tile_id"); | |
| /// Helper to create an arm_sme.intr.ld1*.(horiz|vert)' intrinsic. | |
| static Operation *createLoadTileSliceIntrinsic( | |
| RewriterBase &rewriter, Location loc, arm_sme::ArmSMETileType type, | |
| arm_sme::TileSliceLayout layout, Value maskOp, Value ptr, | |
| IntegerAttr tileId, Value tileSliceI32) { | |
| if (layout == arm_sme::TileSliceLayout::Horizontal) { | |
| switch (type) { | |
| case arm_sme::ArmSMETileType::ZAB: | |
| return arm_sme::aarch64_sme_ld1b_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAH: | |
| return arm_sme::aarch64_sme_ld1h_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAS: | |
| return arm_sme::aarch64_sme_ld1w_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAD: | |
| return arm_sme::aarch64_sme_ld1d_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAQ: | |
| return arm_sme::aarch64_sme_ld1q_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| } | |
| } else { | |
| switch (type) { | |
| case arm_sme::ArmSMETileType::ZAB: | |
| return arm_sme::aarch64_sme_ld1b_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAH: | |
| return arm_sme::aarch64_sme_ld1h_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAS: | |
| return arm_sme::aarch64_sme_ld1w_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAD: | |
| return arm_sme::aarch64_sme_ld1d_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAQ: | |
| return arm_sme::aarch64_sme_ld1q_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| break; | |
| } | |
| } | |
| llvm_unreachable("unknown type in createLoadTileSliceIntrinsic"); | |
| } | |
| /// Helper to create an arm_sme.intr.st1*.(horiz|vert)' intrinsic. | |
| static Operation *createStoreTileSliceIntrinsic( | |
| RewriterBase &rewriter, Location loc, arm_sme::ArmSMETileType type, | |
| arm_sme::TileSliceLayout layout, Value maskOp, Value ptr, | |
| IntegerAttr tileId, Value tileSliceI32) { | |
| if (layout == arm_sme::TileSliceLayout::Horizontal) { | |
| switch (type) { | |
| case arm_sme::ArmSMETileType::ZAB: | |
| return arm_sme::aarch64_sme_st1b_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAH: | |
| return arm_sme::aarch64_sme_st1h_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAS: | |
| return arm_sme::aarch64_sme_st1w_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAD: | |
| return arm_sme::aarch64_sme_st1d_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAQ: | |
| return arm_sme::aarch64_sme_st1q_horiz::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| } | |
| } else { | |
| switch (type) { | |
| case arm_sme::ArmSMETileType::ZAB: | |
| return arm_sme::aarch64_sme_st1b_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAH: | |
| return arm_sme::aarch64_sme_st1h_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAS: | |
| return arm_sme::aarch64_sme_st1w_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAD: | |
| return arm_sme::aarch64_sme_st1d_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| case arm_sme::ArmSMETileType::ZAQ: | |
| return arm_sme::aarch64_sme_st1q_vert::create(rewriter, loc, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| } | |
| } | |
| llvm_unreachable("unknown type in createStoreTileSliceIntrinsic"); | |
| } | |
| IntegerAttr getTileIdOrError(arm_sme::ArmSMETileOpInterface op) { | |
| auto tileId = op.getTileId(); | |
| if (!tileId) | |
| op.emitOpError( | |
| "expected tile ID to be allocated before conversion to LLVM"); | |
| return tileId; | |
| } | |
| /// Creates an alloca matching the size of tile used by `tileOp`. The alloca is | |
| /// placed in the first block of the function. | |
| static memref::AllocaOp | |
| createAllocaForTile(RewriterBase &rewriter, Location loc, | |
| FunctionOpInterface func, | |
| arm_sme::ArmSMETileOpInterface tileOp) { | |
| RewriterBase::InsertionGuard g(rewriter); | |
| // Move to the first operation in the function. | |
| rewriter.setInsertionPointToStart(&func.getBlocks().front()); | |
| // Create an alloca matching the tile size of the `tileOp`. | |
| auto vscale = vector::VectorScaleOp::create(rewriter, loc); | |
| auto tileElementType = tileOp.getTileType().getElementType(); | |
| auto memrefType = MemRefType::get( | |
| {ShapedType::kDynamic, ShapedType::kDynamic}, tileElementType); | |
| unsigned minElements = arm_sme::getSMETileSliceMinNumElts(tileElementType); | |
| auto minElementsOp = | |
| arith::ConstantIndexOp::create(rewriter, loc, minElements); | |
| auto vectorLen = arith::MulIOp::create(rewriter, loc, vscale, minElementsOp); | |
| auto alloca = memref::AllocaOp::create(rewriter, loc, memrefType, | |
| ValueRange{vectorLen, vectorLen}); | |
| return alloca; | |
| } | |
| /// Finds or creates an alloca for a spill of a tile. | |
| static memref::AllocaOp getOrCreateAllocaForTile( | |
| RewriterBase &rewriter, Location loc, FunctionOpInterface func, | |
| arm_sme::ArmSMETileOpInterface tileOp, unsigned tileId) { | |
| // Find an alloca at the top of the function tagged with a | |
| // 'arm_sme.in_memory_tile_id' that matches `tileId`. | |
| for (auto &op : func.getBlocks().front()) { | |
| auto alloca = llvm::dyn_cast<memref::AllocaOp>(op); | |
| if (!alloca) | |
| continue; | |
| auto inMemoryTileId = llvm::dyn_cast_or_null<IntegerAttr>( | |
| alloca->getDiscardableAttr(kInMemoryTileIdAttr)); | |
| if (!inMemoryTileId) | |
| continue; | |
| if (inMemoryTileId.getInt() == tileId) | |
| return alloca; | |
| } | |
| // Otherwise, create a new alloca: | |
| auto alloca = createAllocaForTile(rewriter, loc, func, tileOp); | |
| alloca->setDiscardableAttr(kInMemoryTileIdAttr, | |
| rewriter.getI32IntegerAttr(tileId)); | |
| return alloca; | |
| } | |
| /// Very naive lowering of in-memory tiles (i.e. tiles that were not assigned a | |
| /// hardware tile ID) to ArmSME intrinsics. Currently, this works by assigning | |
| /// the op to tile 0, then emitting a full tile swap between ZA and memory | |
| /// before + after the tile op. | |
| /// | |
| /// Example: | |
| /// | |
| /// // Note: <IN MEMORY TILE> = tile ID >= 16. | |
| /// arm_sme.tile_op { tile_id = <IN MEMORY TILE> } | |
| /// | |
| /// is converted to: | |
| /// // At function entry: | |
| /// %spill = memref.alloca ... : memref<?x?xty> | |
| /// | |
| /// // Around op: | |
| /// scf.for %slice_idx { | |
| /// %slice_to_save = "arm_sme.intr.read.horiz" ... <{tile_id = 0 : i32}> | |
| /// "arm_sme.intr.ld1h.horiz"(%spill, %slice_idx) <{tile_id = 0 : i32}> | |
| /// vector.store %slice_to_save, %spill[%slice_idx, %c0] | |
| /// } | |
| /// arm_sme.tile_op { tile_id = 0 } | |
| /// scf.for %slice_idx { | |
| /// %slice_to_save = "arm_sme.intr.read.horiz" ... <{tile_id = 0 : i32}> | |
| /// "arm_sme.intr.ld1h.horiz"(%spill, %slice_idx) <{tile_id = 0 : i32}> | |
| /// vector.store %slice_to_save, %spill[%slice_idx, %c0] | |
| /// } | |
| /// | |
| /// Note that these spills/fills are not inserted earlier as concept of a | |
| /// register, and the need to swap the contents, can't really be represented | |
| /// correctly at a high level in MLIR. | |
| /// | |
| /// TODO: Reduce the spills/reloads to single slices where possible (and omit | |
| /// redundant reloads). This could be done via a method on the | |
| /// `ArmSMETileOpInterface` which returns how the operation uses ZA. E.g.: | |
| /// | |
| /// `tileOp.getZaUsage()` could return: | |
| /// | |
| /// struct ArmSMEOpZAUsage { | |
| /// enum class Kind { | |
| /// TileRead, // Omit store after tile operation. | |
| /// TileWrite, // Omit load before tile operation. | |
| /// TileReadWrite, // Needs both tile load and store. | |
| /// SliceRead, // Spill single slice and omit store after operation. | |
| /// SliceWrite, // Spill single slice and omit load before operation. | |
| /// SliceReadWrite // Spill single slice. | |
| /// }; | |
| /// Value sliceIndex {}; | |
| /// TileSliceLayout sliceLayout { TileSliceLayout::Horizontal }; | |
| /// }; | |
| /// | |
| struct ConvertArmSMESpillsAndFillsToLLVM : public ConvertToLLVMPattern { | |
| ConvertArmSMESpillsAndFillsToLLVM(StringRef rootOpName, | |
| const LLVMTypeConverter &typeConverter, | |
| PatternBenefit benefit) | |
| : ConvertToLLVMPattern(rootOpName, &typeConverter.getContext(), | |
| typeConverter, benefit) {} | |
| LogicalResult | |
| matchAndRewrite(Operation *op, ArrayRef<Value> operands, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto tileOp = cast<arm_sme::ArmSMETileOpInterface>(op); | |
| // Tile has a real (hardware) tile. No spills/reloads required. | |
| if (!tileOp.isInMemoryTile()) | |
| return failure(); | |
| tileOp->emitWarning( | |
| "failed to allocate SME virtual tile to operation, tile value will go " | |
| "through memory, expect degraded performance"); | |
| // Step 1. Create an alloca for the tile at the top of the function (if one | |
| // does not already exist). | |
| auto loc = tileOp.getLoc(); | |
| auto func = tileOp->getParentOfType<FunctionOpInterface>(); | |
| auto tileAlloca = getOrCreateAllocaForTile(rewriter, loc, func, tileOp, | |
| tileOp.getTileId().getInt()); | |
| // Step 2. Assign the op a real tile ID. | |
| // For simplicity, we always use tile 0 (which always exists). | |
| auto zeroTileId = rewriter.getI32IntegerAttr(0); | |
| rewriter.modifyOpInPlace(tileOp, [&] { tileOp.setTileId(zeroTileId); }); | |
| VectorType tileVectorType = tileOp.getTileType(); | |
| auto sliceType = VectorType::Builder(tileVectorType).dropDim(0); | |
| auto swapInMemoryTileWithSMETileZero = [&] { | |
| emitFullTileSwap(rewriter, loc, tileAlloca, | |
| *arm_sme::getSMETileType(tileVectorType), sliceType, | |
| zeroTileId); | |
| }; | |
| // Step 3. Emit tile swaps before and after the op. | |
| // TODO: Reduce the amount spilled to the amount of data the `tileOp` | |
| // touches (i.e. a single tile slice). | |
| { | |
| rewriter.setInsertionPoint(op); | |
| // Swap the contents of ZA and the in-memory tile before the op. | |
| swapInMemoryTileWithSMETileZero(); | |
| rewriter.setInsertionPointAfter(op); | |
| // Swap the tile back out to memory again after the op. | |
| swapInMemoryTileWithSMETileZero(); | |
| } | |
| return success(); | |
| } | |
| /// Extracts a pointer to a slice of an in-memory tile. | |
| Value getInMemoryTileSlicePtr(RewriterBase &rewriter, Location loc, | |
| Value tileMemory, Value sliceIndex) const { | |
| auto llvmType = getTypeConverter()->convertType(tileMemory.getType()); | |
| auto descriptor = | |
| UnrealizedConversionCastOp::create(rewriter, loc, llvmType, tileMemory); | |
| auto zero = arith::ConstantIntOp::create(rewriter, loc, 0, /*width=*/64); | |
| auto sliceIndexI64 = arith::IndexCastOp::create( | |
| rewriter, loc, rewriter.getI64Type(), sliceIndex); | |
| return getStridedElementPtr( | |
| static_cast<ConversionPatternRewriter &>(rewriter), loc, | |
| llvm::cast<MemRefType>(tileMemory.getType()), descriptor.getResult(0), | |
| {sliceIndexI64, zero}); | |
| } | |
| /// Emits an in-place swap of a slice of a tile in ZA and a slice of a | |
| /// tile-sized memref (`tileAlloca`). | |
| void emitSliceSwap(RewriterBase &rewriter, Location loc, Value tileAlloca, | |
| arm_sme::ArmSMETileType tileType, VectorType sliceType, | |
| IntegerAttr tileId, Value sliceIndex) const { | |
| // Cast the slice index to an i32. | |
| auto sliceIndexI32 = arith::IndexCastOp::create( | |
| rewriter, loc, rewriter.getI32Type(), sliceIndex); | |
| // Create an all-true predicate for the slice. | |
| auto predicateType = sliceType.clone(rewriter.getI1Type()); | |
| auto allTruePredicate = arith::ConstantOp::create( | |
| rewriter, loc, DenseElementsAttr::get(predicateType, true)); | |
| // Create padding vector (never used due to all-true predicate). | |
| auto padVector = LLVM::PoisonOp::create(rewriter, loc, sliceType); | |
| // Get a pointer to the current slice. | |
| auto slicePtr = | |
| getInMemoryTileSlicePtr(rewriter, loc, tileAlloca, sliceIndex); | |
| // Read the value of the current slice from ZA. | |
| auto currentTileSlice = arm_sme::aarch64_sme_read_horiz::create( | |
| rewriter, loc, sliceType, padVector, allTruePredicate, tileId, | |
| sliceIndexI32); | |
| // Load the new tile slice back from memory into ZA. | |
| createLoadTileSliceIntrinsic( | |
| rewriter, loc, tileType, arm_sme::TileSliceLayout::Horizontal, | |
| allTruePredicate, slicePtr, tileId, sliceIndexI32); | |
| // Store the current tile slice to memory. | |
| auto zero = arith::ConstantIndexOp::create(rewriter, loc, 0); | |
| vector::StoreOp::create(rewriter, loc, currentTileSlice, tileAlloca, | |
| ValueRange{sliceIndex, zero}); | |
| } | |
| /// Emits a full in-place swap of the contents of a tile in ZA and a | |
| /// tile-sized memref (`tileAlloca`). | |
| void emitFullTileSwap(RewriterBase &rewriter, Location loc, Value tileAlloca, | |
| arm_sme::ArmSMETileType tileType, VectorType sliceType, | |
| IntegerAttr tileId) const { | |
| RewriterBase::InsertionGuard guard(rewriter); | |
| // Create an scf.for over all tile slices. | |
| auto minNumElts = | |
| arith::ConstantIndexOp::create(rewriter, loc, sliceType.getDimSize(0)); | |
| auto lowerBound = arith::ConstantIndexOp::create(rewriter, loc, 0); | |
| auto upperBound = | |
| arith::MulIOp::create(rewriter, loc, minNumElts, | |
| vector::VectorScaleOp::create(rewriter, loc)); | |
| auto step = arith::ConstantIndexOp::create(rewriter, loc, 1); | |
| auto forOp = | |
| scf::ForOp::create(rewriter, loc, lowerBound, upperBound, step); | |
| // Emit a swap for each tile slice. | |
| rewriter.setInsertionPointToStart(forOp.getBody()); | |
| auto sliceIndex = forOp.getInductionVar(); | |
| emitSliceSwap(rewriter, loc, tileAlloca, tileType, sliceType, tileId, | |
| sliceIndex); | |
| } | |
| }; | |
| enum class RequiresSpillsAndFills { Yes, No }; | |
| /// Base class for ArmSME to LLVM conversion patterns. By default, this adds | |
| /// spills and fills around ArmSME ops that use in-memory tile IDs. This can be | |
| /// disabled by setting the `requiresSpillsAndFills` template parameter to | |
| /// `RequiresSpillsAndFills::No`. | |
| template <typename SourceOp, RequiresSpillsAndFills requiresSpillsAndFills = | |
| RequiresSpillsAndFills::Yes> | |
| struct ConvertArmSMEOpToLLVMPattern : ConvertOpToLLVMPattern<SourceOp> { | |
| using ArmSMEOp = SourceOp; | |
| using ConvertOpToLLVMPattern<SourceOp>::ConvertOpToLLVMPattern; | |
| static constexpr bool requiresSpillsAndFillsConversion() { | |
| return requiresSpillsAndFills == RequiresSpillsAndFills::Yes; | |
| } | |
| }; | |
| template <typename Pattern> | |
| static void addArmSMEConversionPattern(RewritePatternSet &patterns, | |
| LLVMTypeConverter const &typeConverter) { | |
| // Register spills/fills for ops that implement the | |
| // `ArmSMETileOpInterface` and have `requiresSpillsAndFills` set to | |
| // `RequiresSpillsAndFills::Yes`. | |
| if constexpr (Pattern::requiresSpillsAndFillsConversion() && | |
| std::is_base_of_v<arm_sme::ArmSMETileOpInterface::Trait< | |
| typename Pattern::ArmSMEOp>, | |
| typename Pattern::ArmSMEOp>) { | |
| // Add spill/fill conversions with a very high benefit to ensure | |
| // they are lowered first. | |
| patterns.add<ConvertArmSMESpillsAndFillsToLLVM>( | |
| Pattern::ArmSMEOp::getOperationName(), typeConverter, | |
| /*benefit=*/1337); | |
| } | |
| patterns.add<Pattern>(typeConverter); | |
| } | |
| /// Helper to register `ConvertArmSMEOpToLLVMPattern` patterns. | |
| template <typename... Patterns> | |
| static void | |
| addArmSMEConversionPatterns(RewritePatternSet &patterns, | |
| LLVMTypeConverter const &typeConverter) { | |
| (addArmSMEConversionPattern<Patterns>(patterns, typeConverter), ...); | |
| } | |
| /// Lower 'arm_sme.zero' to SME intrinsics. | |
| /// | |
| /// BEFORE: | |
| /// ```mlir | |
| /// %v = arm_sme.zero {tile_id = 0 : i32} : vector<[4]x[4]xi32> | |
| /// ``` | |
| /// | |
| /// AFTER: | |
| /// ```mlir | |
| /// "arm_sme.intr.zero"() <{tile_mask = 17 : i32}> : () -> () | |
| /// %v = arm_sme.get_tile : vector<[4]x[4]xi32> | |
| /// ``` | |
| /// | |
| /// The 'arm_sme.get_tile' (which models the return) will fold away once all | |
| /// ArmSME ops have been converted to LLVM intrinsics. | |
| struct ZeroOpConversion : public ConvertArmSMEOpToLLVMPattern<arm_sme::ZeroOp> { | |
| using ConvertArmSMEOpToLLVMPattern::ConvertArmSMEOpToLLVMPattern; | |
| LogicalResult | |
| matchAndRewrite(arm_sme::ZeroOp zero, OpAdaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto loc = zero.getLoc(); | |
| auto tileId = getTileIdOrError(zero); | |
| if (!tileId) | |
| return failure(); | |
| // Get the base mask for tile based on the element size. | |
| // The base mask is just the mask to zero the first tile (of a size). | |
| // These masks are derived from: | |
| // https://developer.arm.com/documentation/ddi0602/2022-06/SME-Instructions/ZERO--Zero-a-list-of-64-bit-element-ZA-tiles- | |
| arm_sme::ArmSMETileType tileType = | |
| *arm_sme::getSMETileType(zero.getTileType()); | |
| auto baseMaskForSize = [&] { | |
| switch (tileType) { | |
| case arm_sme::ArmSMETileType::ZAB: | |
| // Zeroing the 8-bit ZA0.B tile is equivalent to zeroing all eight | |
| // 64-bit element tiles named ZA0.D to ZA7.D. | |
| return 0b1111'1111; | |
| case arm_sme::ArmSMETileType::ZAH: | |
| // Zeroing the 16-bit ZA0.H tile is equivalent to zeroing 64-bit | |
| // element tiles named ZA0.D, ZA2.D, ZA4.D, and ZA6.D. Shift this left | |
| // once for ZA1.H. | |
| return 0b0101'0101; | |
| case arm_sme::ArmSMETileType::ZAS: | |
| // Zeroing the 32-bit ZA0.S tile is equivalent to zeroing 64-bit | |
| // element tiles named ZA0.D and ZA4.D. | |
| // Shift left by 1, 2, or 3 respectively for ZA1.S, ZA2.S, ZA3.S. | |
| return 0b0001'0001; | |
| case arm_sme::ArmSMETileType::ZAD: | |
| // Zeroing one of the a 64-bit tiles ZA0.D to ZA7.D just requires | |
| // setting the bit for that tile. | |
| return 0b0000'0001; | |
| default: | |
| llvm_unreachable("bad element size"); | |
| } | |
| }(); | |
| // The actual mask is just the base mask shifted by the tile ID. | |
| // This will be folded to a constant after tile allocation. | |
| // | |
| // The shift is just derived from the layout of the tiles, and that the tile | |
| // ID is the index of the tile. For example, looking at the 32-bit ZAx.S | |
| // tiles: | |
| // | |
| // ZA0.S = ZA0.D and ZA4.D | |
| // * Tile ID -> 0 | |
| // * Mask -> 00010001 = (00010001 << 0) | |
| // ZA1.S = ZA1.D and ZA5.D | |
| // * Tile ID -> 1 | |
| // * Mask -> 00100010 = (00010001 << 1) | |
| // ZA2.S = ZA2.D and ZA6.D | |
| // * Tile ID -> 2 | |
| // * Mask -> 01000100 = (00010001 << 2) | |
| // ZA3.S = ZA3.D and ZA7.D | |
| // * Tile ID -> 3 | |
| // * Mask -> 10001000 = (00010001 << 3) | |
| // | |
| // This holds for all tile sizes. | |
| int32_t zeroMask = baseMaskForSize << int32_t(tileId.getInt()); | |
| arm_sme::aarch64_sme_zero::create(rewriter, loc, | |
| rewriter.getI32IntegerAttr(zeroMask)); | |
| // Create a placeholder op to preserve dataflow. | |
| // Note: Place the `get_tile` op at the start of the block. This ensures | |
| // that if there are multiple `zero` ops the intrinsics will be consecutive. | |
| rewriter.setInsertionPointToStart(zero->getBlock()); | |
| rewriter.replaceOpWithNewOp<arm_sme::GetTileOp>(zero, zero.getVectorType()); | |
| return success(); | |
| } | |
| }; | |
| /// Lower `arm_sme.load_tile_slice` to SME intrinsics. | |
| struct LoadTileSliceConversion | |
| : public ConvertArmSMEOpToLLVMPattern<arm_sme::LoadTileSliceOp> { | |
| using ConvertArmSMEOpToLLVMPattern::ConvertArmSMEOpToLLVMPattern; | |
| LogicalResult | |
| matchAndRewrite(arm_sme::LoadTileSliceOp loadTileSliceOp, | |
| arm_sme::LoadTileSliceOp::Adaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto loc = loadTileSliceOp.getLoc(); | |
| auto tileId = getTileIdOrError(loadTileSliceOp); | |
| if (!tileId) | |
| return failure(); | |
| Value ptr = this->getStridedElementPtr( | |
| rewriter, loc, loadTileSliceOp.getMemRefType(), adaptor.getBase(), | |
| adaptor.getIndices()); | |
| auto tileSlice = loadTileSliceOp.getTileSliceIndex(); | |
| // Cast tile slice to i32 for intrinsic. | |
| auto tileSliceI32 = arith::IndexCastUIOp::create( | |
| rewriter, loc, rewriter.getI32Type(), tileSlice); | |
| // Create all active predicate mask. | |
| auto maskOp = loadTileSliceOp.getMask(); | |
| auto tileVectorType = loadTileSliceOp.getVectorType(); | |
| arm_sme::ArmSMETileType tileType = *arm_sme::getSMETileType(tileVectorType); | |
| arm_sme::TileSliceLayout layout = loadTileSliceOp.getLayout(); | |
| // Create 'arm_sme.intr.ld1*.(horiz|vert)' intrinsic to load ZA tile slice. | |
| createLoadTileSliceIntrinsic(rewriter, loc, tileType, layout, maskOp, ptr, | |
| tileId, tileSliceI32); | |
| // The load intrinsics have no result, replace 'arm_sme.tile_load' with | |
| // the input tile to preserve dataflow. | |
| rewriter.replaceOp(loadTileSliceOp, loadTileSliceOp.getTile()); | |
| return success(); | |
| } | |
| }; | |
| /// Lower for `arm_sme.store_tile_slice` to SME intrinsics. | |
| struct StoreTileSliceConversion | |
| : public ConvertArmSMEOpToLLVMPattern<arm_sme::StoreTileSliceOp> { | |
| using ConvertArmSMEOpToLLVMPattern::ConvertArmSMEOpToLLVMPattern; | |
| LogicalResult | |
| matchAndRewrite(arm_sme::StoreTileSliceOp storeTileSliceOp, | |
| arm_sme::StoreTileSliceOp::Adaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto loc = storeTileSliceOp.getLoc(); | |
| auto tileVectorType = storeTileSliceOp.getVectorType(); | |
| auto tileId = getTileIdOrError(storeTileSliceOp); | |
| if (!tileId) | |
| return failure(); | |
| // Create 'arm_sme.intr.st1*.horiz' intrinsic to store ZA tile slice. | |
| Value ptr = this->getStridedElementPtr( | |
| rewriter, loc, storeTileSliceOp.getMemRefType(), adaptor.getBase(), | |
| adaptor.getIndices()); | |
| auto tileSlice = storeTileSliceOp.getTileSliceIndex(); | |
| // Cast tile slice to i32 for intrinsic. | |
| auto tileSliceI32 = arith::IndexCastUIOp::create( | |
| rewriter, loc, rewriter.getI32Type(), tileSlice); | |
| auto maskOp = storeTileSliceOp.getMask(); | |
| arm_sme::TileSliceLayout layout = storeTileSliceOp.getLayout(); | |
| arm_sme::ArmSMETileType tileType = *arm_sme::getSMETileType(tileVectorType); | |
| rewriter.replaceOp(storeTileSliceOp, | |
| createStoreTileSliceIntrinsic(rewriter, loc, tileType, | |
| layout, maskOp, ptr, | |
| tileId, tileSliceI32)); | |
| return success(); | |
| } | |
| }; | |
| /// Lower `arm_sme.insert_tile_slice` to SME intrinsics. | |
| struct InsertTileSliceConversion | |
| : public ConvertArmSMEOpToLLVMPattern<arm_sme::InsertTileSliceOp> { | |
| using ConvertArmSMEOpToLLVMPattern::ConvertArmSMEOpToLLVMPattern; | |
| LogicalResult | |
| matchAndRewrite(arm_sme::InsertTileSliceOp insertTileSliceOp, | |
| arm_sme::InsertTileSliceOp::Adaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto loc = insertTileSliceOp.getLoc(); | |
| auto tileType = insertTileSliceOp.getTileType(); | |
| auto tileId = getTileIdOrError(insertTileSliceOp); | |
| if (!tileId) | |
| return failure(); | |
| auto tileSlice = insertTileSliceOp.getTileSliceIndex(); | |
| // Cast tile slice from index to i32 for intrinsic. | |
| auto tileSliceI32 = arith::IndexCastUIOp::create( | |
| rewriter, loc, rewriter.getI32Type(), tileSlice); | |
| // Create all active predicate mask. | |
| auto one = arith::ConstantOp::create( | |
| rewriter, loc, rewriter.getI1Type(), | |
| rewriter.getIntegerAttr(rewriter.getI1Type(), 1)); | |
| auto predTy = VectorType::get(tileType.getShape()[0], rewriter.getI1Type(), | |
| /*scalableDims=*/{true}); | |
| auto allActiveMask = | |
| vector::BroadcastOp::create(rewriter, loc, predTy, one); | |
| // Create 'arm_sme.intr.write.(horiz|vert)' to write vector to tile slice. | |
| switch (insertTileSliceOp.getLayout()) { | |
| case arm_sme::TileSliceLayout::Horizontal: | |
| arm_sme::aarch64_sme_write_horiz::create(rewriter, loc, tileId, | |
| tileSliceI32, allActiveMask, | |
| insertTileSliceOp.getVector()); | |
| break; | |
| case arm_sme::TileSliceLayout::Vertical: | |
| arm_sme::aarch64_sme_write_vert::create(rewriter, loc, tileId, | |
| tileSliceI32, allActiveMask, | |
| insertTileSliceOp.getVector()); | |
| break; | |
| } | |
| // Intrinsic has no result, replace 'arm_sme.insert_tile_slice' with | |
| // the input tile to preserve dataflow. | |
| rewriter.replaceOp(insertTileSliceOp, insertTileSliceOp.getTile()); | |
| return success(); | |
| } | |
| }; | |
| /// Lower `arm_sme.extract_tile_slice` to SME intrinsics. | |
| struct ExtractTileSliceConversion | |
| : public ConvertArmSMEOpToLLVMPattern<arm_sme::ExtractTileSliceOp> { | |
| using ConvertArmSMEOpToLLVMPattern::ConvertArmSMEOpToLLVMPattern; | |
| LogicalResult | |
| matchAndRewrite(arm_sme::ExtractTileSliceOp extractTileSlice, OpAdaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto loc = extractTileSlice.getLoc(); | |
| auto sliceType = extractTileSlice.getSliceType(); | |
| auto sliceIndex = extractTileSlice.getTileSliceIndex(); | |
| auto tileId = getTileIdOrError(extractTileSlice); | |
| if (!tileId) | |
| return failure(); | |
| // Create an 'all true' predicate for the tile slice. | |
| auto predicateType = sliceType.cloneWith({}, rewriter.getI1Type()); | |
| auto allTruePredicate = arith::ConstantOp::create( | |
| rewriter, loc, DenseElementsAttr::get(predicateType, true)); | |
| // Zero destination/fallback for tile slice extraction. | |
| auto zeroVector = arith::ConstantOp::create( | |
| rewriter, loc, sliceType, rewriter.getZeroAttr(sliceType)); | |
| // Cast tile slice from index to i32 for intrinsic. | |
| auto sliceIndexI32 = arith::IndexCastOp::create( | |
| rewriter, loc, rewriter.getI32Type(), sliceIndex); | |
| // Create 'arm_sme.intr.read.(horiz|vert)' to extract the tile slice. | |
| switch (extractTileSlice.getLayout()) { | |
| case arm_sme::TileSliceLayout::Horizontal: | |
| rewriter.replaceOpWithNewOp<arm_sme::aarch64_sme_read_horiz>( | |
| extractTileSlice, sliceType, zeroVector, allTruePredicate, tileId, | |
| sliceIndexI32); | |
| break; | |
| case arm_sme::TileSliceLayout::Vertical: | |
| rewriter.replaceOpWithNewOp<arm_sme::aarch64_sme_read_vert>( | |
| extractTileSlice, sliceType, zeroVector, allTruePredicate, tileId, | |
| sliceIndexI32); | |
| break; | |
| } | |
| return success(); | |
| } | |
| }; | |
| /// Lower `arm_sme.outerproduct` to SME MOPA intrinsics. | |
| /// | |
| /// Example: | |
| /// | |
| /// %0 = arm_sme.outerproduct %lhs, %rhs acc(%acc) | |
| /// : vector<[4]xf32>, vector<[4]xf32> | |
| /// | |
| /// is converted to: | |
| /// | |
| /// "arm_sme.intr.mopa"(%ptrue_s, %ptrue_s, %lhs, %rhs) <{tile_id = 0 : i32}> | |
| /// : (vector<[4]xi1>, vector<[4]xi1>, vector<[4]xf32>, | |
| /// vector<[4]xf32>) -> () | |
| /// | |
| /// Currently only supports FMOPA and BFMOPA (non-widening). | |
| struct OuterProductOpConversion | |
| : public ConvertArmSMEOpToLLVMPattern<arm_sme::OuterProductOp> { | |
| using ConvertArmSMEOpToLLVMPattern::ConvertArmSMEOpToLLVMPattern; | |
| LogicalResult | |
| matchAndRewrite(arm_sme::OuterProductOp outerProductOp, | |
| arm_sme::OuterProductOp::Adaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto tileId = getTileIdOrError(outerProductOp); | |
| if (!tileId) | |
| return failure(); | |
| auto isSupportedType = [](VectorType vectorType) { | |
| // TODO: the FP outer product instruction variants are predicated on | |
| // different features [1]: | |
| // | |
| // * FMOPA (non-widening) | |
| // * half-precision - +sme2p1,+sme-f16f16 | |
| // * single-precision - +sme | |
| // * double-precision - +sme-f64f64 | |
| // * BFMOPA | |
| // * half-precision - +sme2p1,+b16b16 | |
| // | |
| // It should be possible to control lowering based on target features. | |
| // [1] | |
| // https://developer.arm.com/downloads/-/exploration-tools/feature-names-for-a-profile | |
| if ((vectorType.getRank() != 2) || !vectorType.allDimsScalable()) | |
| return false; | |
| auto elementType = vectorType.getElementType(); | |
| if (!elementType.isF16() && !elementType.isBF16() && | |
| !elementType.isF32() && !elementType.isF64()) | |
| return false; | |
| unsigned minNumElts = arm_sme::MinStreamingVectorLengthInBits / | |
| vectorType.getElementTypeBitWidth(); | |
| return vectorType.getShape() == | |
| ArrayRef<int64_t>({minNumElts, minNumElts}); | |
| }; | |
| // TODO: Support CombiningKind::Sub for outer products. | |
| if (outerProductOp.getKind() != arm_sme::CombiningKind::Add) | |
| return outerProductOp.emitError("unsupported kind"); | |
| auto resultVectorType = outerProductOp.getResultType(); | |
| if (!isSupportedType(resultVectorType)) | |
| return outerProductOp.emitError("unsupported type"); | |
| auto loc = outerProductOp.getLoc(); | |
| Value acc = outerProductOp.getAcc(); | |
| if (!acc) { | |
| // Initalize accumulator with zero. | |
| auto zero = arm_sme::ZeroOp::create(rewriter, loc, resultVectorType); | |
| zero.setTileId(tileId); | |
| acc = zero; | |
| } | |
| Value lhsMask = outerProductOp.getLhsMask(); | |
| Value rhsMask = outerProductOp.getRhsMask(); | |
| if (!lhsMask || !rhsMask) { | |
| auto predTy = | |
| outerProductOp.getLhsType().cloneWith({}, rewriter.getI1Type()); | |
| Value allActiveMask = arith::ConstantOp::create( | |
| rewriter, loc, DenseElementsAttr::get(predTy, true)); | |
| lhsMask = allActiveMask; | |
| rhsMask = allActiveMask; | |
| } | |
| // Create 'arm_sme.intr.mopa' outer product intrinsic. | |
| arm_sme::aarch64_sme_mopa::create(rewriter, loc, tileId, lhsMask, rhsMask, | |
| outerProductOp.getLhs(), | |
| outerProductOp.getRhs()); | |
| // The outerproduct intrinsics have no result, replace | |
| // 'arm_sme.outerproduct' with the input tile to preserve dataflow. | |
| rewriter.replaceOp(outerProductOp, acc); | |
| return success(); | |
| } | |
| }; | |
| /// Lower 2-way and 4-way widening outer products to intrinsics. | |
| template <class OuterProductWideningOp, class OuterProductWideningIntrOp> | |
| struct OuterProductWideningOpConversion | |
| : public ConvertArmSMEOpToLLVMPattern<OuterProductWideningOp> { | |
| using ConvertArmSMEOpToLLVMPattern< | |
| OuterProductWideningOp>::ConvertArmSMEOpToLLVMPattern; | |
| LogicalResult | |
| matchAndRewrite(OuterProductWideningOp op, | |
| typename OuterProductWideningOp::Adaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto tileId = getTileIdOrError(op); | |
| if (!tileId) | |
| return failure(); | |
| auto loc = op.getLoc(); | |
| Value acc = op.getAcc(); | |
| if (!acc) { | |
| // Initalize accumulator with zero. | |
| auto zero = arm_sme::ZeroOp::create(rewriter, loc, op.getResultType()); | |
| zero.setTileId(tileId); | |
| acc = zero; | |
| } | |
| Value lhsMask = op.getLhsMask(); | |
| Value rhsMask = op.getRhsMask(); | |
| if (!lhsMask || !rhsMask) { | |
| auto predTy = op.getLhsType().cloneWith({}, rewriter.getI1Type()); | |
| Value allActiveMask = arith::ConstantOp::create( | |
| rewriter, loc, DenseElementsAttr::get(predTy, true)); | |
| lhsMask = allActiveMask; | |
| rhsMask = allActiveMask; | |
| } | |
| OuterProductWideningIntrOp::create(rewriter, loc, tileId, lhsMask, rhsMask, | |
| adaptor.getLhs(), adaptor.getRhs()); | |
| // The outerproduct intrinsics have no result, replace | |
| // 'arm_sme.outerproduct' with the input tile to preserve dataflow. | |
| rewriter.replaceOp(op, acc); | |
| return success(); | |
| } | |
| }; | |
| /// Lower `arm_sme.streaming_vl` to SME CNTSD intrinsic. | |
| /// | |
| /// Example: | |
| /// | |
| /// %0 = arm_sme.streaming_vl <half> | |
| /// | |
| /// is converted to: | |
| /// | |
| /// %cnt = "arm_sme.intr.cntsd"() : () -> i64 | |
| /// %scale = arith.constant 4 : index | |
| /// %cntIndex = arith.index_cast %cnt : i64 to index | |
| /// %0 = arith.muli %cntIndex, %scale : index | |
| /// | |
| struct StreamingVLOpConversion | |
| : public ConvertArmSMEOpToLLVMPattern<arm_sme::StreamingVLOp, | |
| RequiresSpillsAndFills::No> { | |
| using ConvertArmSMEOpToLLVMPattern::ConvertArmSMEOpToLLVMPattern; | |
| LogicalResult | |
| matchAndRewrite(arm_sme::StreamingVLOp streamingVlOp, | |
| arm_sme::StreamingVLOp::Adaptor adaptor, | |
| ConversionPatternRewriter &rewriter) const override { | |
| auto loc = streamingVlOp.getLoc(); | |
| auto i64Type = rewriter.getI64Type(); | |
| auto cntsd = arm_sme::aarch64_sme_cntsd::create(rewriter, loc, i64Type); | |
| auto cntsdIdx = arith::IndexCastOp::create(rewriter, loc, | |
| rewriter.getIndexType(), cntsd); | |
| auto scale = arith::ConstantIndexOp::create( | |
| rewriter, loc, | |
| 8 / arm_sme::getSizeInBytes(streamingVlOp.getTypeSize())); | |
| rewriter.replaceOpWithNewOp<arith::MulIOp>(streamingVlOp, cntsdIdx, scale); | |
| return success(); | |
| } | |
| }; | |
| /// Merges consecutive `arm_sme.intr.zero` operations in a block by bitwise | |
| /// or-ing the zero masks. Note: In future the backend _should_ handle this. | |
| static void mergeConsecutiveTileZerosInBlock(Block *block) { | |
| uint32_t mergedZeroMask = 0; | |
| SmallVector<arm_sme::aarch64_sme_zero, 16> zeroOpsToMerge; | |
| auto replaceMergedZeroOps = [&] { | |
| llvm::scope_exit cleanup([&] { | |
| mergedZeroMask = 0; | |
| zeroOpsToMerge.clear(); | |
| }); | |
| if (zeroOpsToMerge.size() <= 1) | |
| return; | |
| IRRewriter rewriter(zeroOpsToMerge.front()); | |
| arm_sme::aarch64_sme_zero::create( | |
| rewriter, zeroOpsToMerge.front().getLoc(), | |
| rewriter.getI32IntegerAttr(mergedZeroMask)); | |
| for (auto zeroOp : zeroOpsToMerge) | |
| rewriter.eraseOp(zeroOp); | |
| }; | |
| for (Operation &op : *block) { | |
| if (auto zeroOp = dyn_cast<arm_sme::aarch64_sme_zero>(op)) { | |
| mergedZeroMask |= zeroOp.getTileMask(); | |
| zeroOpsToMerge.push_back(zeroOp); | |
| } else { | |
| replaceMergedZeroOps(); | |
| } | |
| } | |
| replaceMergedZeroOps(); | |
| } | |
| } // namespace | |
| namespace { | |
| struct ConvertArmSMEToLLVMPass | |
| : public impl::ConvertArmSMEToLLVMBase<ConvertArmSMEToLLVMPass> { | |
| ConvertArmSMEToLLVMPass(bool dumpTileLiveRanges) { | |
| this->dumpTileLiveRanges = dumpTileLiveRanges; | |
| } | |
| void runOnOperation() override { | |
| auto function = getOperation(); | |
| if (failed(arm_sme::allocateSMETiles(function, dumpTileLiveRanges))) | |
| return signalPassFailure(); | |
| LLVMConversionTarget target(getContext()); | |
| RewritePatternSet patterns(&getContext()); | |
| LLVMTypeConverter converter(&getContext()); | |
| configureArmSMEToLLVMConversionLegality(target); | |
| populateArmSMEToLLVMConversionPatterns(converter, patterns); | |
| if (failed(applyPartialConversion(function, target, std::move(patterns)))) | |
| signalPassFailure(); | |
| function->walk(mergeConsecutiveTileZerosInBlock); | |
| // Walk the function and fail if there are unexpected operations on SME | |
| // tile types after conversion. | |
| function->walk([&](Operation *op) { | |
| // These ops are legal post conversion, skip these. | |
| if (isa<arm_sme::CopyTileOp, arm_sme::GetTileOp, cf::BranchOp>(op) || | |
| !op->isRegistered()) | |
| return; | |
| auto isSMETileType = [](Type type) { | |
| return arm_sme::isValidSMETileVectorType(type); | |
| }; | |
| if (llvm::any_of(op->getResultTypes(), isSMETileType) || | |
| llvm::any_of(op->getOperandTypes(), isSMETileType)) { | |
| op->emitOpError("unexpected operation with SME tile type after " | |
| "conversion to LLVM"); | |
| signalPassFailure(); | |
| } | |
| }); | |
| } | |
| }; | |
| } // namespace | |
| void mlir::configureArmSMEToLLVMConversionLegality(ConversionTarget &target) { | |
| target.addIllegalDialect<arm_sme::ArmSMEDialect>(); | |
| target.addLegalOp< | |
| arm_sme::aarch64_sme_zero, arm_sme::aarch64_sme_str, | |
| arm_sme::aarch64_sme_ld1b_horiz, arm_sme::aarch64_sme_ld1h_horiz, | |
| arm_sme::aarch64_sme_ld1w_horiz, arm_sme::aarch64_sme_ld1d_horiz, | |
| arm_sme::aarch64_sme_ld1q_horiz, arm_sme::aarch64_sme_st1b_horiz, | |
| arm_sme::aarch64_sme_st1h_horiz, arm_sme::aarch64_sme_st1w_horiz, | |
| arm_sme::aarch64_sme_st1d_horiz, arm_sme::aarch64_sme_st1q_horiz, | |
| arm_sme::aarch64_sme_ld1b_vert, arm_sme::aarch64_sme_ld1h_vert, | |
| arm_sme::aarch64_sme_ld1w_vert, arm_sme::aarch64_sme_ld1d_vert, | |
| arm_sme::aarch64_sme_ld1q_vert, arm_sme::aarch64_sme_st1b_vert, | |
| arm_sme::aarch64_sme_st1h_vert, arm_sme::aarch64_sme_st1w_vert, | |
| arm_sme::aarch64_sme_st1d_vert, arm_sme::aarch64_sme_st1q_vert, | |
| arm_sme::aarch64_sme_read_horiz, arm_sme::aarch64_sme_read_vert, | |
| arm_sme::aarch64_sme_write_horiz, arm_sme::aarch64_sme_write_vert, | |
| arm_sme::aarch64_sme_mopa, arm_sme::aarch64_sme_mopa_wide, | |
| arm_sme::aarch64_sme_mops_wide, arm_sme::aarch64_sme_smopa_wide, | |
| arm_sme::aarch64_sme_smops_wide, arm_sme::aarch64_sme_umopa_wide, | |
| arm_sme::aarch64_sme_umops_wide, arm_sme::aarch64_sme_smopa_za32, | |
| arm_sme::aarch64_sme_smops_za32, arm_sme::aarch64_sme_umopa_za32, | |
| arm_sme::aarch64_sme_umops_za32, arm_sme::aarch64_sme_sumopa_wide, | |
| arm_sme::aarch64_sme_sumops_wide, arm_sme::aarch64_sme_usmopa_wide, | |
| arm_sme::aarch64_sme_usmops_wide, arm_sme::aarch64_sme_cntsd>(); | |
| target.addLegalDialect<arith::ArithDialect, | |
| /* The following are used to lower tile spills/fills */ | |
| vector::VectorDialect, scf::SCFDialect, | |
| memref::MemRefDialect>(); | |
| // Pseudo operations. These cannot be code-generated but may exist in the | |
| // input IR, or be generated during the conversion. They need to be eliminated | |
| // before the final conversion to LLVM IR (and likely will be due to DCE). | |
| target.addLegalOp<arm_sme::GetTileOp, arm_sme::CopyTileOp, | |
| UnrealizedConversionCastOp>(); | |
| } | |
| void mlir::populateArmSMEToLLVMConversionPatterns(LLVMTypeConverter &converter, | |
| RewritePatternSet &patterns) { | |
| converter.addConversion([&](VectorType type) -> std::optional<Type> { | |
| // There's no LLVM type for SME tiles, but after lowering to intrinsics all | |
| // SME vector types should be eliminated. | |
| if (arm_sme::isValidSMETileVectorType(type)) | |
| return type; | |
| return std::nullopt; | |
| }); | |
| addArmSMEConversionPatterns< | |
| LoadTileSliceConversion, ExtractTileSliceConversion, | |
| InsertTileSliceConversion, StoreTileSliceConversion, | |
| StreamingVLOpConversion, OuterProductOpConversion, | |
| OuterProductWideningOpConversion<arm_sme::FMopa2WayOp, | |
| arm_sme::aarch64_sme_mopa_wide>, | |
| OuterProductWideningOpConversion<arm_sme::FMops2WayOp, | |
| arm_sme::aarch64_sme_mops_wide>, | |
| OuterProductWideningOpConversion<arm_sme::SMopa2WayOp, | |
| arm_sme::aarch64_sme_smopa_za32>, | |
| OuterProductWideningOpConversion<arm_sme::SMops2WayOp, | |
| arm_sme::aarch64_sme_smops_za32>, | |
| OuterProductWideningOpConversion<arm_sme::UMopa2WayOp, | |
| arm_sme::aarch64_sme_umopa_za32>, | |
| OuterProductWideningOpConversion<arm_sme::UMops2WayOp, | |
| arm_sme::aarch64_sme_umops_za32>, | |
| OuterProductWideningOpConversion<arm_sme::SMopa4WayOp, | |
| arm_sme::aarch64_sme_smopa_wide>, | |
| OuterProductWideningOpConversion<arm_sme::SMops4WayOp, | |
| arm_sme::aarch64_sme_smops_wide>, | |
| OuterProductWideningOpConversion<arm_sme::UMopa4WayOp, | |
| arm_sme::aarch64_sme_umopa_wide>, | |
| OuterProductWideningOpConversion<arm_sme::UMops4WayOp, | |
| arm_sme::aarch64_sme_umops_wide>, | |
| OuterProductWideningOpConversion<arm_sme::SuMopa4WayOp, | |
| arm_sme::aarch64_sme_sumopa_wide>, | |
| OuterProductWideningOpConversion<arm_sme::SuMops4WayOp, | |
| arm_sme::aarch64_sme_sumops_wide>, | |
| OuterProductWideningOpConversion<arm_sme::UsMopa4WayOp, | |
| arm_sme::aarch64_sme_usmopa_wide>, | |
| OuterProductWideningOpConversion<arm_sme::UsMops4WayOp, | |
| arm_sme::aarch64_sme_usmops_wide>, | |
| ZeroOpConversion>(patterns, converter); | |
| } | |
| std::unique_ptr<Pass> | |
| mlir::createConvertArmSMEToLLVMPass(bool dumpTileLiveRanges) { | |
| return std::make_unique<ConvertArmSMEToLLVMPass>(dumpTileLiveRanges); | |
| } |