//===- XeGPUDialect.cpp - MLIR XeGPU dialect implementation -----*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// #include "mlir/Dialect/Affine/Utils.h" #include "mlir/Dialect/Arith/Utils/Utils.h" #include "mlir/Dialect/GPU/IR/GPUDialect.h" #include "mlir/Dialect/LLVMIR/XeVMDialect.h" #include "mlir/Dialect/Utils/IndexingUtils.h" #include "mlir/Dialect/XeGPU/IR/XeGPU.h" #include "mlir/Dialect/XeGPU/uArch/IntelGpuXe2.h" #include "mlir/IR/Builders.h" #include "mlir/IR/DialectImplementation.h" #include "llvm/ADT/SmallVectorExtras.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/Debug.h" using std::optional; namespace mlir { namespace xegpu { void XeGPUDialect::initialize() { addTypes< #define GET_TYPEDEF_LIST #include >(); addOperations< #define GET_OP_LIST #include >(); addAttributes< #define GET_ATTRDEF_LIST #include >(); } #define GET_OP_INTERFACE_CLASSES #include "mlir/Dialect/XeGPU/IR/XeGPUOpInterface.cpp.inc" // A `srcShape` consists of N distribution units, each being `subShapesLayout` x // `subShape`. A `delinearizedId` is used to identify a particular `subShape` // within each distribution unit. // Example: // WG data is 128x256. SG data is 16x32, in 4x2 layout, this gives a // distribution unit of shape 64x64, we have 2x4 such distribution units. // `delinearizedId` is used to identify a 16x32 of a subgroup in each // distribution unit. static SmallVector> genCoordinates(OpBuilder &builder, Location loc, SmallVector delinearizedId, ArrayRef subShapesLayout, ArrayRef subShape, ArrayRef srcShape) { SmallVector> coordinates; // A distribution unit must be less than or equal to `srcShape` SmallVector distUnitShape = llvm::map_to_vector( llvm::zip_equal(srcShape, computeElementwiseMul(subShapesLayout, subShape)), [](const auto &t) { return std::min(std::get<0>(t), std::get<1>(t)); }); // Get the offset of `subShape` within a distribution unit. SmallVector distUnitLocalOffset = llvm::map_to_vector( llvm::zip(delinearizedId, subShape), [&](const auto &t) -> Value { return builder.createOrFold( loc, std::get<0>(t), builder.createOrFold(loc, std::get<1>(t))); }); // For each dist unit for (SmallVector unitOffs : StaticTileOffsetRange(srcShape, distUnitShape)) { // Get dist unit offset within `srcShape`. SmallVector base = llvm::map_to_vector(unitOffs, [&](int64_t d) -> Value { return arith::ConstantIndexOp::create(builder, loc, d); }); // Calculate `subShape` offset within `srcShape`. SmallVector adds = llvm::map_to_vector(llvm::zip_equal(base, distUnitLocalOffset), [&](const auto &t) -> Value { return builder.createOrFold( loc, std::get<0>(t), std::get<1>(t)); }); // Do not go beyond `srcShape` bounds. SmallVector mods = llvm::map_to_vector( llvm::zip_equal(adds, srcShape), [&](const auto &t) -> Value { return builder.createOrFold( loc, std::get<0>(t), arith::ConstantIndexOp::create(builder, loc, std::get<1>(t))); }); coordinates.push_back(mods); } return coordinates; } static SmallVector> genStaticCoordinates( llvm::ArrayRef canonicalIds, llvm::ArrayRef layout, llvm::ArrayRef subShape, llvm::ArrayRef shape) { // Compute distribution unit shape (clamped to srcShape). SmallVector distUnitShape(shape.size()); for (size_t i = 0; i < shape.size(); ++i) distUnitShape[i] = std::min(shape[i], layout[i] * subShape[i]); // Compute local offset of this ID within a distribution unit. SmallVector localOffset(shape.size()); for (size_t i = 0; i < shape.size(); ++i) localOffset[i] = canonicalIds[i] * subShape[i]; // Enumerate all distribution units and compute coordinates. SmallVector> coordinates; for (SmallVector unitOffs : StaticTileOffsetRange(shape, distUnitShape)) { SmallVector coord(shape.size()); for (size_t i = 0; i < shape.size(); ++i) coord[i] = (unitOffs[i] + localOffset[i]) % shape[i]; coordinates.push_back(coord); } return coordinates; } // Checks if the given memref type represents shared local memory (SLM). bool XeGPUDialect::isSharedMemory(const MemRefType &memrefTy) { Attribute attr = memrefTy.getMemorySpace(); if (!attr) return false; // Default memory space is not shared local memory if (auto intAttr = llvm::dyn_cast_if_present(attr)) return intAttr.getInt() == 3; if (auto memrefSpace = llvm::dyn_cast_if_present(attr)) return memrefSpace.getValue() == MemorySpace::SLM; if (auto xevmSpace = llvm::dyn_cast_if_present(attr)) return xevmSpace.getValue() == xevm::AddrSpace::SHARED; return gpu::GPUDialect::isWorkgroupMemoryAddressSpace(attr); } //===----------------------------------------------------------------------===// // XeGPU_BlockTensorDescAttr //===----------------------------------------------------------------------===// BlockTensorDescAttr BlockTensorDescAttr::get(mlir::MLIRContext *context, xegpu::MemorySpace memory_space, int array_length, bool boundary_check) { auto scopeAttr = MemorySpaceAttr::get(context, memory_space); auto lengthAttr = IntegerAttr::get(IntegerType::get(context, 64), array_length); auto boundaryAttr = BoolAttr::get(context, boundary_check); return Base::get(context, scopeAttr, lengthAttr, boundaryAttr); } bool BlockTensorDescAttr::hasDefaultsOnly() { return getMemorySpace().getValue() == xegpu::MemorySpace::Global && getArrayLength().getInt() == 1 && getBoundaryCheck().getValue(); } //===----------------------------------------------------------------------===// // XeGPU_LayoutAttr //===----------------------------------------------------------------------===// LogicalResult LayoutAttr::verify(llvm::function_ref emitError, DenseI32ArrayAttr sg_layout, DenseI32ArrayAttr sg_data, DenseI32ArrayAttr inst_data, DenseI32ArrayAttr lane_layout, DenseI32ArrayAttr lane_data, DenseI32ArrayAttr order) { // Special case for store_matrix if (!sg_layout && !inst_data && !lane_layout) return success(); // generate code to check sg_laout, inst_data and lane_layout having the same // rank if they are not null. if (sg_layout && inst_data && sg_layout.size() != inst_data.size()) { return emitError() << "expected sg_layout and inst_data to have the same rank"; } if (sg_layout && lane_layout && sg_layout.size() != lane_layout.size()) { return emitError() << "expected sg_layout and lane_layout to have the same rank"; } if (inst_data && lane_layout && inst_data.size() != lane_layout.size()) { return emitError() << "expected inst_data and lane_layout to have the same " "rank, got inst_data " << inst_data.size() << ", lane_layout " << lane_layout.size(); } if ((sg_layout && !sg_data) || (!sg_layout && sg_data)) return emitError() << "sg_layout and sg_data must be used together"; if (sg_layout && sg_data && sg_layout.size() != sg_data.size()) return emitError() << "expected sg_data and sg_layout to have the same rank"; if ((lane_layout && !lane_data) || (!lane_layout && lane_data)) return emitError() << "lane_layout and lane_data must be used together"; if (lane_layout && lane_data && lane_layout.size() != lane_data.size()) return emitError() << "expected lane_data and lane_layout to have the same rank"; if (order) { if (!sg_layout && !lane_layout) return emitError() << "expected sg_layout/lane_layout being used with order"; if (sg_layout && order.size() != sg_layout.size()) return emitError() << "expected order and sg_layout to have the same rank"; if (lane_layout && order.size() != lane_layout.size()) return emitError() << "expected order and lane_layout to have the same rank"; } return success(); } FailureOr> LayoutAttr::delinearizeId(OpBuilder &builder, Location loc, Value linearId) { SmallVector sgLayoutInt; if (isForWorkgroup()) { sgLayoutInt = getEffectiveSgLayoutAsInt(); } else if (isForSubgroup()) { sgLayoutInt = getEffectiveLaneLayoutAsInt(); } else { return failure(); } DenseI32ArrayAttr orderAttr = getOrder(); // Handle order attribute SmallVector order; if (orderAttr && !orderAttr.empty()) { order = llvm::map_to_vector(orderAttr.asArrayRef(), [](int32_t idx) { return static_cast(idx); }); } else { // Default order: [1, 0] for 2D (row-major), [2, 1, 0] for 3D, etc. order = llvm::to_vector( llvm::reverse(llvm::seq(0, sgLayoutInt.size()))); } if (order.size() != sgLayoutInt.size()) { return failure(); } SmallVector result(sgLayoutInt.size()); Value remaining = linearId; /// Process dimensions in the order they appear in the order array /// The first dimension in order is the fastest-changing /// /// Example walkthrough for linearId=22, sgLayout=[2,4,4], order=[2,1,0]: /// /// Initial: remaining=22, dimIdx = order[i], dimSize = sgLayout[dimIdx], /// result=[?,?,?] /// /// i=0 (process columns, dimIdx=2, dimSize=4): /// result[2] = 22 % 4 = 2 (column coordinate) /// remaining = 22 / 4 = 5 (5 complete groups of 4 columns processed) /// /// i=1 (process rows, dimIdx=1, dimSize=4): /// result[1] = 5 % 4 = 1 (row coordinate) /// remaining = 5 / 4 = 1 (1 complete group of 4 rows processed) /// /// i=2 (process layers, dimIdx=0, dimSize=2): /// result[0] = 1 % 2 = 1 (layer coordinate) /// (no remaining update - last iteration) /// /// Final result: [1,1,2] = Layer 1, Row 1, Column 2 for (size_t i = 0; i < order.size(); ++i) { int64_t dimIdx = order[i]; int64_t dimSize = sgLayoutInt[dimIdx]; Value dimSizeVal = builder.createOrFold(loc, dimSize); /// Extract the coordinate for this dimension using modulo operation /// This gives us "how far within this dimension" we are /// e.g., linearId=22, dimSize=4: 22 % 4 = 2 (we're at position 2 within /// this dimension) result[dimIdx] = builder.createOrFold(loc, remaining, dimSizeVal); /// Update remaining for the next dimension by removing what we've already /// processed. Division tells us "how many complete groups of this dimension /// we've gone through" e.g., linearId=22, dimSize=4: 22 / 4 = 5 (we've /// completed 5 groups of 4) Skip this for the last iteration since there's /// no next dimension to process if (i < order.size() - 1) { remaining = builder.createOrFold(loc, remaining, dimSizeVal); } } return result; } /// Implements DistributeLayoutAttr::computeDistributedCoords to generate /// instructions for computing multi-dimensional offsets when distributed by /// LayoutAttr. FailureOr>> LayoutAttr::computeDistributedCoords(OpBuilder &builder, Location loc, Value linearId, ArrayRef shape) { SmallVector layout; SmallVector subShape; if (isForWorkgroup()) { layout = getEffectiveSgLayoutAsInt(); subShape = getEffectiveSgDataAsInt(); } else if (isForSubgroup()) { layout = getEffectiveLaneLayoutAsInt(); subShape = getEffectiveLaneDataAsInt(); } else { return failure(); } assert(!subShape.empty() && "sgdata or lanedata cannot be empty for " "distributed coordinates computation"); // delinearize Ids auto maybeIds = delinearizeId(builder, loc, linearId); if (failed(maybeIds)) return failure(); SmallVector ids = *maybeIds; return genCoordinates(builder, loc, ids, layout, subShape, shape); } bool LayoutAttr::isEqualTo(const xegpu::DistributeLayoutAttr &other) { if (dyn_cast(other)) return false; return *this == dyn_cast(other); } /// Implements DistributeLayoutAttr::computeStaticDistributedCoords to /// compute multi-dimensional offsets for a given linear ID when distributed by /// LayoutAttr. SmallVector> LayoutAttr::computeStaticDistributedCoords(int64_t linearId, ArrayRef shape) { SmallVector layoutVec; SmallVector subShape; SmallVector instData; if (isForWorkgroup()) { layoutVec = getEffectiveSgLayoutAsInt(); subShape = getEffectiveSgDataAsInt(); } else if (isForSubgroup()) { instData = getEffectiveInstDataAsInt(); layoutVec = getEffectiveLaneLayoutAsInt(); subShape = getEffectiveLaneDataAsInt(); } if (!instData.empty()) { linearId = 0; subShape = instData; } assert(!subShape.empty() && "sgdata or lanedata cannot be empty"); // Delinearize the linear ID using the order attribute. SmallVector order = getEffectiveOrderAsInt(); SmallVector delinearizedId(layoutVec.size()); int64_t remaining = linearId; for (size_t i = 0; i < order.size(); ++i) { int64_t dimIdx = order[i]; delinearizedId[dimIdx] = remaining % layoutVec[dimIdx]; remaining = remaining / layoutVec[dimIdx]; } return genStaticCoordinates(delinearizedId, layoutVec, subShape, shape); } // set the layout for unit dims: sg_data, inst_data and lane_data to 1 DistributeLayoutAttr LayoutAttr::setUnitDimData(SmallVector unitDims) const { auto sgDataOpt = getSgData(); auto instDataOpt = getInstData(); auto laneDataOpt = getLaneData(); SmallVector sgData; SmallVector instData; SmallVector laneData; if (sgDataOpt) sgData = llvm::to_vector(sgDataOpt.asArrayRef()); if (instDataOpt) instData = llvm::to_vector(instDataOpt.asArrayRef()); if (laneDataOpt) laneData = llvm::to_vector(laneDataOpt.asArrayRef()); for (auto dim : unitDims) { if (dim < static_cast(sgData.size())) sgData[dim] = 1; if (dim < static_cast(instData.size())) instData[dim] = 1; if (dim < static_cast(laneData.size())) laneData[dim] = 1; } return LayoutAttr::get( getContext(), getSgLayout(), sgData.empty() ? DenseI32ArrayAttr() : DenseI32ArrayAttr::get(getContext(), sgData), instData.empty() ? DenseI32ArrayAttr() : DenseI32ArrayAttr::get(getContext(), instData), getLaneLayout(), laneData.empty() ? DenseI32ArrayAttr() : DenseI32ArrayAttr::get(getContext(), laneData), getOrder()); } // set the layout for the sepcified unit dims: sg_lane and lane_layout to 1 DistributeLayoutAttr LayoutAttr::setUnitDimLayout(SmallVector unitDims) const { auto sgLayoutOpt = getSgLayout(); auto laneLayoutOpt = getLaneLayout(); SmallVector sgLayout; SmallVector laneLayout; if (sgLayoutOpt) sgLayout = llvm::to_vector(sgLayoutOpt.asArrayRef()); if (laneLayoutOpt) laneLayout = llvm::to_vector(laneLayoutOpt.asArrayRef()); for (auto dim : unitDims) { if (dim < static_cast(sgLayout.size())) sgLayout[dim] = 1; if (dim < static_cast(laneLayout.size())) laneLayout[dim] = 1; } return LayoutAttr::get( getContext(), sgLayout.empty() ? DenseI32ArrayAttr() : DenseI32ArrayAttr::get(getContext(), sgLayout), getSgData(), getInstData(), laneLayout.empty() ? DenseI32ArrayAttr() : DenseI32ArrayAttr::get(getContext(), laneLayout), getLaneData(), getOrder()); } // Derive a new layout with sg_data, inst_data and lane_data set to the // specified values for the given dimension DistributeLayoutAttr LayoutAttr::setDimData(int64_t dim, int64_t sgData, int64_t instData, int64_t laneData) { SmallVector sgDataVec = getEffectiveSgDataAsInt(); SmallVector instDataVec = getEffectiveInstDataAsInt(); SmallVector laneDataVec = getEffectiveLaneDataAsInt(); if (dim < static_cast(sgDataVec.size()) && sgData != -1) sgDataVec[dim] = sgData; if (dim < static_cast(instDataVec.size()) && instData != -1) instDataVec[dim] = instData; if (dim < static_cast(laneDataVec.size()) && laneData != -1) laneDataVec[dim] = laneData; SmallVector sgDataVec32(sgDataVec.begin(), sgDataVec.end()); SmallVector instDataVec32(instDataVec.begin(), instDataVec.end()); SmallVector laneDataVec32(laneDataVec.begin(), laneDataVec.end()); return LayoutAttr::get( getContext(), getSgLayout(), sgDataVec.empty() ? DenseI32ArrayAttr() : DenseI32ArrayAttr::get(getContext(), sgDataVec32), instDataVec.empty() ? DenseI32ArrayAttr() : DenseI32ArrayAttr::get(getContext(), instDataVec32), getLaneLayout(), laneDataVec.empty() ? DenseI32ArrayAttr() : DenseI32ArrayAttr::get(getContext(), laneDataVec32), getOrder()); } // Derive a new layout by removing dimensions. // `dimGroup` specifies a group of dimensions to be removed in the derived // layout. DistributeLayoutAttr LayoutAttr::dropDims(SmallVector dimGroup) { SmallVector sgLayout = getEffectiveSgLayoutAsInt(); SmallVector sgData = getEffectiveSgDataAsInt(); SmallVector instData = getEffectiveInstDataAsInt(); SmallVector laneLayout = getEffectiveLaneLayoutAsInt(); SmallVector laneData = getEffectiveLaneDataAsInt(); SmallVector origOrder = getEffectiveOrderAsInt(); SmallVector sortedDimGroup = dimGroup; llvm::sort(sortedDimGroup); for (auto dimIdx : llvm::reverse(sortedDimGroup)) { if (!sgLayout.empty()) { sgLayout.erase(sgLayout.begin() + dimIdx); sgData.erase(sgData.begin() + dimIdx); } if (!instData.empty()) instData.erase(instData.begin() + dimIdx); if (!laneLayout.empty()) { laneLayout.erase(laneLayout.begin() + dimIdx); laneData.erase(laneData.begin() + dimIdx); } } SmallVector newOrder; for (int64_t d : origOrder) { if (llvm::is_contained(dimGroup, d)) continue; int64_t offset = llvm::count_if(dimGroup, [&](int64_t s) { return s < d; }); newOrder.push_back(d - offset); } if ((sgLayout.empty() && laneLayout.empty()) || newOrder.size() == 1) newOrder.clear(); auto toAttr = [&](ArrayRef v) -> DenseI32ArrayAttr { if (v.empty()) return DenseI32ArrayAttr(); SmallVector v32(v.begin(), v.end()); return DenseI32ArrayAttr::get(getContext(), v32); }; auto droppedLayout = xegpu::LayoutAttr::get( getContext(), toAttr(sgLayout), toAttr(sgData), toAttr(instData), toAttr(laneLayout), toAttr(laneData), toAttr(newOrder)); return droppedLayout; } // Derive a new layout by collapsing dimensions. // `dimGroup` specifies a group of adjacent dimensions // that are collapsed into a single dimension in the derived layout. DistributeLayoutAttr LayoutAttr::collapseDims(SmallVector dimGroup) { SmallVector sgLayout = getEffectiveSgLayoutAsInt(); SmallVector sgData = getEffectiveSgDataAsInt(); SmallVector instData = getEffectiveInstDataAsInt(); SmallVector laneLayout = getEffectiveLaneLayoutAsInt(); SmallVector laneData = getEffectiveLaneDataAsInt(); SmallVector origOrder = getEffectiveOrderAsInt(); SmallVector sortedDimGroup = dimGroup; llvm::sort(sortedDimGroup); int64_t dimBeforeCurrent = -1; for (auto dimIdx : sortedDimGroup) { // when order attr is present, adjacency dims are values like [3, 2, 1, 0] // in decreasing order; otherwise based on dim indices like [0, 1, 2, 3] // in increasing order if (dimBeforeCurrent >= 0) { if (getOrder() && !getOrder().empty()) { int64_t orderBefore = origOrder[dimBeforeCurrent]; int64_t orderCurrent = origOrder[dimIdx]; if (orderBefore != (orderCurrent - 1)) llvm::report_fatal_error( "dimensions being collapsed must be adjacent in order"); } else { if (dimIdx != (dimBeforeCurrent + 1)) llvm::report_fatal_error( "dimensions being collapsed must be adjacent"); } } dimBeforeCurrent = dimIdx; } int firstDim = sortedDimGroup.front(); // collapse the dimensions in dimGroup into one dimension by multiplying their // sizes together if (!sgLayout.empty()) { int64_t collapsedSglayout = 1, collapsedSgData = 1; for (auto dimIdx : dimGroup) { collapsedSglayout *= sgLayout[dimIdx]; collapsedSgData *= sgData[dimIdx]; } for (auto dimIdx : llvm::reverse(sortedDimGroup)) { sgLayout.erase(sgLayout.begin() + dimIdx, sgLayout.begin() + dimIdx + 1); sgData.erase(sgData.begin() + dimIdx, sgData.begin() + dimIdx + 1); } sgLayout.insert(sgLayout.begin() + firstDim, collapsedSglayout); sgData.insert(sgData.begin() + firstDim, collapsedSgData); } if (!instData.empty()) { int64_t collapsedInstData = 1; for (auto dimIdx : dimGroup) collapsedInstData *= instData[dimIdx]; for (auto dimIdx : llvm::reverse(sortedDimGroup)) instData.erase(instData.begin() + dimIdx, instData.begin() + dimIdx + 1); instData.insert(instData.begin() + firstDim, collapsedInstData); } if (!laneLayout.empty()) { int64_t collapsedLaneLayout = 1, collapsedLaneData = 1; for (auto dimIdx : dimGroup) { collapsedLaneLayout *= laneLayout[dimIdx]; collapsedLaneData *= laneData[dimIdx]; } for (auto dimIdx : llvm::reverse(sortedDimGroup)) { laneLayout.erase(laneLayout.begin() + dimIdx, laneLayout.begin() + dimIdx + 1); laneData.erase(laneData.begin() + dimIdx, laneData.begin() + dimIdx + 1); } laneLayout.insert(laneLayout.begin() + firstDim, collapsedLaneLayout); laneData.insert(laneData.begin() + firstDim, collapsedLaneData); } SmallVector newOrder; DenseI32ArrayAttr orderAttr = getOrder(); if (orderAttr && !orderAttr.empty()) { for (auto dimIdx : llvm::reverse(sortedDimGroup)) { if (dimIdx != firstDim) origOrder.erase(origOrder.begin() + dimIdx); } // say we have orderVec = {5, 3, 2, 1, 0} // Create indices [0, 1, 2, 3, 4] SmallVector indices = llvm::to_vector(llvm::seq(0, origOrder.size())); // Sort indices based on corresponding values llvm::sort(indices, [&](size_t a, size_t b) { return origOrder[a] < origOrder[b]; }); newOrder = llvm::to_vector(llvm::map_range( indices, [&](size_t i) { return static_cast(i); })); } auto toAttr = [&](ArrayRef v) -> DenseI32ArrayAttr { if (v.empty()) return DenseI32ArrayAttr(); SmallVector v32(v.begin(), v.end()); return DenseI32ArrayAttr::get(getContext(), v32); }; auto collapsedLayout = xegpu::LayoutAttr::get( getContext(), toAttr(sgLayout), toAttr(sgData), toAttr(instData), toAttr(laneLayout), toAttr(laneData), toAttr(newOrder)); return collapsedLayout; } // Derive a new layout by transpose the layout using `permutation`. DistributeLayoutAttr LayoutAttr::transposeDims(ArrayRef permutation) { SmallVector origSgLayout = getEffectiveSgLayoutAsInt(); SmallVector origSgData = getEffectiveSgDataAsInt(); SmallVector origInstData = getEffectiveInstDataAsInt(); SmallVector origLaneLayout = getEffectiveLaneLayoutAsInt(); SmallVector origLaneData = getEffectiveLaneDataAsInt(); SmallVector origOrder = getEffectiveOrderAsInt(); SmallVector sgLayout; SmallVector sgData; SmallVector instData; SmallVector laneLayout; SmallVector laneData; SmallVector order; for (int64_t idx : permutation) { if (!origLaneLayout.empty()) { laneLayout.push_back(static_cast(origLaneLayout[idx])); laneData.push_back(static_cast(origLaneData[idx])); } if (!origInstData.empty()) instData.push_back(static_cast(origInstData[idx])); if (!origSgLayout.empty()) { sgLayout.push_back(static_cast(origSgLayout[idx])); sgData.push_back(static_cast(origSgData[idx])); } order.push_back(static_cast(origOrder[idx])); } if (origLaneLayout.empty() && origSgLayout.empty()) order.clear(); auto toAttr = [&](ArrayRef v) -> DenseI32ArrayAttr { return v.empty() ? nullptr : DenseI32ArrayAttr::get(getContext(), v); }; return xegpu::LayoutAttr::get(getContext(), toAttr(sgLayout), toAttr(sgData), toAttr(instData), toAttr(laneLayout), toAttr(laneData), toAttr(order)); } /// Check if this layout is a transpose of another layout. bool LayoutAttr::isTransposeOf(const xegpu::DistributeLayoutAttr &other, ArrayRef perm, const xegpu::LayoutKind kind) { if (!other) return false; if (getRank() != other.getRank() || perm.size() != static_cast(getRank())) return false; if (!isPermutationVector(perm)) return false; auto checkTranspose = [](ArrayRef dst, ArrayRef src, ArrayRef perm) { for (const auto &ta : llvm::enumerate(perm)) { if (src[ta.index()] != dst[ta.value()]) return false; } return true; }; if (kind == xegpu::LayoutKind::Subgroup) return checkTranspose(getEffectiveSgLayoutAsInt(), other.getEffectiveSgLayoutAsInt(), perm) && checkTranspose(getEffectiveSgDataAsInt(), other.getEffectiveSgDataAsInt(), perm) && checkTranspose(getEffectiveOrderAsInt(), other.getEffectiveOrderAsInt(), perm); if (kind == xegpu::LayoutKind::InstData) return checkTranspose(getEffectiveInstDataAsInt(), other.getEffectiveInstDataAsInt(), perm); if (kind == xegpu::LayoutKind::Lane) return checkTranspose(getEffectiveLaneLayoutAsInt(), other.getEffectiveLaneLayoutAsInt(), perm) && checkTranspose(getEffectiveLaneDataAsInt(), other.getEffectiveLaneDataAsInt(), perm) && checkTranspose(getEffectiveOrderAsInt(), other.getEffectiveOrderAsInt(), perm); return false; } bool LayoutAttr::isCompatibleWith(const xegpu::DistributeLayoutAttr &other, SmallVector shape, xegpu::LayoutKind level) { if (!other) return false; if (getEffectiveOrderAsInt() == other.getEffectiveOrderAsInt()) { // short cut when order is the same, no need to compute coords and compare if (level == xegpu::LayoutKind::Subgroup) if (getEffectiveSgLayoutAsInt() == other.getEffectiveSgLayoutAsInt() && getEffectiveSgDataAsInt() == other.getEffectiveSgDataAsInt()) return true; if (level == xegpu::LayoutKind::Lane) if (getEffectiveLaneLayoutAsInt() == other.getEffectiveLaneLayoutAsInt() && getEffectiveLaneDataAsInt() == other.getEffectiveLaneDataAsInt()) return true; } auto compareCoordsForAllIds = [&](int64_t size) { for (int64_t id : llvm::seq(0, size)) { auto coords = computeStaticDistributedCoords(id, shape); auto otherCoords = other.computeStaticDistributedCoords(id, shape); if (coords != otherCoords) return false; } return true; }; if (level == xegpu::LayoutKind::Subgroup) { int64_t wgSize = computeProduct(getEffectiveSgLayoutAsInt()); return compareCoordsForAllIds(wgSize); } if (level == xegpu::LayoutKind::InstData) { return (getEffectiveInstDataAsInt() == other.getEffectiveInstDataAsInt()); } if (level == xegpu::LayoutKind::Lane) { int64_t subgroupSize = computeProduct(getEffectiveLaneLayoutAsInt()); return compareCoordsForAllIds(subgroupSize); } return true; } //===----------------------------------------------------------------------===// // XeGPU_SliceAttr //===----------------------------------------------------------------------===// LogicalResult SliceAttr::verify(llvm::function_ref emitError, xegpu::DistributeLayoutAttr parent, DenseI64ArrayAttr dims) { if (!dims) return emitError() << "expected dims attribute"; // check every element in dims is unique and smaller than rank llvm::SmallDenseSet seen; for (int64_t dim : dims.asArrayRef()) { if (dim < 0) return emitError() << "invalid dim (" << dim << ") in slice attribute."; if (!seen.insert(dim).second) return emitError() << "repeated dim (" << dim << ") in slice attribute."; } return success(); } SliceAttr SliceAttr::flatten() const { xegpu::DistributeLayoutAttr parent = getParent(); SmallVector slicedDims({getDims()}); while (auto sliceAttr = dyn_cast(parent)) { parent = sliceAttr.getParent(); slicedDims.push_back(sliceAttr.getDims()); } auto layoutAttr = dyn_cast(parent); SmallVector indices = llvm::to_vector(llvm::seq(0, layoutAttr.getRank())); // get remaining dims (flattened) by applying slice ops with all slicedDims SmallVector remainingDims(indices); for (auto dim : llvm::reverse(slicedDims)) remainingDims = XeGPUDialect::slice(llvm::ArrayRef(remainingDims), dim.asArrayRef()); // get flattened sliced dims by applying slice ops with the remaining dims SmallVector flattenedDims = XeGPUDialect::slice( llvm::ArrayRef(indices), llvm::ArrayRef(remainingDims)); return xegpu::SliceAttr::get( getContext(), layoutAttr, DenseI64ArrayAttr::get(getContext(), flattenedDims)); } FailureOr> SliceAttr::delinearizeId(OpBuilder &builder, Location loc, Value linearId) { SliceAttr attr = flatten(); auto parent = dyn_cast(attr.getParent()); return parent.delinearizeId(builder, loc, linearId); } // Implements DistributeLayoutAttr::computeDistributedCoords to generate // instructions for computing multi-dimensional offsets when distributed by // LayoutAttr. FailureOr>> SliceAttr::computeDistributedCoords(OpBuilder &builder, Location loc, Value linearId, ArrayRef shape) { assert(getRank() == static_cast(shape.size()) && "invalid shape."); SmallVector layout; SmallVector subShape; if (isForWorkgroup()) { layout = getEffectiveSgLayoutAsInt(); subShape = getEffectiveSgDataAsInt(); } else if (isForSubgroup()) { layout = getEffectiveLaneLayoutAsInt(); subShape = getEffectiveLaneDataAsInt(); } else { return failure(); } if (subShape.empty()) return failure(); // delinearize Ids auto maybeIds = delinearizeId(builder, loc, linearId); if (failed(maybeIds)) return failure(); // The effective sgIds for offsets computing correspond // to the dims that are not sliced. ArrayRef dims = flatten().getDims().asArrayRef(); SmallVector canonicalIds = XeGPUDialect::slice(ArrayRef(*maybeIds), dims); return genCoordinates(builder, loc, canonicalIds, layout, subShape, shape); } /// Implements DistributeLayoutAttr::computeStaticDistributedCoords to /// compute multi-dimensional offsets for a given linear ID when distributed by /// SliceAttr. Delegates delinearization to the parent LayoutAttr, then uses /// only the non-sliced dimensions for coordinate computation. SmallVector> SliceAttr::computeStaticDistributedCoords(int64_t linearId, ArrayRef shape) { assert(getRank() == static_cast(shape.size()) && "invalid shape."); SmallVector layout; SmallVector subShape; SmallVector instData; if (isForWorkgroup()) { layout = getEffectiveSgLayoutAsInt(); subShape = getEffectiveSgDataAsInt(); } else if (isForSubgroup()) { instData = getEffectiveInstDataAsInt(); layout = getEffectiveLaneLayoutAsInt(); subShape = getEffectiveLaneDataAsInt(); } if (!instData.empty()) { linearId = 0; subShape = instData; } assert(!subShape.empty() && "sgdata or lanedata cannot be empty"); // Delinearize the ID using the parent layout (same as the IR version). SliceAttr flattened = flatten(); auto parent = dyn_cast(flattened.getParent()); SmallVector parentLayoutVec; if (parent.isForWorkgroup()) parentLayoutVec = parent.getEffectiveSgLayoutAsInt(); else parentLayoutVec = parent.getEffectiveLaneLayoutAsInt(); SmallVector order = parent.getEffectiveOrderAsInt(); SmallVector allIds(parentLayoutVec.size()); int64_t remaining = linearId; for (size_t i = 0; i < order.size(); ++i) { int64_t dimIdx = order[i]; allIds[dimIdx] = remaining % parentLayoutVec[dimIdx]; if (i < order.size() - 1) remaining = remaining / parentLayoutVec[dimIdx]; } // The effective IDs for coordinate computation correspond // to the dims that are not sliced. ArrayRef dims = flattened.getDims().asArrayRef(); SmallVector canonicalIds = XeGPUDialect::slice(ArrayRef(allIds), dims); return genStaticCoordinates(canonicalIds, layout, subShape, shape); } bool SliceAttr::isSliceOf(const xegpu::DistributeLayoutAttr &other) { auto flattenedThis = flatten(); // If other is a LayoutAttr, just compare directly with parent of // flattenedThis. if (auto otherLayout = dyn_cast(other)) return flattenedThis.getParent() == otherLayout; // If other is a SliceAttr, flatten it first before comparing. auto flattenedOther = dyn_cast(other).flatten(); // Both must have common parent LayoutAttr. if (flattenedThis.getParent() != flattenedOther.getParent()) return false; // otherFlattened's sliced dims must be a subset of flattenedThis's sliced // dims. llvm::SmallDenseSet thisDims( flattenedThis.getDims().asArrayRef().begin(), flattenedThis.getDims().asArrayRef().end()); return llvm::all_of(flattenedOther.getDims().asArrayRef(), [&](int64_t dim) { return thisDims.contains(dim); }); } bool SliceAttr::isEqualTo(const xegpu::DistributeLayoutAttr &other) { if (dyn_cast(other)) return false; auto flattenedThis = flatten(); auto flattenedOther = dyn_cast(other).flatten(); return ((flattenedThis.getParent() == flattenedOther.getParent()) && (flattenedThis.getDims() == flattenedOther.getDims())); } bool SliceAttr::isCompatibleWith(const xegpu::DistributeLayoutAttr &other, SmallVector shape, xegpu::LayoutKind level) { if (!other) return false; if (getEffectiveOrderAsInt() == other.getEffectiveOrderAsInt()) { // short cut when order is the same, no need to compute coords and compare if (level == xegpu::LayoutKind::Subgroup) if (getEffectiveSgLayoutAsInt() == other.getEffectiveSgLayoutAsInt() && getEffectiveSgDataAsInt() == other.getEffectiveSgDataAsInt()) return true; if (level == xegpu::LayoutKind::Lane) if (getEffectiveLaneLayoutAsInt() == other.getEffectiveLaneLayoutAsInt() && getEffectiveLaneDataAsInt() == other.getEffectiveLaneDataAsInt()) return true; } auto compareCoordsForAllIds = [&](int64_t size) { for (int64_t id : llvm::seq(0, size)) { auto coords = computeStaticDistributedCoords(id, shape); auto otherCoords = other.computeStaticDistributedCoords(id, shape); if (coords != otherCoords) return false; } return true; }; auto flattenedThis = flatten(); auto parent = dyn_cast(flattenedThis.getParent()); if (level == xegpu::LayoutKind::Subgroup) { int64_t wgSize = computeProduct(parent.getEffectiveSgLayoutAsInt()); return compareCoordsForAllIds(wgSize); } if (level == xegpu::LayoutKind::InstData) { return (getEffectiveInstDataAsInt() == other.getEffectiveInstDataAsInt()); } if (level == xegpu::LayoutKind::Lane) { int64_t subgroupSize = computeProduct(parent.getEffectiveLaneLayoutAsInt()); return compareCoordsForAllIds(subgroupSize); } return true; } xegpu::SliceAttr SliceAttr::dropSliceDims(ArrayRef sliceDimsToDrop) { if (sliceDimsToDrop.empty()) return *this; SmallVector sliceDims{getDims().asArrayRef()}; for (auto dim : sliceDimsToDrop) { auto foundIt = std::find(sliceDims.begin(), sliceDims.end(), dim); assert(foundIt != sliceDims.end() && "Expected to find the specified reduction dim in slice dims"); sliceDims.erase(foundIt); } auto sliceWithoutDims = xegpu::SliceAttr::get( this->getContext(), getParent(), DenseI64ArrayAttr::get(this->getContext(), sliceDims)); return sliceWithoutDims; } // Helper function to adjust dimensions from sliced space to parent space // say we have a parent shape of rank 4, and slice dims [1,3], so the sliced // shape is of rank 2, if we want to set unit dim [0] in sliced space, it maps // to dim [0] in parent space; if we want to set unit dim [1] in sliced space, // it maps to dim [2] in parent space. static SmallVector mapSlicedDimsToParentSpace(const SmallVector &dimsToMap, ArrayRef sliceDims) { // Rather than recovering the exact parent rank, we compute a safe upper // bound so that dimsToMap can be adjusted safely. This upper bound is // defined as max(dimsToMap, sliceDims) + 1 + sliceDims.size(). int64_t maxDim = -1; maxDim = std::max(maxDim, *std::max_element(sliceDims.begin(), sliceDims.end())); maxDim = std::max(maxDim, *std::max_element(dimsToMap.begin(), dimsToMap.end())); int64_t parentSpaceRank = maxDim + sliceDims.size() + 1; // get remaining dims in parent space after applying slicing with parent's // slice Dims llvm::SmallDenseSet slicedDimsSet(sliceDims.begin(), sliceDims.end()); SmallVector remainingDims; for (int64_t i = 0; i < parentSpaceRank; ++i) { if (!slicedDimsSet.contains(i)) remainingDims.push_back(i); } // Map unit dims from sliced space to parent space SmallVector adjustUnitDims; for (auto dim : dimsToMap) { int64_t mappedDim = remainingDims[dim]; adjustUnitDims.push_back(mappedDim); } return adjustUnitDims; } // set the layout for unit dims: sg_data, inst_data and lane_data to 1 DistributeLayoutAttr SliceAttr::setUnitDimData(SmallVector unitDims) const { DistributeLayoutAttr parentLayout = getParent(); ArrayRef sliceDims = getDims().asArrayRef(); SmallVector adjustUnitDims = mapSlicedDimsToParentSpace(unitDims, sliceDims); return SliceAttr::get(getContext(), parentLayout.setUnitDimData(adjustUnitDims), getDims()); } // set the layout for the sepcified unit dims: sg_lane and lane_layout to 1 DistributeLayoutAttr SliceAttr::setUnitDimLayout(SmallVector unitDims) const { DistributeLayoutAttr parentLayout = getParent(); ArrayRef sliceDims = getDims().asArrayRef(); SmallVector adjustUnitDims = mapSlicedDimsToParentSpace(unitDims, sliceDims); return SliceAttr::get( getContext(), parentLayout.setUnitDimLayout(adjustUnitDims), getDims()); } // Derive a new layout with sg_data, inst_data and lane_data set to the // specified values for the given dimension DistributeLayoutAttr SliceAttr::setDimData(int64_t dim, int64_t sgData, int64_t instData, int64_t laneData) { ArrayRef sliceDims = getDims().asArrayRef(); auto parent = getParent(); SmallVector dimSet; dimSet.push_back(dim); SmallVector adjustDims = mapSlicedDimsToParentSpace(dimSet, sliceDims); return SliceAttr::get( getContext(), parent.setDimData(adjustDims[0], sgData, instData, laneData), getDims()); } // Derive a new layout by removing dimensions. `dimGroup` specifies a group of // dimensions to be removed in the derived layout. // // Example: drop the 2nd dimension from a rank-3 sliced view. // // Suppose: // xegpu.layout = slice, [1, 3]> // // The slice removes parent dims [1, 3], so the sliced-space dims map to // parent dims [V0, V2, V4]. // // If we drop sliced-space dim 1 (the 2nd dim), that corresponds to dropping // parent dim 2, result in parent layout [V0, V1, V3, V4] after dropping. // After parent dim 2 is removed, sliced dims [1, 3] must be reindexed to [1, // 2]. // // Result: // xegpu.layout = slice, [1, 2]> DistributeLayoutAttr SliceAttr::dropDims(SmallVector dimGroup) { // Map the sliced dims from parent space to collapsed space SmallVector sliceDims = llvm::to_vector(getDims().asArrayRef()); SmallVector dimsInParentSpace = mapSlicedDimsToParentSpace(dimGroup, sliceDims); auto droppedParent = getParent().dropDims(dimsInParentSpace); // Adjust the sliced dims after dropping dims in parent space. For example, if // we drop dim 2 in parent space, the dims after dim 2 will all be shifted by // 1, so sliced dim 3 will be adjusted to 2. SmallVector newSliceDims; for (int64_t d : sliceDims) { int64_t offset = llvm::count_if(dimsInParentSpace, [&](int64_t s) { return s < d; }); newSliceDims.push_back(d - offset); } return SliceAttr::get(getContext(), droppedParent, DenseI64ArrayAttr::get(getContext(), newSliceDims)); } // Derive a new layout by collapsing dimensions. // `dimGroup` specifies a group of adjacent dimensions // that are collapsed into a single dimension in the derived layout. DistributeLayoutAttr SliceAttr::collapseDims(SmallVector dimGroup) { // Map the sliced dims from parent space to collapsed space SmallVector sliceDims = llvm::to_vector(getDims().asArrayRef()); assert("expect sliceDims not being collapsed" && llvm::none_of(dimGroup, [&](int64_t dim) { return llvm::is_contained(sliceDims, dim); })); SmallVector dimsInParentSpace = mapSlicedDimsToParentSpace(dimGroup, sliceDims); auto collapsedParent = getParent().collapseDims(dimsInParentSpace); return SliceAttr::get(getContext(), collapsedParent, DenseI64ArrayAttr::get(getContext(), sliceDims)); } SmallVector getPermForParentLayout(ArrayRef sliceDims, ArrayRef permutation) { SmallVector sortedSliceDims = llvm::to_vector(sliceDims); llvm::sort(sortedSliceDims); for (size_t i = 1; i < sortedSliceDims.size(); ++i) { assert((sortedSliceDims[i] == sortedSliceDims[i - 1] + 1) && "slice dims non consecutive, cannot be transposed"); } SmallVector permForParent; if (sortedSliceDims.front() == 0) { // Example: sliceDims.size() = 2, permutation= {1, 0} // result: {3, 2, 1, 0}. for (int64_t dim : permutation) permForParent.push_back(dim + sortedSliceDims.size()); for (int64_t i = sortedSliceDims.size() - 1; i >= 0; --i) permForParent.push_back(i); } else { // Example: sliceDims.size() = 2, permutation = {0, 1} // result: {3, 2, 0, 1}. for (int64_t i = sortedSliceDims.size() - 1; i >= 0; --i) permForParent.push_back(i + permutation.size()); for (int64_t dim : permutation) permForParent.push_back(dim); } return permForParent; } // Derive a new layout by transpose the layout using `permutation`. DistributeLayoutAttr SliceAttr::transposeDims(ArrayRef permutation) { SmallVector sliceDims = llvm::to_vector(getDims().asArrayRef()); DistributeLayoutAttr parent = getParent(); SmallVector permForParent = getPermForParentLayout(sliceDims, permutation); auto transposedParent = parent.transposeDims(permForParent); return SliceAttr::get(getContext(), transposedParent, DenseI64ArrayAttr::get(getContext(), sliceDims)); } /// Check if this layout is a transpose of another layout. bool SliceAttr::isTransposeOf(const xegpu::DistributeLayoutAttr &other, ArrayRef perm, const xegpu::LayoutKind kind) { // other must be a SliceAttr with the same slice dims. auto otherSlice = dyn_cast(other); if (!otherSlice || getDims() != otherSlice.getDims()) return false; // check whether the parent layout is transpose of each other. SmallVector sliceDims = llvm::to_vector(getDims().asArrayRef()); DistributeLayoutAttr parent = getParent(); SmallVector permForParent = getPermForParentLayout(sliceDims, perm); auto otherParent = otherSlice.getParent(); return parent.isTransposeOf(otherParent, permForParent, kind); } //===----------------------------------------------------------------------===// // XeGPU_RangeAttr //===----------------------------------------------------------------------===// LogicalResult RangeAttr::verify(llvm::function_ref emitError, IntegerAttr startOfRange, IntegerAttr endOfRange) { if (startOfRange.getInt() >= endOfRange.getInt()) return emitError() << "'end' : " << endOfRange.getInt() << " must be greater than 'start' : " << startOfRange.getInt(); return success(); } //===----------------------------------------------------------------------===// // XeGPU_TensorDescType //===----------------------------------------------------------------------===// mlir::Type TensorDescType::parse(AsmParser &parser) { llvm::SmallVector shape; mlir::Type elementType; mlir::FailureOr encoding; mlir::FailureOr layout; // Parse literal '<' if (parser.parseLess()) return {}; auto shapeLoc = parser.getCurrentLocation(); if (mlir::failed(parser.parseDimensionList(shape))) { parser.emitError(shapeLoc, "failed to parse parameter 'shape'"); return {}; } auto elemTypeLoc = parser.getCurrentLocation(); if (mlir::failed(parser.parseType(elementType))) { parser.emitError(elemTypeLoc, "failed to parse parameter 'elementType'"); return {}; } // parse optional attributes while (mlir::succeeded(parser.parseOptionalComma())) { mlir::Attribute attr; ParseResult res = parser.parseAttribute(attr); if (mlir::succeeded(res)) { if (mlir::isa(attr)) { layout = attr; continue; } if (mlir::isa(attr)) { encoding = attr; continue; } } return {}; } // Parse literal '>' if (parser.parseGreater()) return {}; MLIRContext *ctxt = parser.getContext(); return TensorDescType::getChecked( [&]() { return parser.emitError(parser.getNameLoc()); }, ctxt, shape, elementType, encoding.value_or(BlockTensorDescAttr::get(ctxt)), layout.value_or(mlir::Attribute())); } void TensorDescType::print(AsmPrinter &printer) const { printer << "<"; auto shape = getShape(); for (int64_t dim : shape) { if (mlir::ShapedType::isDynamic(dim)) printer << '?'; else printer << dim; printer << 'x'; } printer << getElementType(); auto encoding = getEncoding(); auto blockAttr = llvm::dyn_cast_if_present(encoding); if (encoding && (!blockAttr || !blockAttr.hasDefaultsOnly())) printer << ", " << encoding; if (auto layout = getLayout()) printer << ", " << layout; printer << ">"; } TensorDescType TensorDescType::get(llvm::ArrayRef shape, mlir::Type elementType, int array_length, bool boundary_check, MemorySpace memory_space, mlir::Attribute layout) { auto *context = elementType.getContext(); auto attr = BlockTensorDescAttr::get(context, memory_space, array_length, boundary_check); return Base::get(context, shape, elementType, attr, layout); } LogicalResult TensorDescType::verify(llvm::function_ref emitError, llvm::ArrayRef shape, mlir::Type elementType, mlir::Attribute encoding, mlir::Attribute layout) { size_t rank = shape.size(); if (rank == 0) return emitError() << "expected non-zero rank tensor"; auto blockAttr = mlir::dyn_cast_if_present(encoding); if (blockAttr) { MemorySpaceAttr memorySpaceAttr = blockAttr.getMemorySpace(); if (rank > 1 && memorySpaceAttr && memorySpaceAttr.getValue() == MemorySpace::SLM) return emitError() << "SLM is only supported for 1D block tensor"; } if (!elementType.isIntOrFloat()) return emitError() << "unsupported element type " << elementType << ": expected integer or float"; if (auto layoutAttr = mlir::dyn_cast_if_present(layout)) { if (rank != (size_t)layoutAttr.getRank()) return emitError() << "expected layout rank to match tensor rank"; if (!layoutAttr.isDistributable(SmallVector(shape))) { std::string shapeStr; llvm::raw_string_ostream stream(shapeStr); llvm::interleaveComma(shape, stream); return emitError() << "cannot distribute [" << shapeStr << "] using " << layoutAttr; } } return success(); } //===----------------------------------------------------------------------===// // XeGPU_MemDescType //===----------------------------------------------------------------------===// mlir::Type MemDescType::parse(AsmParser &parser) { llvm::SmallVector shape; mlir::Type elementType; mlir::FailureOr layout; // Parse literal '<' if (parser.parseLess()) return {}; auto shapeLoc = parser.getCurrentLocation(); if (mlir::failed(parser.parseDimensionList(shape, false, true))) { parser.emitError(shapeLoc, "failed to parse parameter 'shape'"); return {}; } auto elemTypeLoc = parser.getCurrentLocation(); if (mlir::failed(parser.parseType(elementType))) { parser.emitError(elemTypeLoc, "failed to parse parameter 'elementType'"); return {}; } // parse optional attributes if (mlir::succeeded(parser.parseOptionalComma())) { MemLayoutAttr attr; ParseResult res = parser.parseAttribute(attr); if (mlir::failed(res)) return {}; layout = attr; } // Parse literal '>' if (parser.parseGreater()) return {}; MLIRContext *ctxt = parser.getContext(); return MemDescType::getChecked( [&]() { return parser.emitError(parser.getNameLoc()); }, ctxt, shape, elementType, layout.value_or(MemLayoutAttr())); } void MemDescType::print(AsmPrinter &printer) const { printer << "<"; printer.printDimensionList(getShape()); printer << 'x'; printer << getElementType(); if (auto layout = getMemLayout()) printer << ", " << layout; printer << ">"; } //===----------------------------------------------------------------------===// // XeGPU_MemDescType //===----------------------------------------------------------------------===// Attribute MemLayoutAttr::parse(AsmParser &parser, Type type) { auto *context = parser.getContext(); llvm::SMLoc loc = parser.getCurrentLocation(); llvm::SmallDenseSet seenKeys; SmallVector attributes; auto parseElt = [&]() -> ParseResult { StringRef nameId; if (failed(parser.parseKeyword(&nameId))) return parser.emitError(loc, "expected valid attribute name"); if (!seenKeys.insert(nameId).second) return parser.emitError(loc, "duplicate key '") << nameId << " in mem layout attribute"; if (failed(parser.parseEqual())) return failure(); Attribute attr; if (failed(parser.parseAttribute(attr))) return failure(); attributes.emplace_back(nameId, attr); return success(); }; // Parse literal '<' if (parser.parseLess()) return {}; if (failed(parser.parseCommaSeparatedList(parseElt))) return {}; // Parse literal '>' if (parser.parseGreater()) return {}; return parser.getChecked( loc, context, DictionaryAttr::get(context, attributes)); } void MemLayoutAttr::print(AsmPrinter &printer) const { printer << "<"; ArrayRef attrs = getAttrs().getValue(); for (size_t i = 0; i < attrs.size(); i++) { printer << attrs[i].getName().str() << " = " << attrs[i].getValue(); if (i < attrs.size() - 1) printer << ", "; } printer << ">"; } // a helper utility to perform binary operation on OpFoldResult. // If both a and b are attributes, it will simply return the result. // Otherwise, the corresponding arith op will be generated, and an // contant op will be created if one of them is an attribute. template OpFoldResult genBinOp(OpFoldResult a, OpFoldResult b, Location loc, OpBuilder &builder) { auto aVal = getValueOrCreateConstantIndexOp(builder, loc, a); auto bVal = getValueOrCreateConstantIndexOp(builder, loc, b); return ArithOp::create(builder, loc, aVal, bVal).getResult(); } // a helper utility to perform division operation on OpFoldResult and int64_t. #define div(a, b) \ genBinOp(a, builder.getIndexAttr(b), loc, builder) // a helper utility to perform reminder operation on OpFoldResult and int64_t. #define rem(a, b) \ genBinOp(a, builder.getIndexAttr(b), loc, builder) // a helper utility to perform multiply operation on OpFoldResult and int64_t. #define mul(a, b) \ genBinOp(a, builder.getIndexAttr(b), loc, builder) // a helper utility to perform addition operation on two OpFoldResult. #define add(a, b) genBinOp(a, b, loc, builder) // block the given offsets according to the block shape // say the original offset is [y, x], and the block shape is [By, Bx], // then the blocked offset is [y/By, x/Bx, y%By, x%Bx] SmallVector getBlockedOffsets(OpBuilder &builder, Location loc, ArrayRef offsets, ArrayRef blockShape) { assert(offsets.size() == blockShape.size() && "offsets and blockShape must have the same size"); SmallVector blockedOffsets; SmallVector divs, rems; for (auto [offset, block] : llvm::zip(offsets, blockShape)) { divs.push_back(div(offset, block)); rems.push_back(rem(offset, block)); } blockedOffsets.append(divs.begin(), divs.end()); blockedOffsets.append(rems.begin(), rems.end()); return blockedOffsets; } // Get strides as vector of integer for MemDesc. SmallVector MemDescType::getStrideShape() { SmallVector matrixShape(getShape().begin(), getShape().end()); ArrayAttr strideAttr = getStrideAttr(); SmallVector strides; for (Attribute attr : strideAttr.getValue()) { strides.push_back(cast(attr).getInt()); } SmallVector innerBlkShape = getBlockShape(); // get perm from FCD to LCD // perm[i] = the dim with i-th smallest stride SmallVector perm = llvm::to_vector<4>(llvm::seq(0, strides.size())); llvm::sort(perm, [&](int a, int b) { return strides[a] < strides[b]; }); assert(strides[perm[0]] == 1 && "inner most dim must have stride 1"); SmallVector innerBlkStride(innerBlkShape.size()); innerBlkStride[perm[0]] = 1; for (size_t i = 1; i < perm.size(); ++i) innerBlkStride[perm[i]] = innerBlkStride[perm[i - 1]] * innerBlkShape[perm[i - 1]]; // compute the original matrix shape using the stride info // and compute the number of blocks in each dimension // The shape of highest dim can't be derived from stride info, // but doesn't impact the stride computation for blocked layout. SmallVector matrixShapeOrig(matrixShape.size()); SmallVector BlkShapeOrig(matrixShape.size()); for (size_t i = 0; i < perm.size() - 1; ++i) { matrixShapeOrig[perm[i]] = strides[perm[i + 1]] / strides[perm[i]]; BlkShapeOrig[perm[i]] = matrixShapeOrig[perm[i]] / innerBlkShape[perm[i]]; } int64_t innerBlkSize = 1; for (auto s : innerBlkShape) innerBlkSize *= s; SmallVector outerBlkStride(matrixShape.size()); outerBlkStride[perm[0]] = innerBlkSize; for (size_t i = 0; i < perm.size() - 1; ++i) { outerBlkStride[perm[i + 1]] = outerBlkStride[perm[i]] * BlkShapeOrig[perm[i]]; } // combine the inner and outer strides SmallVector blockedStrides; blockedStrides.append(outerBlkStride.begin(), outerBlkStride.end()); blockedStrides.append(innerBlkStride.begin(), innerBlkStride.end()); return blockedStrides; } // Calculate the linear offset using the blocked offsets and stride Value MemDescType::getLinearOffsets(OpBuilder &builder, Location loc, ArrayRef offsets) { SmallVector matrixShape(getShape().begin(), getShape().end()); SmallVector blockShape = getBlockShape(); SmallVector strides = getStrideShape(); SmallVector blockedOffsets; // blockshape equal to matrixshape means no blocking if (llvm::equal(blockShape, matrixShape)) { // remove the outer dims from strides strides.erase(strides.begin(), strides.begin() + matrixShape.size()); } else { assert(offsets.size() == blockShape.size() && "offsets and blockShape must have the same size"); // say the original offset is [y, x], and the block shape is [By, Bx], // then the blocked offset is [y/By, x/Bx, y%By, x%Bx] SmallVector divs, rems; for (auto [offset, block] : llvm::zip(offsets, blockShape)) { divs.push_back(div(offset, block)); rems.push_back(rem(offset, block)); } blockedOffsets.append(divs.begin(), divs.end()); blockedOffsets.append(rems.begin(), rems.end()); offsets = blockedOffsets; } // Start with initial value as matrix descriptor's base offset. Value linearOffset = arith::ConstantIndexOp::create(builder, loc, 0); for (size_t i = 0; i < offsets.size(); ++i) { OpFoldResult mulResult = mul(offsets[i], strides[i]); Value mulVal = getValueOrCreateConstantIndexOp(builder, loc, mulResult); linearOffset = arith::AddIOp::create(builder, loc, mulVal, linearOffset); } return linearOffset; } } // namespace xegpu } // namespace mlir #include #define GET_ATTRDEF_CLASSES #include #define GET_TYPEDEF_CLASSES #include